Message ID | 20180623210810.21232-6-david@lechnology.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
On 24/06/18 00:08, David Lechner wrote: > This adds a new remoteproc driver for TI Programmable Realtime Units > (PRUs). > > This has been tested working on AM1808 (LEGO MINDSTORMS EV3) using the > sample rpmsg client driver. > > Signed-off-by: David Lechner <david@lechnology.com> > --- > MAINTAINERS | 5 + > drivers/remoteproc/Kconfig | 7 + > drivers/remoteproc/Makefile | 1 + > drivers/remoteproc/ti_pru_rproc.c | 660 ++++++++++++++++++++++++++++++ > 4 files changed, 673 insertions(+) > create mode 100644 drivers/remoteproc/ti_pru_rproc.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index edf3cf5ea691..06dea089d9ae 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -14288,6 +14288,11 @@ L: netdev@vger.kernel.org > S: Maintained > F: drivers/net/ethernet/ti/netcp* > > +TI PRU REMOTEPROC DRIVER > +R: David Lechner <david@lechnology.com> > +F: Documentation/devicetree/bindings/remoteproc/ti_pru_rproc.txt > +F: drivers/remoteproc/ti_pru_rproc.c > + > TI TAS571X FAMILY ASoC CODEC DRIVER > M: Kevin Cernekee <cernekee@chromium.org> > L: alsa-devel@alsa-project.org (moderated for non-subscribers) > diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig > index cd1c168fd188..ae6e725e1755 100644 > --- a/drivers/remoteproc/Kconfig > +++ b/drivers/remoteproc/Kconfig > @@ -158,6 +158,13 @@ config ST_REMOTEPROC > config ST_SLIM_REMOTEPROC > tristate > > +config TI_PRU_REMOTEPROC > + tristate "TI Programmable Realtime Unit" > + depends on ARCH_DAVINCI_DA8XX || SOC_AM33XX > + help > + Say y here to support TI Programmable Runtime Units (PRUs) via the > + remote processor framework. > + > endif # REMOTEPROC > > endmenu > diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile > index 02627ede8d4a..451efee5c8d3 100644 > --- a/drivers/remoteproc/Makefile > +++ b/drivers/remoteproc/Makefile > @@ -23,3 +23,4 @@ qcom_wcnss_pil-y += qcom_wcnss.o > qcom_wcnss_pil-y += qcom_wcnss_iris.o > obj-$(CONFIG_ST_REMOTEPROC) += st_remoteproc.o > obj-$(CONFIG_ST_SLIM_REMOTEPROC) += st_slim_rproc.o > +obj-$(CONFIG_TI_PRU_REMOTEPROC) += ti_pru_rproc.o > diff --git a/drivers/remoteproc/ti_pru_rproc.c b/drivers/remoteproc/ti_pru_rproc.c > new file mode 100644 > index 000000000000..cd8302c318c9 > --- /dev/null > +++ b/drivers/remoteproc/ti_pru_rproc.c > @@ -0,0 +1,660 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Copyright (C) 2018 David Lechner <david@lechnology.com> > + * > + * Remoteproc driver for TI Programmable Realtime Unit > + */ > + > +#include <linux/bitops.h> > +#include <linux/interrupt.h> > +#include <linux/module.h> > +#include <linux/of_device.h> > +#include <linux/platform_data/ti-pruss.h> > +#include <linux/platform_device.h> > +#include <linux/pm_runtime.h> > +#include <linux/remoteproc.h> > +#include <linux/regmap.h> > +#include <linux/sizes.h> > +#include <linux/types.h> > + > +#include "remoteproc_internal.h" > + > +#define SZ_12K 0x3000 > + > +/* control/status registers */ > +#define TI_PRU_CS_CONTROL 0x0 > +#define TI_PRU_CS_STATUS 0x4 > +#define TI_PRU_CS_WAKEUP 0x8 > +#define TI_PRU_CS_CYCLECNT 0xc > + > +/* control register bits */ > +#define TI_PRU_CONTROL_PCRESETVAL GENMASK(31, 16) > +#define TI_PRU_CONTROL_RUNSTATE BIT(15) > +#define TI_PRU_CONTROL_SINGLESTEP BIT(8) > +#define TI_PRU_CONTROL_COUNTENABLE BIT(3) > +#define TI_PRU_CONTROL_SLEEPING BIT(2) > +#define TI_PRU_CONTROL_ENABLE BIT(1) > +#define TI_PRU_CONTROL_SOFTRESET BIT(0) > + > +/* status bits */ > +#define TI_PRU_STATUS_PCOUNTER GENMASK(15, 0) > + > +/* interrupt controller registers */ > +#define TI_PRU_INTC_GLBLEN 0x10 > +#define TI_PRU_INTC_STATIDXSET 0x20 > +#define TI_PRU_INTC_STATIDXCLR 0x24 > +#define TI_PRU_INTC_ENIDXSET 0x28 > +#define TI_PRU_INTC_HSTINTENIDXSET 0x34 > +#define TI_PRU_INTC_CHANMAP0 0x400 > +#define TI_PRU_INTC_POLARITY0 0xd00 > +#define TI_PRU_INTC_TYPE0 0xd80 > +#define TI_PRU_INTC_HOSTMAP0 0x800 > + > +/* config registers */ > +#define TI_PRU_CFG_SYSCFG 0x4 > + > +/* syscfg bits */ > +#define TI_PRU_SYSCFG_SUB_MWAIT BIT(5) > +#define TI_PRU_SYSCFG_STANDBY_INIT BIT(4) > +#define TI_PRU_SYSCFG_STANDBY_MODE_MASK GENMASK(3, 2) > +#define TI_PRU_SYSCFG_STANDBY_MODE_SMART (2 << 2) > +#define TI_PRU_SYSCFG_IDLE_MODE_MASK GENMASK(1, 0) > +#define TI_PRU_SYSCFG_IDLE_MODE_SMART (2 << 0) > + > +enum ti_pru { > + TI_PRU0, > + TI_PRU1, > + NUM_TI_PRU > +}; > + > +enum ti_pru_type { > + TI_PRU_TYPE_AM18XX, > + TI_PRU_TYPE_AM335X, > + NUM_TI_PRU_TYPE > +}; > + > +enum ti_pru_evtout { > + TI_PRU_EVTOUT0, > + TI_PRU_EVTOUT1, > + TI_PRU_EVTOUT2, > + TI_PRU_EVTOUT3, > + TI_PRU_EVTOUT4, > + TI_PRU_EVTOUT5, > + TI_PRU_EVTOUT6, > + TI_PRU_EVTOUT7, > + NUM_TI_PRU_EVTOUT > +}; > + > +struct ti_pru_mem_region { > + off_t offset; > + size_t size; > +}; > + > +/** > + * ti_pru_shared_info - common init info for the PRUSS > + * @ram: shared RAM, if present > + * @intc: interrupt controller > + * @cfg: configuration registers, if present > + */ > +struct ti_pru_shared_info { > + struct ti_pru_mem_region ram; > + struct ti_pru_mem_region intc; > + struct ti_pru_mem_region cfg; > +}; > + > +/** > + * ti_pru_info - init info each individual PRU > + * @ram: PRU RAM > + * @ctrl: PRU control/status registers > + * @dbg: PRU dbg registers > + * @inst: instruction RAM > + * @vq_arm_to_pru_event: The index of the PRU system event interrupt used > + * used by the ARM for kicking the PRU > + * @vq_pru_to_arm_event: The index of the PRU system event interrupt used > + * used by the PRU for kicking the ARM > + */ > +struct ti_pru_info { > + struct ti_pru_mem_region ram; > + struct ti_pru_mem_region ctrl; > + struct ti_pru_mem_region dbg; > + struct ti_pru_mem_region inst; > + int vq_arm_to_pru_event; > + int vq_pru_to_arm_event; > +}; > + > +struct ti_pru_device_info { > + struct ti_pru_shared_info shared; > + struct ti_pru_info pru[NUM_TI_PRU]; > +}; > + > +static const struct ti_pru_device_info ti_pru_devices[NUM_TI_PRU_TYPE] = { > + [TI_PRU_TYPE_AM18XX] = { > + .shared = { > + .intc = { .offset = 0x4000, .size = SZ_12K, }, > + }, > + .pru[TI_PRU0] = { > + .ram = { .offset = 0x0000, .size = SZ_512, }, > + .ctrl = { .offset = 0x7000, .size = SZ_1K, }, > + .dbg = { .offset = 0x7400, .size = SZ_1K, }, > + .inst = { .offset = 0x8000, .size = SZ_4K, }, > + .vq_arm_to_pru_event = 32, > + .vq_pru_to_arm_event = 33, > + }, > + .pru[TI_PRU1] = { > + .ram = { .offset = 0x2000, .size = SZ_512, }, > + .ctrl = { .offset = 0x7800, .size = SZ_1K, }, > + .dbg = { .offset = 0x7c00, .size = SZ_1K, }, > + .inst = { .offset = 0xc000, .size = SZ_4K, }, > + .vq_arm_to_pru_event = 34, > + .vq_pru_to_arm_event = 35, > + }, > + }, > + [TI_PRU_TYPE_AM335X] = { > + .shared = { > + .ram = { .offset = 0x10000, .size = SZ_12K, }, > + .intc = { .offset = 0x20000, .size = SZ_8K, }, > + .cfg = { .offset = 0x26000, .size = SZ_8K, }, > + }, > + .pru[TI_PRU0] = { > + .ram = { .offset = 0x00000, .size = SZ_8K, }, > + .ctrl = { .offset = 0x22000, .size = SZ_1K, }, > + .dbg = { .offset = 0x22400, .size = SZ_1K, }, > + .inst = { .offset = 0x34000, .size = SZ_8K, }, > + .vq_arm_to_pru_event = 16, > + .vq_pru_to_arm_event = 17, > + }, > + .pru[TI_PRU1] = { > + .ram = { .offset = 0x02000, .size = SZ_8K, }, > + .ctrl = { .offset = 0x24000, .size = SZ_1K, }, > + .dbg = { .offset = 0x24400, .size = SZ_1K, }, > + .inst = { .offset = 0x38000, .size = SZ_8K, }, > + .vq_arm_to_pru_event = 18, > + .vq_pru_to_arm_event = 19, > + }, > + }, All this information should really come from the DT. > +}; > + > +/** > + * ti_pru_shared_data - private platform driver data > + * @info: init info common to both PRU cores > + * @dev: the platform device > + * @base: the mapped memory region of the PRUSS > + * @intc: regmap of the interrupt controller > + * @cfg: regmap of configuration registers > + * @pru: per-PRU core data > + */ > +struct ti_pru_shared_data { > + const struct ti_pru_shared_info *info; > + struct device *dev; > + void __iomem *base; > + struct regmap *intc; > + struct regmap *cfg; > + struct rproc *pru[NUM_TI_PRU]; > +}; > + > +/** > + * ti_pru_data - private data for each PRU core > + * @info: static init info > + * @shared: pointer to the shared data struct > + * @ctrl: regmap of the PRU control/status register > + * @vq_irq: interrupt used for rpmsg > + */ > +struct ti_pru_data { > + const struct ti_pru_info *info; > + struct ti_pru_shared_data *shared; > + struct regmap *ctrl; > + int vq_irq; > +}; > + > +static int ti_pru_rproc_start(struct rproc *rproc) > +{ > + struct ti_pru_data *pru = rproc->priv; > + u32 val; > + > + val = (rproc->bootaddr >> 2) << (ffs(TI_PRU_CONTROL_PCRESETVAL) - 1); > + val |= TI_PRU_CONTROL_ENABLE; > + > + return regmap_write(pru->ctrl, TI_PRU_CS_CONTROL, val); > +} > + > +static int ti_pru_rproc_stop(struct rproc *rproc) > +{ > + struct ti_pru_data *pru = rproc->priv; > + u32 mask; > + > + mask = TI_PRU_CONTROL_ENABLE; > + > + return regmap_write_bits(pru->ctrl, TI_PRU_CS_CONTROL, mask, 0); > +} > + > +static void ti_pru_rproc_kick(struct rproc *rproc, int vqid) > +{ > + struct ti_pru_data *pru = rproc->priv; > + struct ti_pru_shared_data *shared = pru->shared; > + u32 val; > + > + val = pru->info->vq_arm_to_pru_event; > + > + regmap_write(shared->intc, TI_PRU_INTC_STATIDXSET, val); > +} > + > +static void *ti_pru_rproc_da_to_va(struct rproc *rproc, u64 da, int len, int map) > +{ > + struct ti_pru_data *pru = rproc->priv; > + struct ti_pru_shared_data *shared = pru->shared; > + > + if (map == 0) { > + if (da + len > pru->info->inst.size) > + return ERR_PTR(-EINVAL); > + > + return shared->base + pru->info->inst.offset + da; > + } > + > + if (map == 1) { > + if (da + len > pru->info->ram.size) > + return ERR_PTR(-EINVAL); > + > + return shared->base + pru->info->ram.offset + da; > + } > + > + return ERR_PTR(-EINVAL); > +} > + > +static const struct rproc_ops ti_pru_rproc_ops = { > + .start = ti_pru_rproc_start, > + .stop = ti_pru_rproc_stop, > + .kick = ti_pru_rproc_kick, > + .da_to_va = ti_pru_rproc_da_to_va, > +}; > + > +static struct regmap_config ti_pru_ctrl_regmap_config = { > + .reg_bits = 32, > + .val_bits = 32, > + .reg_stride = 4, > + .max_register = 0x2c, > +}; > + > +static struct regmap_config ti_pru_intc_regmap_config = { > + .reg_bits = 32, > + .val_bits = 32, > + .reg_stride = 4, > + .max_register = 0x1500, > +}; > + > +static struct regmap_config ti_pru_cfg_regmap_config = { > + .reg_bits = 32, > + .val_bits = 32, > + .reg_stride = 4, > + .max_register = 0x40, > +}; > + > +static const struct of_device_id ti_pru_rproc_of_match[] = { > + { > + .compatible = "ti,da850-pru-rproc", > + .data = &ti_pru_devices[TI_PRU_TYPE_AM18XX] > + }, > + { > + .compatible = "ti,am3352-pru-rproc", > + .data = &ti_pru_devices[TI_PRU_TYPE_AM335X] > + }, > + { }, > +}; > +MODULE_DEVICE_TABLE(of, ti_pru_rproc_of_match); > + > +static irqreturn_t ti_pru_handle_vq_irq(int irq, void *p) > +{ > + struct rproc *rproc = p; > + struct ti_pru_data *pru = rproc->priv; > + > + regmap_write(pru->shared->intc, TI_PRU_INTC_STATIDXCLR, > + pru->info->vq_pru_to_arm_event); > + > + return IRQ_WAKE_THREAD; > +} > + > +static irqreturn_t ti_pru_vq_irq_thread(int irq, void *p) > +{ > + struct rproc *rproc = p; > + > + rproc_vq_interrupt(rproc, 0); > + rproc_vq_interrupt(rproc, 1); > + > + return IRQ_HANDLED; > +} > + > +static void ti_pru_free_rproc(void *data) > +{ > + struct rproc *rproc = data; > + > + rproc_free(rproc); > +} > + > +static struct rproc *ti_pru_init_one_rproc(struct ti_pru_shared_data *shared, > + const struct ti_pru_info *info, > + enum ti_pru id) > +{ > + struct device *dev = shared->dev; > + struct platform_device *pdev = to_platform_device(dev); > + const char *name; > + char irq_name[16]; > + struct rproc *rproc; > + struct ti_pru_data *pru; > + int err; > + > + name = devm_kasprintf(dev, GFP_KERNEL, "pru%u", id); > + if (!name) > + return ERR_PTR(-ENOMEM); > + > + rproc = rproc_alloc(dev, name, &ti_pru_rproc_ops, NULL, sizeof(*pru)); > + if (!rproc) > + return ERR_PTR(-ENOMEM); > + > + devm_add_action(dev, ti_pru_free_rproc, rproc); > + > + /* don't auto-boot for now - bad firmware can lock up the system */ > + rproc->auto_boot = false; > + > + pru = rproc->priv; > + pru->info = info; > + pru->shared = shared; > + > + snprintf(irq_name, 16, "%s-vq", name); > + > + pru->vq_irq = platform_get_irq_byname(pdev, irq_name); > + if (pru->vq_irq < 0) { > + dev_err(&rproc->dev, "failed to get vq IRQ\n"); > + return ERR_PTR(pru->vq_irq); > + } > + > + err = devm_request_threaded_irq(&rproc->dev, pru->vq_irq, > + ti_pru_handle_vq_irq, > + ti_pru_vq_irq_thread, 0, name, rproc); > + if (err < 0) { > + dev_err(&rproc->dev, "failed to request vq IRQ\n"); > + return ERR_PTR(err); > + } > + > + pru->ctrl = devm_regmap_init_mmio(&rproc->dev, > + shared->base + info->ctrl.offset, > + &ti_pru_ctrl_regmap_config); > + if (IS_ERR(pru->ctrl)) { > + dev_err(&rproc->dev, "failed to init ctrl regmap\n"); > + return ERR_CAST(pru->ctrl); > + } > + > + return rproc; > +} > + > +/** > + * ti_pru_init_intc_polarity - configure polarity interrupt event > + * @intc: the interrtup controller regmap > + * @event: the source event > + */ > +static void ti_pru_init_intc_polarity(struct regmap *intc, int event) > +{ > + int offset, shift, mask; > + > + /* 32 events per register */ > + offset = event / 32 * 4; > + shift = event % 32; > + mask = 1 << shift; > + > + /* polarity is always high (1) */ > + regmap_write_bits(intc, TI_PRU_INTC_POLARITY0 + offset, mask, ~0); > +} > + > +/** > + * ti_pru_init_intc_type - configure type of interrupt event > + * @intc: the interrtup controller regmap > + * @event: the source event > + */ > +static void ti_pru_init_intc_type(struct regmap *intc, int event) > +{ > + int offset, shift, mask; > + > + /* 32 events per register */ > + offset = event / 32 * 4; > + shift = event % 32; > + mask = 1 << shift; > + > + /* type is always pulse (0) */ > + regmap_write_bits(intc, TI_PRU_INTC_TYPE0 + offset, mask, 0); > +} > + > +/** > + * ti_pru_init_intc_channel_map - configure interrupt event to channel mapping > + * @intc: the interrtup controller regmap > + * @event: the source event > + * @ch: the channel to be assigned to the event > + */ > +static void ti_pru_init_intc_channel_map(struct regmap *intc, int event, int ch) > +{ > + int offset, shift, mask, val; > + > + /* 4 channels per 32-bit register */ > + offset = event / 4 * 4; > + shift = event % 4 * 8; > + mask = 0xff << shift; > + val = ch << shift; > + > + regmap_write_bits(intc, TI_PRU_INTC_CHANMAP0 + offset, mask, val); > +} > + > +/** > + * ti_pru_init_intc_host_map - configure interrupt channel to host mapping > + * @intc: the interrtup controller regmap > + * @ch: the source channel > + * @host: the host interrupt to be assigned to the channel > + */ > +static void ti_pru_init_intc_host_map(struct regmap *intc, int ch, int host) > +{ > + int offset, shift, mask, val; > + > + /* 4 hosts per 32-bit register */ > + offset = ch / 4 * 4; > + shift = ch % 4 * 8; > + mask = 0xff << shift; > + val = host << shift; > + > + regmap_write_bits(intc, TI_PRU_INTC_HOSTMAP0 + offset, mask, val); > +} > + > +static void ti_pru_init_intc(struct regmap *intc, > + const struct ti_pru_device_info *info) > +{ > + int arm_to_pru0 = info->pru[TI_PRU0].vq_arm_to_pru_event; > + int arm_to_pru1 = info->pru[TI_PRU1].vq_arm_to_pru_event; > + int pru0_to_arm = info->pru[TI_PRU0].vq_pru_to_arm_event; > + int pru1_to_arm = info->pru[TI_PRU1].vq_pru_to_arm_event; > + > + /* set polarity of system events */ > + ti_pru_init_intc_polarity(intc, arm_to_pru0); > + ti_pru_init_intc_polarity(intc, arm_to_pru1); > + ti_pru_init_intc_polarity(intc, pru0_to_arm); > + ti_pru_init_intc_polarity(intc, pru1_to_arm); > + > + /* set type of system events */ > + ti_pru_init_intc_type(intc, arm_to_pru0); > + ti_pru_init_intc_type(intc, arm_to_pru1); > + ti_pru_init_intc_type(intc, pru0_to_arm); > + ti_pru_init_intc_type(intc, pru1_to_arm); > + > + /* map system events to channels */ > + ti_pru_init_intc_channel_map(intc, arm_to_pru0, 0); > + ti_pru_init_intc_channel_map(intc, arm_to_pru1, 1); > + ti_pru_init_intc_channel_map(intc, pru0_to_arm, 2); > + ti_pru_init_intc_channel_map(intc, pru1_to_arm, 3); > + > + /* map channels to host interrupts */ > + ti_pru_init_intc_host_map(intc, 0, 0); /* ARM to PRU0 */ > + ti_pru_init_intc_host_map(intc, 1, 1); /* ARM to PRU1 */ > + ti_pru_init_intc_host_map(intc, 2, 2); /* PRU0 to ARM */ > + ti_pru_init_intc_host_map(intc, 3, 3); /* PRU1 to ARM */ > + > + /* clear system interrupts */ > + regmap_write(intc, TI_PRU_INTC_STATIDXCLR, arm_to_pru0); > + regmap_write(intc, TI_PRU_INTC_STATIDXCLR, arm_to_pru1); > + regmap_write(intc, TI_PRU_INTC_STATIDXCLR, pru0_to_arm); > + regmap_write(intc, TI_PRU_INTC_STATIDXCLR, pru1_to_arm); > + > + /* enable host interrupts for kicking */ > + regmap_write(intc, TI_PRU_INTC_HSTINTENIDXSET, 0); /* ARM to PRU0 */ > + regmap_write(intc, TI_PRU_INTC_HSTINTENIDXSET, 1); /* ARM to PRU1 */ > + regmap_write(intc, TI_PRU_INTC_HSTINTENIDXSET, 2); /* PRU0 to ARM */ > + regmap_write(intc, TI_PRU_INTC_HSTINTENIDXSET, 3); /* PRU1 to ARM */ > + > + /* enable system events for kicking */ > + regmap_write(intc, TI_PRU_INTC_ENIDXSET, arm_to_pru0); > + regmap_write(intc, TI_PRU_INTC_ENIDXSET, arm_to_pru1); > + regmap_write(intc, TI_PRU_INTC_ENIDXSET, pru0_to_arm); > + regmap_write(intc, TI_PRU_INTC_ENIDXSET, pru1_to_arm); > + > + /* enable all interrupts */ > + regmap_write_bits(intc, TI_PRU_INTC_GLBLEN, 1, 1); > +} We already have a working irq_chip implementation for INTC. https://git.ti.com/ti-linux-kernel/ti-linux-kernel/blobs/ti-linux-4.14.y/drivers/remoteproc/pruss_intc.c I think we can leverage directly from that. This way pru_rproc or client device nodes can easily specify a pruss_intc interrupt parent and the SYSEVENT number as the irq. Then device drivers can simply use request_irq(). example usage here https://git.ti.com/ti-linux-kernel/ti-linux-kernel/blobs/ti-linux-4.14.y/arch/arm/boot/dts/am33xx.dtsi#line986 https://git.ti.com/ti-linux-kernel/ti-linux-kernel/blobs/ti-linux-4.14.y/drivers/remoteproc/pru_rproc.c#line670 > + > +static int ti_pru_rproc_probe(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + struct ti_pruss_platform_data *pdata = dev_get_platdata(dev); > + const struct of_device_id *of_id; > + const struct ti_pru_device_info *info; > + struct ti_pru_shared_data *shared; > + struct resource *res; > + int err; > + > + of_id = of_match_device(ti_pru_rproc_of_match, dev); > + if (!of_id || !of_id->data) > + return -EINVAL; > + > + info = of_id->data; > + > + shared = devm_kzalloc(dev, sizeof(*shared), GFP_KERNEL); > + > + platform_set_drvdata(pdev, shared); > + > + shared->info = &info->shared; > + shared->dev = dev; > + > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); > + shared->base = devm_ioremap_resource(dev, res); > + if (IS_ERR(shared->base)) { > + dev_err(dev, "failed to ioremap resource\n"); > + return PTR_ERR(shared->base); > + } > + > + shared->intc = devm_regmap_init_mmio(dev, > + shared->base + shared->info->intc.offset, > + &ti_pru_intc_regmap_config); > + if (IS_ERR(shared->intc)) { > + dev_err(dev, "failed to init intc regmap\n"); > + return PTR_ERR(shared->intc); > + } > + > + if (shared->info->cfg.size) { > + shared->cfg = devm_regmap_init_mmio(dev, > + shared->base + shared->info->cfg.offset, > + &ti_pru_cfg_regmap_config); > + if (IS_ERR(shared->cfg)) { > + dev_err(dev, "failed to init cfg regmap\n"); > + return PTR_ERR(shared->cfg); > + } > + } > + > + shared->pru[TI_PRU0] = ti_pru_init_one_rproc(shared, &info->pru[TI_PRU0], > + TI_PRU0); > + if (IS_ERR(shared->pru[TI_PRU0])) > + return PTR_ERR(shared->pru[TI_PRU0]); > + > + shared->pru[TI_PRU1] = ti_pru_init_one_rproc(shared, &info->pru[TI_PRU1], > + TI_PRU1); > + if (IS_ERR(shared->pru[TI_PRU1])) > + return PTR_ERR(shared->pru[TI_PRU1]); > + > + pm_runtime_enable(dev); > + > + err = pm_runtime_get_sync(dev); > + if (err < 0) > + goto err_pm_runtime_disable; > + > + if (pdata) { > + err = pdata->deassert_reset(pdev, pdata->reset_name); > + if (err < 0) { > + dev_err(dev, "Failed to reset pruss\n"); > + goto err_pm_runtime_put; > + } > + } > + > + if (shared->cfg) { > + int mask, val; > + > + mask = TI_PRU_SYSCFG_IDLE_MODE_MASK | TI_PRU_SYSCFG_STANDBY_MODE_MASK; > + val = TI_PRU_SYSCFG_IDLE_MODE_SMART | TI_PRU_SYSCFG_STANDBY_MODE_SMART; > + regmap_write_bits(shared->cfg, TI_PRU_CFG_SYSCFG, mask, val); > + > + mask = TI_PRU_SYSCFG_STANDBY_INIT; > + val = 0; > + regmap_write_bits(shared->cfg, TI_PRU_CFG_SYSCFG, mask, val); > + > + err = regmap_read_poll_timeout(shared->cfg, TI_PRU_CFG_SYSCFG, > + val, !(val & TI_PRU_SYSCFG_SUB_MWAIT), 5, 50); > + if (err < 0) { > + dev_err(dev, "timeout while enabling pruss\n"); > + goto err_pm_runtime_put; > + } > + } > + > + ti_pru_init_intc(shared->intc, info); This is using a static INTC map right? This limits our possibility to use application based INTC mapping. There needs to be a way to specify the INTC mapping in the DT and/or resource table. > + > + err = rproc_add(shared->pru[TI_PRU0]); > + if (err < 0) > + goto err_assert_reset; > + > + err = rproc_add(shared->pru[TI_PRU1]); > + if (err < 0) > + goto err_del_pru0; > + > + return 0; > + > +err_del_pru0: > + rproc_del(shared->pru[TI_PRU0]); > +err_assert_reset: > + if (pdata) > + pdata->assert_reset(pdev, pdata->reset_name); > +err_pm_runtime_put: > + pm_runtime_put(dev); > +err_pm_runtime_disable: > + pm_runtime_disable(dev); > + > + return err; > +} > + > +static int ti_pru_rproc_remove(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + struct ti_pruss_platform_data *pdata = dev_get_platdata(dev); > + struct ti_pru_shared_data *shared = platform_get_drvdata(pdev); > + > + rproc_del(shared->pru[TI_PRU1]); > + rproc_del(shared->pru[TI_PRU0]); > + if (pdata) > + pdata->assert_reset(pdev, pdata->reset_name); > + pm_runtime_put(dev); > + pm_runtime_disable(dev); > + > + return 0; > +} > + > +static struct platform_driver ti_pru_rproc_driver = { > + .probe = ti_pru_rproc_probe, > + .remove = ti_pru_rproc_remove, > + .driver = { > + .name = "ti-pru-rproc", > + .of_match_table = ti_pru_rproc_of_match, > + }, > +}; > +module_platform_driver(ti_pru_rproc_driver); > + > +MODULE_AUTHOR("David Lechner <david@lechnology.com>"); > +MODULE_LICENSE("GPL v2"); > +MODULE_DESCRIPTION("Remoteproc driver for TI PRU"); >
Derald, On 30/06/18 22:02, Derald Woods wrote: > > > On Fri, Jun 29, 2018 at 5:14 AM, Roger Quadros <rogerq@ti.com <mailto:rogerq@ti.com>> wrote: > > > > On 24/06/18 00:08, David Lechner wrote: > > This adds a new remoteproc driver for TI Programmable Realtime Units > > (PRUs). > > > > This has been tested working on AM1808 (LEGO MINDSTORMS EV3) using the > > sample rpmsg client driver. > > > > Signed-off-by: David Lechner <david@lechnology.com <mailto:david@lechnology.com>> > > --- > > MAINTAINERS | 5 + > > drivers/remoteproc/Kconfig | 7 + > > drivers/remoteproc/Makefile | 1 + > > drivers/remoteproc/ti_pru_rproc.c | 660 ++++++++++++++++++++++++++++++ > > 4 files changed, 673 insertions(+) > > create mode 100644 drivers/remoteproc/ti_pru_rproc.c <snip> > > We already have a working irq_chip implementation for INTC. > https://git.ti.com/ti-linux-kernel/ti-linux-kernel/blobs/ti-linux-4.14.y/drivers/remoteproc/pruss_intc.c <https://git.ti.com/ti-linux-kernel/ti-linux-kernel/blobs/ti-linux-4.14.y/drivers/remoteproc/pruss_intc.c> > > I think we can leverage directly from that. > > This way pru_rproc or client device nodes can easily specify a pruss_intc interrupt parent and the > SYSEVENT number as the irq. Then device drivers can simply use request_irq(). > > example usage here > https://git.ti.com/ti-linux-kernel/ti-linux-kernel/blobs/ti-linux-4.14.y/arch/arm/boot/dts/am33xx.dtsi#line986 <https://git.ti.com/ti-linux-kernel/ti-linux-kernel/blobs/ti-linux-4.14.y/arch/arm/boot/dts/am33xx.dtsi#line986> > https://git.ti.com/ti-linux-kernel/ti-linux-kernel/blobs/ti-linux-4.14.y/drivers/remoteproc/pru_rproc.c#line670 <https://git.ti.com/ti-linux-kernel/ti-linux-kernel/blobs/ti-linux-4.14.y/drivers/remoteproc/pru_rproc.c#line670> > > > > > Is this PRU code on a path to be added to the mainline kernel? There is an increase in the number of available systems which would benefit from consistent PRU interfaces. If code is not mainlined, or on a mainline path, some may think it is not usable or ready for production. Is this a permanent "out-of-tree" and/or "TI-tree" development. Just wondering. > Yes, we constantly upstream our work. We are currently working to get the PRU support upstream.
diff --git a/MAINTAINERS b/MAINTAINERS index edf3cf5ea691..06dea089d9ae 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14288,6 +14288,11 @@ L: netdev@vger.kernel.org S: Maintained F: drivers/net/ethernet/ti/netcp* +TI PRU REMOTEPROC DRIVER +R: David Lechner <david@lechnology.com> +F: Documentation/devicetree/bindings/remoteproc/ti_pru_rproc.txt +F: drivers/remoteproc/ti_pru_rproc.c + TI TAS571X FAMILY ASoC CODEC DRIVER M: Kevin Cernekee <cernekee@chromium.org> L: alsa-devel@alsa-project.org (moderated for non-subscribers) diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig index cd1c168fd188..ae6e725e1755 100644 --- a/drivers/remoteproc/Kconfig +++ b/drivers/remoteproc/Kconfig @@ -158,6 +158,13 @@ config ST_REMOTEPROC config ST_SLIM_REMOTEPROC tristate +config TI_PRU_REMOTEPROC + tristate "TI Programmable Realtime Unit" + depends on ARCH_DAVINCI_DA8XX || SOC_AM33XX + help + Say y here to support TI Programmable Runtime Units (PRUs) via the + remote processor framework. + endif # REMOTEPROC endmenu diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile index 02627ede8d4a..451efee5c8d3 100644 --- a/drivers/remoteproc/Makefile +++ b/drivers/remoteproc/Makefile @@ -23,3 +23,4 @@ qcom_wcnss_pil-y += qcom_wcnss.o qcom_wcnss_pil-y += qcom_wcnss_iris.o obj-$(CONFIG_ST_REMOTEPROC) += st_remoteproc.o obj-$(CONFIG_ST_SLIM_REMOTEPROC) += st_slim_rproc.o +obj-$(CONFIG_TI_PRU_REMOTEPROC) += ti_pru_rproc.o diff --git a/drivers/remoteproc/ti_pru_rproc.c b/drivers/remoteproc/ti_pru_rproc.c new file mode 100644 index 000000000000..cd8302c318c9 --- /dev/null +++ b/drivers/remoteproc/ti_pru_rproc.c @@ -0,0 +1,660 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018 David Lechner <david@lechnology.com> + * + * Remoteproc driver for TI Programmable Realtime Unit + */ + +#include <linux/bitops.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/platform_data/ti-pruss.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/remoteproc.h> +#include <linux/regmap.h> +#include <linux/sizes.h> +#include <linux/types.h> + +#include "remoteproc_internal.h" + +#define SZ_12K 0x3000 + +/* control/status registers */ +#define TI_PRU_CS_CONTROL 0x0 +#define TI_PRU_CS_STATUS 0x4 +#define TI_PRU_CS_WAKEUP 0x8 +#define TI_PRU_CS_CYCLECNT 0xc + +/* control register bits */ +#define TI_PRU_CONTROL_PCRESETVAL GENMASK(31, 16) +#define TI_PRU_CONTROL_RUNSTATE BIT(15) +#define TI_PRU_CONTROL_SINGLESTEP BIT(8) +#define TI_PRU_CONTROL_COUNTENABLE BIT(3) +#define TI_PRU_CONTROL_SLEEPING BIT(2) +#define TI_PRU_CONTROL_ENABLE BIT(1) +#define TI_PRU_CONTROL_SOFTRESET BIT(0) + +/* status bits */ +#define TI_PRU_STATUS_PCOUNTER GENMASK(15, 0) + +/* interrupt controller registers */ +#define TI_PRU_INTC_GLBLEN 0x10 +#define TI_PRU_INTC_STATIDXSET 0x20 +#define TI_PRU_INTC_STATIDXCLR 0x24 +#define TI_PRU_INTC_ENIDXSET 0x28 +#define TI_PRU_INTC_HSTINTENIDXSET 0x34 +#define TI_PRU_INTC_CHANMAP0 0x400 +#define TI_PRU_INTC_POLARITY0 0xd00 +#define TI_PRU_INTC_TYPE0 0xd80 +#define TI_PRU_INTC_HOSTMAP0 0x800 + +/* config registers */ +#define TI_PRU_CFG_SYSCFG 0x4 + +/* syscfg bits */ +#define TI_PRU_SYSCFG_SUB_MWAIT BIT(5) +#define TI_PRU_SYSCFG_STANDBY_INIT BIT(4) +#define TI_PRU_SYSCFG_STANDBY_MODE_MASK GENMASK(3, 2) +#define TI_PRU_SYSCFG_STANDBY_MODE_SMART (2 << 2) +#define TI_PRU_SYSCFG_IDLE_MODE_MASK GENMASK(1, 0) +#define TI_PRU_SYSCFG_IDLE_MODE_SMART (2 << 0) + +enum ti_pru { + TI_PRU0, + TI_PRU1, + NUM_TI_PRU +}; + +enum ti_pru_type { + TI_PRU_TYPE_AM18XX, + TI_PRU_TYPE_AM335X, + NUM_TI_PRU_TYPE +}; + +enum ti_pru_evtout { + TI_PRU_EVTOUT0, + TI_PRU_EVTOUT1, + TI_PRU_EVTOUT2, + TI_PRU_EVTOUT3, + TI_PRU_EVTOUT4, + TI_PRU_EVTOUT5, + TI_PRU_EVTOUT6, + TI_PRU_EVTOUT7, + NUM_TI_PRU_EVTOUT +}; + +struct ti_pru_mem_region { + off_t offset; + size_t size; +}; + +/** + * ti_pru_shared_info - common init info for the PRUSS + * @ram: shared RAM, if present + * @intc: interrupt controller + * @cfg: configuration registers, if present + */ +struct ti_pru_shared_info { + struct ti_pru_mem_region ram; + struct ti_pru_mem_region intc; + struct ti_pru_mem_region cfg; +}; + +/** + * ti_pru_info - init info each individual PRU + * @ram: PRU RAM + * @ctrl: PRU control/status registers + * @dbg: PRU dbg registers + * @inst: instruction RAM + * @vq_arm_to_pru_event: The index of the PRU system event interrupt used + * used by the ARM for kicking the PRU + * @vq_pru_to_arm_event: The index of the PRU system event interrupt used + * used by the PRU for kicking the ARM + */ +struct ti_pru_info { + struct ti_pru_mem_region ram; + struct ti_pru_mem_region ctrl; + struct ti_pru_mem_region dbg; + struct ti_pru_mem_region inst; + int vq_arm_to_pru_event; + int vq_pru_to_arm_event; +}; + +struct ti_pru_device_info { + struct ti_pru_shared_info shared; + struct ti_pru_info pru[NUM_TI_PRU]; +}; + +static const struct ti_pru_device_info ti_pru_devices[NUM_TI_PRU_TYPE] = { + [TI_PRU_TYPE_AM18XX] = { + .shared = { + .intc = { .offset = 0x4000, .size = SZ_12K, }, + }, + .pru[TI_PRU0] = { + .ram = { .offset = 0x0000, .size = SZ_512, }, + .ctrl = { .offset = 0x7000, .size = SZ_1K, }, + .dbg = { .offset = 0x7400, .size = SZ_1K, }, + .inst = { .offset = 0x8000, .size = SZ_4K, }, + .vq_arm_to_pru_event = 32, + .vq_pru_to_arm_event = 33, + }, + .pru[TI_PRU1] = { + .ram = { .offset = 0x2000, .size = SZ_512, }, + .ctrl = { .offset = 0x7800, .size = SZ_1K, }, + .dbg = { .offset = 0x7c00, .size = SZ_1K, }, + .inst = { .offset = 0xc000, .size = SZ_4K, }, + .vq_arm_to_pru_event = 34, + .vq_pru_to_arm_event = 35, + }, + }, + [TI_PRU_TYPE_AM335X] = { + .shared = { + .ram = { .offset = 0x10000, .size = SZ_12K, }, + .intc = { .offset = 0x20000, .size = SZ_8K, }, + .cfg = { .offset = 0x26000, .size = SZ_8K, }, + }, + .pru[TI_PRU0] = { + .ram = { .offset = 0x00000, .size = SZ_8K, }, + .ctrl = { .offset = 0x22000, .size = SZ_1K, }, + .dbg = { .offset = 0x22400, .size = SZ_1K, }, + .inst = { .offset = 0x34000, .size = SZ_8K, }, + .vq_arm_to_pru_event = 16, + .vq_pru_to_arm_event = 17, + }, + .pru[TI_PRU1] = { + .ram = { .offset = 0x02000, .size = SZ_8K, }, + .ctrl = { .offset = 0x24000, .size = SZ_1K, }, + .dbg = { .offset = 0x24400, .size = SZ_1K, }, + .inst = { .offset = 0x38000, .size = SZ_8K, }, + .vq_arm_to_pru_event = 18, + .vq_pru_to_arm_event = 19, + }, + }, +}; + +/** + * ti_pru_shared_data - private platform driver data + * @info: init info common to both PRU cores + * @dev: the platform device + * @base: the mapped memory region of the PRUSS + * @intc: regmap of the interrupt controller + * @cfg: regmap of configuration registers + * @pru: per-PRU core data + */ +struct ti_pru_shared_data { + const struct ti_pru_shared_info *info; + struct device *dev; + void __iomem *base; + struct regmap *intc; + struct regmap *cfg; + struct rproc *pru[NUM_TI_PRU]; +}; + +/** + * ti_pru_data - private data for each PRU core + * @info: static init info + * @shared: pointer to the shared data struct + * @ctrl: regmap of the PRU control/status register + * @vq_irq: interrupt used for rpmsg + */ +struct ti_pru_data { + const struct ti_pru_info *info; + struct ti_pru_shared_data *shared; + struct regmap *ctrl; + int vq_irq; +}; + +static int ti_pru_rproc_start(struct rproc *rproc) +{ + struct ti_pru_data *pru = rproc->priv; + u32 val; + + val = (rproc->bootaddr >> 2) << (ffs(TI_PRU_CONTROL_PCRESETVAL) - 1); + val |= TI_PRU_CONTROL_ENABLE; + + return regmap_write(pru->ctrl, TI_PRU_CS_CONTROL, val); +} + +static int ti_pru_rproc_stop(struct rproc *rproc) +{ + struct ti_pru_data *pru = rproc->priv; + u32 mask; + + mask = TI_PRU_CONTROL_ENABLE; + + return regmap_write_bits(pru->ctrl, TI_PRU_CS_CONTROL, mask, 0); +} + +static void ti_pru_rproc_kick(struct rproc *rproc, int vqid) +{ + struct ti_pru_data *pru = rproc->priv; + struct ti_pru_shared_data *shared = pru->shared; + u32 val; + + val = pru->info->vq_arm_to_pru_event; + + regmap_write(shared->intc, TI_PRU_INTC_STATIDXSET, val); +} + +static void *ti_pru_rproc_da_to_va(struct rproc *rproc, u64 da, int len, int map) +{ + struct ti_pru_data *pru = rproc->priv; + struct ti_pru_shared_data *shared = pru->shared; + + if (map == 0) { + if (da + len > pru->info->inst.size) + return ERR_PTR(-EINVAL); + + return shared->base + pru->info->inst.offset + da; + } + + if (map == 1) { + if (da + len > pru->info->ram.size) + return ERR_PTR(-EINVAL); + + return shared->base + pru->info->ram.offset + da; + } + + return ERR_PTR(-EINVAL); +} + +static const struct rproc_ops ti_pru_rproc_ops = { + .start = ti_pru_rproc_start, + .stop = ti_pru_rproc_stop, + .kick = ti_pru_rproc_kick, + .da_to_va = ti_pru_rproc_da_to_va, +}; + +static struct regmap_config ti_pru_ctrl_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = 0x2c, +}; + +static struct regmap_config ti_pru_intc_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = 0x1500, +}; + +static struct regmap_config ti_pru_cfg_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = 0x40, +}; + +static const struct of_device_id ti_pru_rproc_of_match[] = { + { + .compatible = "ti,da850-pru-rproc", + .data = &ti_pru_devices[TI_PRU_TYPE_AM18XX] + }, + { + .compatible = "ti,am3352-pru-rproc", + .data = &ti_pru_devices[TI_PRU_TYPE_AM335X] + }, + { }, +}; +MODULE_DEVICE_TABLE(of, ti_pru_rproc_of_match); + +static irqreturn_t ti_pru_handle_vq_irq(int irq, void *p) +{ + struct rproc *rproc = p; + struct ti_pru_data *pru = rproc->priv; + + regmap_write(pru->shared->intc, TI_PRU_INTC_STATIDXCLR, + pru->info->vq_pru_to_arm_event); + + return IRQ_WAKE_THREAD; +} + +static irqreturn_t ti_pru_vq_irq_thread(int irq, void *p) +{ + struct rproc *rproc = p; + + rproc_vq_interrupt(rproc, 0); + rproc_vq_interrupt(rproc, 1); + + return IRQ_HANDLED; +} + +static void ti_pru_free_rproc(void *data) +{ + struct rproc *rproc = data; + + rproc_free(rproc); +} + +static struct rproc *ti_pru_init_one_rproc(struct ti_pru_shared_data *shared, + const struct ti_pru_info *info, + enum ti_pru id) +{ + struct device *dev = shared->dev; + struct platform_device *pdev = to_platform_device(dev); + const char *name; + char irq_name[16]; + struct rproc *rproc; + struct ti_pru_data *pru; + int err; + + name = devm_kasprintf(dev, GFP_KERNEL, "pru%u", id); + if (!name) + return ERR_PTR(-ENOMEM); + + rproc = rproc_alloc(dev, name, &ti_pru_rproc_ops, NULL, sizeof(*pru)); + if (!rproc) + return ERR_PTR(-ENOMEM); + + devm_add_action(dev, ti_pru_free_rproc, rproc); + + /* don't auto-boot for now - bad firmware can lock up the system */ + rproc->auto_boot = false; + + pru = rproc->priv; + pru->info = info; + pru->shared = shared; + + snprintf(irq_name, 16, "%s-vq", name); + + pru->vq_irq = platform_get_irq_byname(pdev, irq_name); + if (pru->vq_irq < 0) { + dev_err(&rproc->dev, "failed to get vq IRQ\n"); + return ERR_PTR(pru->vq_irq); + } + + err = devm_request_threaded_irq(&rproc->dev, pru->vq_irq, + ti_pru_handle_vq_irq, + ti_pru_vq_irq_thread, 0, name, rproc); + if (err < 0) { + dev_err(&rproc->dev, "failed to request vq IRQ\n"); + return ERR_PTR(err); + } + + pru->ctrl = devm_regmap_init_mmio(&rproc->dev, + shared->base + info->ctrl.offset, + &ti_pru_ctrl_regmap_config); + if (IS_ERR(pru->ctrl)) { + dev_err(&rproc->dev, "failed to init ctrl regmap\n"); + return ERR_CAST(pru->ctrl); + } + + return rproc; +} + +/** + * ti_pru_init_intc_polarity - configure polarity interrupt event + * @intc: the interrtup controller regmap + * @event: the source event + */ +static void ti_pru_init_intc_polarity(struct regmap *intc, int event) +{ + int offset, shift, mask; + + /* 32 events per register */ + offset = event / 32 * 4; + shift = event % 32; + mask = 1 << shift; + + /* polarity is always high (1) */ + regmap_write_bits(intc, TI_PRU_INTC_POLARITY0 + offset, mask, ~0); +} + +/** + * ti_pru_init_intc_type - configure type of interrupt event + * @intc: the interrtup controller regmap + * @event: the source event + */ +static void ti_pru_init_intc_type(struct regmap *intc, int event) +{ + int offset, shift, mask; + + /* 32 events per register */ + offset = event / 32 * 4; + shift = event % 32; + mask = 1 << shift; + + /* type is always pulse (0) */ + regmap_write_bits(intc, TI_PRU_INTC_TYPE0 + offset, mask, 0); +} + +/** + * ti_pru_init_intc_channel_map - configure interrupt event to channel mapping + * @intc: the interrtup controller regmap + * @event: the source event + * @ch: the channel to be assigned to the event + */ +static void ti_pru_init_intc_channel_map(struct regmap *intc, int event, int ch) +{ + int offset, shift, mask, val; + + /* 4 channels per 32-bit register */ + offset = event / 4 * 4; + shift = event % 4 * 8; + mask = 0xff << shift; + val = ch << shift; + + regmap_write_bits(intc, TI_PRU_INTC_CHANMAP0 + offset, mask, val); +} + +/** + * ti_pru_init_intc_host_map - configure interrupt channel to host mapping + * @intc: the interrtup controller regmap + * @ch: the source channel + * @host: the host interrupt to be assigned to the channel + */ +static void ti_pru_init_intc_host_map(struct regmap *intc, int ch, int host) +{ + int offset, shift, mask, val; + + /* 4 hosts per 32-bit register */ + offset = ch / 4 * 4; + shift = ch % 4 * 8; + mask = 0xff << shift; + val = host << shift; + + regmap_write_bits(intc, TI_PRU_INTC_HOSTMAP0 + offset, mask, val); +} + +static void ti_pru_init_intc(struct regmap *intc, + const struct ti_pru_device_info *info) +{ + int arm_to_pru0 = info->pru[TI_PRU0].vq_arm_to_pru_event; + int arm_to_pru1 = info->pru[TI_PRU1].vq_arm_to_pru_event; + int pru0_to_arm = info->pru[TI_PRU0].vq_pru_to_arm_event; + int pru1_to_arm = info->pru[TI_PRU1].vq_pru_to_arm_event; + + /* set polarity of system events */ + ti_pru_init_intc_polarity(intc, arm_to_pru0); + ti_pru_init_intc_polarity(intc, arm_to_pru1); + ti_pru_init_intc_polarity(intc, pru0_to_arm); + ti_pru_init_intc_polarity(intc, pru1_to_arm); + + /* set type of system events */ + ti_pru_init_intc_type(intc, arm_to_pru0); + ti_pru_init_intc_type(intc, arm_to_pru1); + ti_pru_init_intc_type(intc, pru0_to_arm); + ti_pru_init_intc_type(intc, pru1_to_arm); + + /* map system events to channels */ + ti_pru_init_intc_channel_map(intc, arm_to_pru0, 0); + ti_pru_init_intc_channel_map(intc, arm_to_pru1, 1); + ti_pru_init_intc_channel_map(intc, pru0_to_arm, 2); + ti_pru_init_intc_channel_map(intc, pru1_to_arm, 3); + + /* map channels to host interrupts */ + ti_pru_init_intc_host_map(intc, 0, 0); /* ARM to PRU0 */ + ti_pru_init_intc_host_map(intc, 1, 1); /* ARM to PRU1 */ + ti_pru_init_intc_host_map(intc, 2, 2); /* PRU0 to ARM */ + ti_pru_init_intc_host_map(intc, 3, 3); /* PRU1 to ARM */ + + /* clear system interrupts */ + regmap_write(intc, TI_PRU_INTC_STATIDXCLR, arm_to_pru0); + regmap_write(intc, TI_PRU_INTC_STATIDXCLR, arm_to_pru1); + regmap_write(intc, TI_PRU_INTC_STATIDXCLR, pru0_to_arm); + regmap_write(intc, TI_PRU_INTC_STATIDXCLR, pru1_to_arm); + + /* enable host interrupts for kicking */ + regmap_write(intc, TI_PRU_INTC_HSTINTENIDXSET, 0); /* ARM to PRU0 */ + regmap_write(intc, TI_PRU_INTC_HSTINTENIDXSET, 1); /* ARM to PRU1 */ + regmap_write(intc, TI_PRU_INTC_HSTINTENIDXSET, 2); /* PRU0 to ARM */ + regmap_write(intc, TI_PRU_INTC_HSTINTENIDXSET, 3); /* PRU1 to ARM */ + + /* enable system events for kicking */ + regmap_write(intc, TI_PRU_INTC_ENIDXSET, arm_to_pru0); + regmap_write(intc, TI_PRU_INTC_ENIDXSET, arm_to_pru1); + regmap_write(intc, TI_PRU_INTC_ENIDXSET, pru0_to_arm); + regmap_write(intc, TI_PRU_INTC_ENIDXSET, pru1_to_arm); + + /* enable all interrupts */ + regmap_write_bits(intc, TI_PRU_INTC_GLBLEN, 1, 1); +} + +static int ti_pru_rproc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct ti_pruss_platform_data *pdata = dev_get_platdata(dev); + const struct of_device_id *of_id; + const struct ti_pru_device_info *info; + struct ti_pru_shared_data *shared; + struct resource *res; + int err; + + of_id = of_match_device(ti_pru_rproc_of_match, dev); + if (!of_id || !of_id->data) + return -EINVAL; + + info = of_id->data; + + shared = devm_kzalloc(dev, sizeof(*shared), GFP_KERNEL); + + platform_set_drvdata(pdev, shared); + + shared->info = &info->shared; + shared->dev = dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + shared->base = devm_ioremap_resource(dev, res); + if (IS_ERR(shared->base)) { + dev_err(dev, "failed to ioremap resource\n"); + return PTR_ERR(shared->base); + } + + shared->intc = devm_regmap_init_mmio(dev, + shared->base + shared->info->intc.offset, + &ti_pru_intc_regmap_config); + if (IS_ERR(shared->intc)) { + dev_err(dev, "failed to init intc regmap\n"); + return PTR_ERR(shared->intc); + } + + if (shared->info->cfg.size) { + shared->cfg = devm_regmap_init_mmio(dev, + shared->base + shared->info->cfg.offset, + &ti_pru_cfg_regmap_config); + if (IS_ERR(shared->cfg)) { + dev_err(dev, "failed to init cfg regmap\n"); + return PTR_ERR(shared->cfg); + } + } + + shared->pru[TI_PRU0] = ti_pru_init_one_rproc(shared, &info->pru[TI_PRU0], + TI_PRU0); + if (IS_ERR(shared->pru[TI_PRU0])) + return PTR_ERR(shared->pru[TI_PRU0]); + + shared->pru[TI_PRU1] = ti_pru_init_one_rproc(shared, &info->pru[TI_PRU1], + TI_PRU1); + if (IS_ERR(shared->pru[TI_PRU1])) + return PTR_ERR(shared->pru[TI_PRU1]); + + pm_runtime_enable(dev); + + err = pm_runtime_get_sync(dev); + if (err < 0) + goto err_pm_runtime_disable; + + if (pdata) { + err = pdata->deassert_reset(pdev, pdata->reset_name); + if (err < 0) { + dev_err(dev, "Failed to reset pruss\n"); + goto err_pm_runtime_put; + } + } + + if (shared->cfg) { + int mask, val; + + mask = TI_PRU_SYSCFG_IDLE_MODE_MASK | TI_PRU_SYSCFG_STANDBY_MODE_MASK; + val = TI_PRU_SYSCFG_IDLE_MODE_SMART | TI_PRU_SYSCFG_STANDBY_MODE_SMART; + regmap_write_bits(shared->cfg, TI_PRU_CFG_SYSCFG, mask, val); + + mask = TI_PRU_SYSCFG_STANDBY_INIT; + val = 0; + regmap_write_bits(shared->cfg, TI_PRU_CFG_SYSCFG, mask, val); + + err = regmap_read_poll_timeout(shared->cfg, TI_PRU_CFG_SYSCFG, + val, !(val & TI_PRU_SYSCFG_SUB_MWAIT), 5, 50); + if (err < 0) { + dev_err(dev, "timeout while enabling pruss\n"); + goto err_pm_runtime_put; + } + } + + ti_pru_init_intc(shared->intc, info); + + err = rproc_add(shared->pru[TI_PRU0]); + if (err < 0) + goto err_assert_reset; + + err = rproc_add(shared->pru[TI_PRU1]); + if (err < 0) + goto err_del_pru0; + + return 0; + +err_del_pru0: + rproc_del(shared->pru[TI_PRU0]); +err_assert_reset: + if (pdata) + pdata->assert_reset(pdev, pdata->reset_name); +err_pm_runtime_put: + pm_runtime_put(dev); +err_pm_runtime_disable: + pm_runtime_disable(dev); + + return err; +} + +static int ti_pru_rproc_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct ti_pruss_platform_data *pdata = dev_get_platdata(dev); + struct ti_pru_shared_data *shared = platform_get_drvdata(pdev); + + rproc_del(shared->pru[TI_PRU1]); + rproc_del(shared->pru[TI_PRU0]); + if (pdata) + pdata->assert_reset(pdev, pdata->reset_name); + pm_runtime_put(dev); + pm_runtime_disable(dev); + + return 0; +} + +static struct platform_driver ti_pru_rproc_driver = { + .probe = ti_pru_rproc_probe, + .remove = ti_pru_rproc_remove, + .driver = { + .name = "ti-pru-rproc", + .of_match_table = ti_pru_rproc_of_match, + }, +}; +module_platform_driver(ti_pru_rproc_driver); + +MODULE_AUTHOR("David Lechner <david@lechnology.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Remoteproc driver for TI PRU");
This adds a new remoteproc driver for TI Programmable Realtime Units (PRUs). This has been tested working on AM1808 (LEGO MINDSTORMS EV3) using the sample rpmsg client driver. Signed-off-by: David Lechner <david@lechnology.com> --- MAINTAINERS | 5 + drivers/remoteproc/Kconfig | 7 + drivers/remoteproc/Makefile | 1 + drivers/remoteproc/ti_pru_rproc.c | 660 ++++++++++++++++++++++++++++++ 4 files changed, 673 insertions(+) create mode 100644 drivers/remoteproc/ti_pru_rproc.c