* [PATCH 0/4] dmaengine: stm32-dma3: improvements and helper functions
@ 2025-11-03 10:15 Amelie Delaunay
2025-11-03 10:15 ` [PATCH 1/4] dmaengine: stm32-dma3: use module_platform_driver Amelie Delaunay
` (3 more replies)
0 siblings, 4 replies; 5+ messages in thread
From: Amelie Delaunay @ 2025-11-03 10:15 UTC (permalink / raw)
To: Vinod Koul, Maxime Coquelin, Alexandre Torgue
Cc: dmaengine, linux-stm32, linux-arm-kernel, linux-kernel,
Amelie Delaunay
This series introduces improvements and helper functions for channel
and driver management.
It enables proper unloading of the stm32_dma3 module, replacing the
previous subsys_initcall() mechanism with module_plaform_driver().
It introduces helfer functions to take and release the channel semaphore,
and restores the semaphore state on resume, considering the possible
reset of CxSEMCR register during suspend.
It also adds a helper to retrieve the device pointer from the dma_device
structure contained in stm32_dma3_ddata, simplifying the code.
Signed-off-by: Amelie Delaunay <amelie.delaunay@foss.st.com>
---
Amelie Delaunay (4):
dmaengine: stm32-dma3: use module_platform_driver
dmaengine: stm32-dma3: introduce channel semaphore helpers
dmaengine: stm32-dma3: restore channel semaphore status after suspend
dmaengine: stm32-dma3: introduce ddata2dev helper
drivers/dma/stm32/stm32-dma3.c | 166 ++++++++++++++++++++++++++++++++++-------
1 file changed, 137 insertions(+), 29 deletions(-)
---
base-commit: 398035178503bf662281bbffb4bebce1460a4bc5
change-id: 20251103-dma3_improv-b4b36ee231e4
Best regards,
--
Amelie Delaunay <amelie.delaunay@foss.st.com>
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH 1/4] dmaengine: stm32-dma3: use module_platform_driver
2025-11-03 10:15 [PATCH 0/4] dmaengine: stm32-dma3: improvements and helper functions Amelie Delaunay
@ 2025-11-03 10:15 ` Amelie Delaunay
2025-11-03 10:15 ` [PATCH 2/4] dmaengine: stm32-dma3: introduce channel semaphore helpers Amelie Delaunay
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Amelie Delaunay @ 2025-11-03 10:15 UTC (permalink / raw)
To: Vinod Koul, Maxime Coquelin, Alexandre Torgue
Cc: dmaengine, linux-stm32, linux-arm-kernel, linux-kernel,
Amelie Delaunay
Without module_platform_driver(), stm32-dma3 doesn't have a
module_exit procedure. Once stm32-dma3 module is inserted, it
can't be removed, marked busy.
Use module_platform_driver() instead of subsys_initcall() to register
(insmod) and unregister (rmmod) stm32-dma3 driver.
Signed-off-by: Amelie Delaunay <amelie.delaunay@foss.st.com>
---
drivers/dma/stm32/stm32-dma3.c | 7 +------
1 file changed, 1 insertion(+), 6 deletions(-)
diff --git a/drivers/dma/stm32/stm32-dma3.c b/drivers/dma/stm32/stm32-dma3.c
index 50e7106c5cb7..9500164c8f68 100644
--- a/drivers/dma/stm32/stm32-dma3.c
+++ b/drivers/dma/stm32/stm32-dma3.c
@@ -1914,12 +1914,7 @@ static struct platform_driver stm32_dma3_driver = {
},
};
-static int __init stm32_dma3_init(void)
-{
- return platform_driver_register(&stm32_dma3_driver);
-}
-
-subsys_initcall(stm32_dma3_init);
+module_platform_driver(stm32_dma3_driver);
MODULE_DESCRIPTION("STM32 DMA3 controller driver");
MODULE_AUTHOR("Amelie Delaunay <amelie.delaunay@foss.st.com>");
--
2.43.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH 2/4] dmaengine: stm32-dma3: introduce channel semaphore helpers
2025-11-03 10:15 [PATCH 0/4] dmaengine: stm32-dma3: improvements and helper functions Amelie Delaunay
2025-11-03 10:15 ` [PATCH 1/4] dmaengine: stm32-dma3: use module_platform_driver Amelie Delaunay
@ 2025-11-03 10:15 ` Amelie Delaunay
2025-11-03 10:15 ` [PATCH 3/4] dmaengine: stm32-dma3: restore channel semaphore status after suspend Amelie Delaunay
2025-11-03 10:15 ` [PATCH 4/4] dmaengine: stm32-dma3: introduce ddata2dev helper Amelie Delaunay
3 siblings, 0 replies; 5+ messages in thread
From: Amelie Delaunay @ 2025-11-03 10:15 UTC (permalink / raw)
To: Vinod Koul, Maxime Coquelin, Alexandre Torgue
Cc: dmaengine, linux-stm32, linux-arm-kernel, linux-kernel,
Amelie Delaunay
Before restoring semaphore status after suspend, introduce new functions
to handle semaphore operations :
- stm32_dma3_get_chan_sem() to take the semaphore
- stm32_dma3_put_chan_sem() to release the semaphore
Also, use a new boolean variable semaphore_taken, which is true when the
semaphore has been taken and false when it has been released.
Signed-off-by: Amelie Delaunay <amelie.delaunay@foss.st.com>
---
drivers/dma/stm32/stm32-dma3.c | 55 +++++++++++++++++++++++++++++++++---------
1 file changed, 44 insertions(+), 11 deletions(-)
diff --git a/drivers/dma/stm32/stm32-dma3.c b/drivers/dma/stm32/stm32-dma3.c
index 9500164c8f68..a1583face7ec 100644
--- a/drivers/dma/stm32/stm32-dma3.c
+++ b/drivers/dma/stm32/stm32-dma3.c
@@ -288,6 +288,7 @@ struct stm32_dma3_chan {
u32 fifo_size;
u32 max_burst;
bool semaphore_mode;
+ bool semaphore_taken;
struct stm32_dma3_dt_conf dt_config;
struct dma_slave_config dma_config;
u8 config_set;
@@ -1063,11 +1064,50 @@ static irqreturn_t stm32_dma3_chan_irq(int irq, void *devid)
return IRQ_HANDLED;
}
+static int stm32_dma3_get_chan_sem(struct stm32_dma3_chan *chan)
+{
+ struct stm32_dma3_ddata *ddata = to_stm32_dma3_ddata(chan);
+ u32 csemcr, ccid;
+
+ csemcr = readl_relaxed(ddata->base + STM32_DMA3_CSEMCR(chan->id));
+ /* Make an attempt to take the channel semaphore if not already taken */
+ if (!(csemcr & CSEMCR_SEM_MUTEX)) {
+ writel_relaxed(CSEMCR_SEM_MUTEX, ddata->base + STM32_DMA3_CSEMCR(chan->id));
+ csemcr = readl_relaxed(ddata->base + STM32_DMA3_CSEMCR(chan->id));
+ }
+
+ /* Check if channel is under CID1 control */
+ ccid = FIELD_GET(CSEMCR_SEM_CCID, csemcr);
+ if (!(csemcr & CSEMCR_SEM_MUTEX) || ccid != CCIDCFGR_CID1)
+ goto bad_cid;
+
+ chan->semaphore_taken = true;
+ dev_dbg(chan2dev(chan), "under CID1 control (semcr=0x%08x)\n", csemcr);
+
+ return 0;
+
+bad_cid:
+ chan->semaphore_taken = false;
+ dev_err(chan2dev(chan), "not under CID1 control (in-use by CID%d)\n", ccid);
+
+ return -EACCES;
+}
+
+static void stm32_dma3_put_chan_sem(struct stm32_dma3_chan *chan)
+{
+ struct stm32_dma3_ddata *ddata = to_stm32_dma3_ddata(chan);
+
+ if (chan->semaphore_taken) {
+ writel_relaxed(0, ddata->base + STM32_DMA3_CSEMCR(chan->id));
+ chan->semaphore_taken = false;
+ dev_dbg(chan2dev(chan), "no more under CID1 control\n");
+ }
+}
+
static int stm32_dma3_alloc_chan_resources(struct dma_chan *c)
{
struct stm32_dma3_chan *chan = to_stm32_dma3_chan(c);
struct stm32_dma3_ddata *ddata = to_stm32_dma3_ddata(chan);
- u32 id = chan->id, csemcr, ccid;
int ret;
ret = pm_runtime_resume_and_get(ddata->dma_dev.dev);
@@ -1092,16 +1132,9 @@ static int stm32_dma3_alloc_chan_resources(struct dma_chan *c)
/* Take the channel semaphore */
if (chan->semaphore_mode) {
- writel_relaxed(CSEMCR_SEM_MUTEX, ddata->base + STM32_DMA3_CSEMCR(id));
- csemcr = readl_relaxed(ddata->base + STM32_DMA3_CSEMCR(id));
- ccid = FIELD_GET(CSEMCR_SEM_CCID, csemcr);
- /* Check that the channel is well taken */
- if (ccid != CCIDCFGR_CID1) {
- dev_err(chan2dev(chan), "Not under CID1 control (in-use by CID%d)\n", ccid);
- ret = -EPERM;
+ ret = stm32_dma3_get_chan_sem(chan);
+ if (ret)
goto err_pool_destroy;
- }
- dev_dbg(chan2dev(chan), "Under CID1 control (semcr=0x%08x)\n", csemcr);
}
return 0;
@@ -1135,7 +1168,7 @@ static void stm32_dma3_free_chan_resources(struct dma_chan *c)
/* Release the channel semaphore */
if (chan->semaphore_mode)
- writel_relaxed(0, ddata->base + STM32_DMA3_CSEMCR(chan->id));
+ stm32_dma3_put_chan_sem(chan);
pm_runtime_put_sync(ddata->dma_dev.dev);
--
2.43.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH 3/4] dmaengine: stm32-dma3: restore channel semaphore status after suspend
2025-11-03 10:15 [PATCH 0/4] dmaengine: stm32-dma3: improvements and helper functions Amelie Delaunay
2025-11-03 10:15 ` [PATCH 1/4] dmaengine: stm32-dma3: use module_platform_driver Amelie Delaunay
2025-11-03 10:15 ` [PATCH 2/4] dmaengine: stm32-dma3: introduce channel semaphore helpers Amelie Delaunay
@ 2025-11-03 10:15 ` Amelie Delaunay
2025-11-03 10:15 ` [PATCH 4/4] dmaengine: stm32-dma3: introduce ddata2dev helper Amelie Delaunay
3 siblings, 0 replies; 5+ messages in thread
From: Amelie Delaunay @ 2025-11-03 10:15 UTC (permalink / raw)
To: Vinod Koul, Maxime Coquelin, Alexandre Torgue
Cc: dmaengine, linux-stm32, linux-arm-kernel, linux-kernel,
Amelie Delaunay
Depending on the power state reached during suspend, the CxSEMCR register
could have been reset, and the semaphore released.
On resume, try to take the semaphore again. If the semaphore cannot be
taken, an error log displaying the channel number and channel user is
generated.
This requires introducing two new functions:
stm32_dma3_pm_suspend(), where the status of each channel is checked
because suspension is not allowed if a channel is still running;
stm32_dma3_pm_resume(), where the channel semaphore is restored if it was
taken before suspend.
Signed-off-by: Amelie Delaunay <amelie.delaunay@foss.st.com>
---
drivers/dma/stm32/stm32-dma3.c | 75 +++++++++++++++++++++++++++++++++++++++++-
1 file changed, 74 insertions(+), 1 deletion(-)
diff --git a/drivers/dma/stm32/stm32-dma3.c b/drivers/dma/stm32/stm32-dma3.c
index a1583face7ec..29ea510fa539 100644
--- a/drivers/dma/stm32/stm32-dma3.c
+++ b/drivers/dma/stm32/stm32-dma3.c
@@ -1237,6 +1237,10 @@ static struct dma_async_tx_descriptor *stm32_dma3_prep_dma_memcpy(struct dma_cha
bool prevent_refactor = !!FIELD_GET(STM32_DMA3_DT_NOPACK, chan->dt_config.tr_conf) ||
!!FIELD_GET(STM32_DMA3_DT_NOREFACT, chan->dt_config.tr_conf);
+ /* Semaphore could be lost during suspend/resume */
+ if (chan->semaphore_mode && !chan->semaphore_taken)
+ return NULL;
+
count = stm32_dma3_get_ll_count(chan, len, prevent_refactor);
swdesc = stm32_dma3_chan_desc_alloc(chan, count);
@@ -1297,6 +1301,10 @@ static struct dma_async_tx_descriptor *stm32_dma3_prep_slave_sg(struct dma_chan
!!FIELD_GET(STM32_DMA3_DT_NOREFACT, chan->dt_config.tr_conf);
int ret;
+ /* Semaphore could be lost during suspend/resume */
+ if (chan->semaphore_mode && !chan->semaphore_taken)
+ return NULL;
+
count = 0;
for_each_sg(sgl, sg, sg_len, i)
count += stm32_dma3_get_ll_count(chan, sg_dma_len(sg), prevent_refactor);
@@ -1383,6 +1391,10 @@ static struct dma_async_tx_descriptor *stm32_dma3_prep_dma_cyclic(struct dma_cha
u32 count, i, ctr1, ctr2;
int ret;
+ /* Semaphore could be lost during suspend/resume */
+ if (chan->semaphore_mode && !chan->semaphore_taken)
+ return NULL;
+
if (!buf_len || !period_len || period_len > STM32_DMA3_MAX_BLOCK_SIZE) {
dev_err(chan2dev(chan), "Invalid buffer/period length\n");
return NULL;
@@ -1932,8 +1944,69 @@ static int stm32_dma3_runtime_resume(struct device *dev)
return ret;
}
+static int stm32_dma3_pm_suspend(struct device *dev)
+{
+ struct stm32_dma3_ddata *ddata = dev_get_drvdata(dev);
+ struct dma_device *dma_dev = &ddata->dma_dev;
+ struct dma_chan *c;
+ int ccr, ret;
+
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret < 0)
+ return ret;
+
+ list_for_each_entry(c, &dma_dev->channels, device_node) {
+ struct stm32_dma3_chan *chan = to_stm32_dma3_chan(c);
+
+ ccr = readl_relaxed(ddata->base + STM32_DMA3_CCR(chan->id));
+ if (ccr & CCR_EN) {
+ dev_warn(dev, "Suspend is prevented: %s still in use by %s\n",
+ dma_chan_name(c), dev_name(c->slave));
+ pm_runtime_put_sync(dev);
+ return -EBUSY;
+ }
+ }
+
+ pm_runtime_put_sync(dev);
+
+ pm_runtime_force_suspend(dev);
+
+ return 0;
+}
+
+static int stm32_dma3_pm_resume(struct device *dev)
+{
+ struct stm32_dma3_ddata *ddata = dev_get_drvdata(dev);
+ struct dma_device *dma_dev = &ddata->dma_dev;
+ struct dma_chan *c;
+ int ret;
+
+ ret = pm_runtime_force_resume(dev);
+ if (ret < 0)
+ return ret;
+
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Channel semaphores need to be restored in case of registers reset during low power.
+ * stm32_dma3_get_chan_sem() will prior check the semaphore status.
+ */
+ list_for_each_entry(c, &dma_dev->channels, device_node) {
+ struct stm32_dma3_chan *chan = to_stm32_dma3_chan(c);
+
+ if (chan->semaphore_mode && chan->semaphore_taken)
+ stm32_dma3_get_chan_sem(chan);
+ }
+
+ pm_runtime_put_sync(dev);
+
+ return 0;
+}
+
static const struct dev_pm_ops stm32_dma3_pm_ops = {
- SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
+ SYSTEM_SLEEP_PM_OPS(stm32_dma3_pm_suspend, stm32_dma3_pm_resume)
RUNTIME_PM_OPS(stm32_dma3_runtime_suspend, stm32_dma3_runtime_resume, NULL)
};
--
2.43.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH 4/4] dmaengine: stm32-dma3: introduce ddata2dev helper
2025-11-03 10:15 [PATCH 0/4] dmaengine: stm32-dma3: improvements and helper functions Amelie Delaunay
` (2 preceding siblings ...)
2025-11-03 10:15 ` [PATCH 3/4] dmaengine: stm32-dma3: restore channel semaphore status after suspend Amelie Delaunay
@ 2025-11-03 10:15 ` Amelie Delaunay
3 siblings, 0 replies; 5+ messages in thread
From: Amelie Delaunay @ 2025-11-03 10:15 UTC (permalink / raw)
To: Vinod Koul, Maxime Coquelin, Alexandre Torgue
Cc: dmaengine, linux-stm32, linux-arm-kernel, linux-kernel,
Amelie Delaunay
ddata2dev helper returns the device pointer from struct dma_device stored
in stm32_dma3_ddata structure.
Device pointer from struct dma_device has been initialized with &pdev->dev,
so the ddata2dev helper returns &pdev->dev.
Signed-off-by: Amelie Delaunay <amelie.delaunay@foss.st.com>
---
drivers/dma/stm32/stm32-dma3.c | 29 ++++++++++++++++++-----------
1 file changed, 18 insertions(+), 11 deletions(-)
diff --git a/drivers/dma/stm32/stm32-dma3.c b/drivers/dma/stm32/stm32-dma3.c
index 29ea510fa539..9f49ef8e2972 100644
--- a/drivers/dma/stm32/stm32-dma3.c
+++ b/drivers/dma/stm32/stm32-dma3.c
@@ -333,6 +333,11 @@ static struct device *chan2dev(struct stm32_dma3_chan *chan)
return &chan->vchan.chan.dev->device;
}
+static struct device *ddata2dev(struct stm32_dma3_ddata *ddata)
+{
+ return ddata->dma_dev.dev;
+}
+
static void stm32_dma3_chan_dump_reg(struct stm32_dma3_chan *chan)
{
struct stm32_dma3_ddata *ddata = to_stm32_dma3_ddata(chan);
@@ -392,6 +397,7 @@ static void stm32_dma3_chan_dump_hwdesc(struct stm32_dma3_chan *chan,
} else {
dev_dbg(chan2dev(chan), "X\n");
}
+
}
static struct stm32_dma3_swdesc *stm32_dma3_chan_desc_alloc(struct stm32_dma3_chan *chan, u32 count)
@@ -1110,7 +1116,7 @@ static int stm32_dma3_alloc_chan_resources(struct dma_chan *c)
struct stm32_dma3_ddata *ddata = to_stm32_dma3_ddata(chan);
int ret;
- ret = pm_runtime_resume_and_get(ddata->dma_dev.dev);
+ ret = pm_runtime_resume_and_get(ddata2dev(ddata));
if (ret < 0)
return ret;
@@ -1144,7 +1150,7 @@ static int stm32_dma3_alloc_chan_resources(struct dma_chan *c)
chan->lli_pool = NULL;
err_put_sync:
- pm_runtime_put_sync(ddata->dma_dev.dev);
+ pm_runtime_put_sync(ddata2dev(ddata));
return ret;
}
@@ -1170,7 +1176,7 @@ static void stm32_dma3_free_chan_resources(struct dma_chan *c)
if (chan->semaphore_mode)
stm32_dma3_put_chan_sem(chan);
- pm_runtime_put_sync(ddata->dma_dev.dev);
+ pm_runtime_put_sync(ddata2dev(ddata));
/* Reset configuration */
memset(&chan->dt_config, 0, sizeof(chan->dt_config));
@@ -1610,11 +1616,11 @@ static bool stm32_dma3_filter_fn(struct dma_chan *c, void *fn_param)
if (!(mask & BIT(chan->id)))
return false;
- ret = pm_runtime_resume_and_get(ddata->dma_dev.dev);
+ ret = pm_runtime_resume_and_get(ddata2dev(ddata));
if (ret < 0)
return false;
semcr = readl_relaxed(ddata->base + STM32_DMA3_CSEMCR(chan->id));
- pm_runtime_put_sync(ddata->dma_dev.dev);
+ pm_runtime_put_sync(ddata2dev(ddata));
/* Check if chan is free */
if (semcr & CSEMCR_SEM_MUTEX)
@@ -1636,7 +1642,7 @@ static struct dma_chan *stm32_dma3_of_xlate(struct of_phandle_args *dma_spec, st
struct dma_chan *c;
if (dma_spec->args_count < 3) {
- dev_err(ddata->dma_dev.dev, "Invalid args count\n");
+ dev_err(ddata2dev(ddata), "Invalid args count\n");
return NULL;
}
@@ -1645,14 +1651,14 @@ static struct dma_chan *stm32_dma3_of_xlate(struct of_phandle_args *dma_spec, st
conf.tr_conf = dma_spec->args[2];
if (conf.req_line >= ddata->dma_requests) {
- dev_err(ddata->dma_dev.dev, "Invalid request line\n");
+ dev_err(ddata2dev(ddata), "Invalid request line\n");
return NULL;
}
/* Request dma channel among the generic dma controller list */
c = dma_request_channel(mask, stm32_dma3_filter_fn, &conf);
if (!c) {
- dev_err(ddata->dma_dev.dev, "No suitable channel found\n");
+ dev_err(ddata2dev(ddata), "No suitable channel found\n");
return NULL;
}
@@ -1665,6 +1671,7 @@ static struct dma_chan *stm32_dma3_of_xlate(struct of_phandle_args *dma_spec, st
static u32 stm32_dma3_check_rif(struct stm32_dma3_ddata *ddata)
{
+ struct device *dev = ddata2dev(ddata);
u32 chan_reserved, mask = 0, i, ccidcfgr, invalid_cid = 0;
/* Reserve Secure channels */
@@ -1676,7 +1683,7 @@ static u32 stm32_dma3_check_rif(struct stm32_dma3_ddata *ddata)
* In case CID filtering is not configured, dma-channel-mask property can be used to
* specify available DMA channels to the kernel.
*/
- of_property_read_u32(ddata->dma_dev.dev->of_node, "dma-channel-mask", &mask);
+ of_property_read_u32(dev->of_node, "dma-channel-mask", &mask);
/* Reserve !CID-filtered not in dma-channel-mask, static CID != CID1, CID1 not allowed */
for (i = 0; i < ddata->dma_channels; i++) {
@@ -1696,7 +1703,7 @@ static u32 stm32_dma3_check_rif(struct stm32_dma3_ddata *ddata)
ddata->chans[i].semaphore_mode = true;
}
}
- dev_dbg(ddata->dma_dev.dev, "chan%d: %s mode, %s\n", i,
+ dev_dbg(dev, "chan%d: %s mode, %s\n", i,
!(ccidcfgr & CCIDCFGR_CFEN) ? "!CID-filtered" :
ddata->chans[i].semaphore_mode ? "Semaphore" : "Static CID",
(chan_reserved & BIT(i)) ? "denied" :
@@ -1704,7 +1711,7 @@ static u32 stm32_dma3_check_rif(struct stm32_dma3_ddata *ddata)
}
if (invalid_cid)
- dev_warn(ddata->dma_dev.dev, "chan%*pbl have invalid CID configuration\n",
+ dev_warn(dev, "chan%*pbl have invalid CID configuration\n",
ddata->dma_channels, &invalid_cid);
return chan_reserved;
--
2.43.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
end of thread, other threads:[~2025-11-03 10:15 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-11-03 10:15 [PATCH 0/4] dmaengine: stm32-dma3: improvements and helper functions Amelie Delaunay
2025-11-03 10:15 ` [PATCH 1/4] dmaengine: stm32-dma3: use module_platform_driver Amelie Delaunay
2025-11-03 10:15 ` [PATCH 2/4] dmaengine: stm32-dma3: introduce channel semaphore helpers Amelie Delaunay
2025-11-03 10:15 ` [PATCH 3/4] dmaengine: stm32-dma3: restore channel semaphore status after suspend Amelie Delaunay
2025-11-03 10:15 ` [PATCH 4/4] dmaengine: stm32-dma3: introduce ddata2dev helper Amelie Delaunay
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).