diff --git a/drivers/remoteproc/omap_remoteproc.c b/drivers/remoteproc/omap_remoteproc.c index 5bccb5840a02..aac6158bc286 100644 --- a/drivers/remoteproc/omap_remoteproc.c +++ b/drivers/remoteproc/omap_remoteproc.c @@ -26,6 +26,9 @@ #include #include #include +#include + +#include #include "omap_remoteproc.h" #include "remoteproc_internal.h" @@ -57,6 +60,16 @@ struct omap_rproc_mem { size_t size; }; +/** + * struct omap_rproc_timer - data structure for a timer used by a omap rproc + * @odt: timer pointer + * @timer_ops: OMAP dmtimer ops for @odt timer + */ +struct omap_rproc_timer { + struct omap_dm_timer *odt; + const struct omap_dm_timer_ops *timer_ops; +}; + /** * struct omap_rproc - omap remote processor state * @mbox: mailbox channel handle @@ -64,6 +77,8 @@ struct omap_rproc_mem { * @boot_data: boot data structure for setting processor boot address * @mem: internal memory regions data * @num_mems: number of internal memory regions + * @num_timers: number of rproc timer(s) + * @timers: timer(s) info used by rproc * @rproc: rproc handle * @reset: reset handle */ @@ -73,6 +88,8 @@ struct omap_rproc { struct omap_rproc_boot_data *boot_data; struct omap_rproc_mem *mem; int num_mems; + int num_timers; + struct omap_rproc_timer *timers; struct rproc *rproc; struct reset_control *reset; }; @@ -97,6 +114,231 @@ struct omap_rproc_dev_data { const struct omap_rproc_mem_data *mems; }; +/** + * omap_rproc_request_timer() - request a timer for a remoteproc + * @dev: device requesting the timer + * @np: device node pointer to the desired timer + * @timer: handle to a struct omap_rproc_timer to return the timer handle + * + * This helper function is used primarily to request a timer associated with + * a remoteproc. The returned handle is stored in the .odt field of the + * @timer structure passed in, and is used to invoke other timer specific + * ops (like starting a timer either during device initialization or during + * a resume operation, or for stopping/freeing a timer). + * + * Return: 0 on success, otherwise an appropriate failure + */ +static int omap_rproc_request_timer(struct device *dev, struct device_node *np, + struct omap_rproc_timer *timer) +{ + int ret; + + timer->odt = timer->timer_ops->request_by_node(np); + if (!timer->odt) { + dev_err(dev, "request for timer node %p failed\n", np); + return -EBUSY; + } + + ret = timer->timer_ops->set_source(timer->odt, OMAP_TIMER_SRC_SYS_CLK); + if (ret) { + dev_err(dev, "error setting OMAP_TIMER_SRC_SYS_CLK as source for timer node %p\n", + np); + timer->timer_ops->free(timer->odt); + return ret; + } + + /* clean counter, remoteproc code will set the value */ + timer->timer_ops->set_load(timer->odt, 0, 0); + + return 0; +} + +/** + * omap_rproc_start_timer() - start a timer for a remoteproc + * @timer: handle to a OMAP rproc timer + * + * This helper function is used to start a timer associated with a remoteproc, + * obtained using the request_timer ops. The helper function needs to be + * invoked by the driver to start the timer (during device initialization) + * or to just resume the timer. + * + * Return: 0 on success, otherwise a failure as returned by DMTimer ops + */ +static inline int omap_rproc_start_timer(struct omap_rproc_timer *timer) +{ + return timer->timer_ops->start(timer->odt); +} + +/** + * omap_rproc_stop_timer() - stop a timer for a remoteproc + * @timer: handle to a OMAP rproc timer + * + * This helper function is used to disable a timer associated with a + * remoteproc, and needs to be called either during a device shutdown + * or suspend operation. The separate helper function allows the driver + * to just stop a timer without having to release the timer during a + * suspend operation. + * + * Return: 0 on success, otherwise a failure as returned by DMTimer ops + */ +static inline int omap_rproc_stop_timer(struct omap_rproc_timer *timer) +{ + return timer->timer_ops->stop(timer->odt); +} + +/** + * omap_rproc_release_timer() - release a timer for a remoteproc + * @timer: handle to a OMAP rproc timer + * + * This helper function is used primarily to release a timer associated + * with a remoteproc. The dmtimer will be available for other clients to + * use once released. + * + * Return: 0 on success, otherwise a failure as returned by DMTimer ops + */ +static inline int omap_rproc_release_timer(struct omap_rproc_timer *timer) +{ + return timer->timer_ops->free(timer->odt); +} + +/** + * omap_rproc_enable_timers() - enable the timers for a remoteproc + * @rproc: handle of a remote processor + * @configure: boolean flag used to acquire and configure the timer handle + * + * This function is used primarily to enable the timers associated with + * a remoteproc. The configure flag is provided to allow the driver to + * to either acquire and start a timer (during device initialization) or + * to just start a timer (during a resume operation). + * + * Return: 0 on success, otherwise an appropriate failure + */ +static int omap_rproc_enable_timers(struct rproc *rproc, bool configure) +{ + int i; + int ret = 0; + struct platform_device *tpdev; + struct dmtimer_platform_data *tpdata; + const struct omap_dm_timer_ops *timer_ops; + struct omap_rproc *oproc = rproc->priv; + struct omap_rproc_timer *timers = oproc->timers; + struct device *dev = rproc->dev.parent; + struct device_node *np = NULL; + + if (!oproc->num_timers) + return 0; + + if (!configure) + goto start_timers; + + for (i = 0; i < oproc->num_timers; i++) { + np = of_parse_phandle(dev->of_node, "ti,timers", i); + if (!np) { + ret = -ENXIO; + dev_err(dev, "device node lookup for timer at index %d failed: %d\n", + i, ret); + goto free_timers; + } + + tpdev = of_find_device_by_node(np); + if (!tpdev) { + ret = -ENODEV; + dev_err(dev, "could not get timer platform device\n"); + goto put_node; + } + + tpdata = dev_get_platdata(&tpdev->dev); + put_device(&tpdev->dev); + if (!tpdata) { + ret = -EINVAL; + dev_err(dev, "dmtimer pdata structure NULL\n"); + goto put_node; + } + + timer_ops = tpdata->timer_ops; + if (!timer_ops || !timer_ops->request_by_node || + !timer_ops->set_source || !timer_ops->set_load || + !timer_ops->free || !timer_ops->start || + !timer_ops->stop) { + ret = -EINVAL; + dev_err(dev, "device does not have required timer ops\n"); + goto put_node; + } + + timers[i].timer_ops = timer_ops; + ret = omap_rproc_request_timer(dev, np, &timers[i]); + if (ret) { + dev_err(dev, "request for timer %p failed: %d\n", np, + ret); + goto put_node; + } + of_node_put(np); + } + +start_timers: + for (i = 0; i < oproc->num_timers; i++) { + ret = omap_rproc_start_timer(&timers[i]); + if (ret) { + dev_err(dev, "start timer %p failed failed: %d\n", np, + ret); + break; + } + } + if (ret) { + while (i >= 0) { + omap_rproc_stop_timer(&timers[i]); + i--; + } + goto put_node; + } + return 0; + +put_node: + if (configure) + of_node_put(np); +free_timers: + while (i--) { + omap_rproc_release_timer(&timers[i]); + timers[i].odt = NULL; + timers[i].timer_ops = NULL; + } + + return ret; +} + +/** + * omap_rproc_disable_timers() - disable the timers for a remoteproc + * @rproc: handle of a remote processor + * @configure: boolean flag used to release the timer handle + * + * This function is used primarily to disable the timers associated with + * a remoteproc. The configure flag is provided to allow the driver to + * to either stop and release a timer (during device shutdown) or to just + * stop a timer (during a suspend operation). + * + * Return: 0 on success or no timers + */ +static int omap_rproc_disable_timers(struct rproc *rproc, bool configure) +{ + int i; + struct omap_rproc *oproc = rproc->priv; + struct omap_rproc_timer *timers = oproc->timers; + + if (!oproc->num_timers) + return 0; + + for (i = 0; i < oproc->num_timers; i++) { + omap_rproc_stop_timer(&timers[i]); + if (configure) { + omap_rproc_release_timer(&timers[i]); + timers[i].odt = NULL; + timers[i].timer_ops = NULL; + } + } + + return 0; +} + /** * omap_rproc_mbox_callback() - inbound mailbox message handler * @client: mailbox client pointer used for requesting the mailbox channel @@ -232,14 +474,22 @@ static int omap_rproc_start(struct rproc *rproc) goto put_mbox; } + ret = omap_rproc_enable_timers(rproc, true); + if (ret) { + dev_err(dev, "omap_rproc_enable_timers failed: %d\n", ret); + goto put_mbox; + } + ret = reset_control_deassert(oproc->reset); if (ret) { dev_err(dev, "reset control deassert failed: %d\n", ret); - goto put_mbox; + goto disable_timers; } return 0; +disable_timers: + omap_rproc_disable_timers(rproc, true); put_mbox: mbox_free_channel(oproc->mbox); return ret; @@ -255,6 +505,10 @@ static int omap_rproc_stop(struct rproc *rproc) if (ret) return ret; + ret = omap_rproc_disable_timers(rproc, true); + if (ret) + return ret; + mbox_free_channel(oproc->mbox); return 0; @@ -482,6 +736,37 @@ static int omap_rproc_of_get_internal_memories(struct platform_device *pdev, return 0; } +static int omap_rproc_of_get_timers(struct platform_device *pdev, + struct rproc *rproc) +{ + struct device_node *np = pdev->dev.of_node; + struct omap_rproc *oproc = rproc->priv; + struct device *dev = &pdev->dev; + + /* + * Timer nodes are directly used in client nodes as phandles, so + * retrieve the count using appropriate size + */ + oproc->num_timers = of_count_phandle_with_args(np, "ti,timers", NULL); + if (oproc->num_timers <= 0) { + dev_dbg(dev, "device does not have timers, status = %d\n", + oproc->num_timers); + oproc->num_timers = 0; + } + + if (oproc->num_timers) { + oproc->timers = devm_kcalloc(dev, oproc->num_timers, + sizeof(*oproc->timers), + GFP_KERNEL); + if (!oproc->timers) + return -ENOMEM; + + dev_dbg(dev, "device has %d tick timers\n", oproc->num_timers); + } + + return 0; +} + static int omap_rproc_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; @@ -529,6 +814,10 @@ static int omap_rproc_probe(struct platform_device *pdev) if (ret) goto free_rproc; + ret = omap_rproc_of_get_timers(pdev, rproc); + if (ret) + goto free_rproc; + ret = of_reserved_mem_device_init(&pdev->dev); if (ret) { dev_warn(&pdev->dev, "device does not have specific CMA pool.\n");