/* * PRU-ICSS remoteproc driver for various TI SoCs * * Copyright (C) 2014-2016 Texas Instruments Incorporated - http://www.ti.com/ * Suman Anna * Andrew F. Davis * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include #include #include #include #include "remoteproc_internal.h" #include "pruss.h" #include "pru_rproc.h" /* PRU_ICSS_PRU_CTRL registers */ #define PRU_CTRL_CTRL 0x0000 #define PRU_CTRL_STS 0x0004 #define PRU_CTRL_WAKEUP_EN 0x0008 #define PRU_CTRL_CYCLE 0x000C #define PRU_CTRL_STALL 0x0010 #define PRU_CTRL_CTBIR0 0x0020 #define PRU_CTRL_CTBIR1 0x0024 #define PRU_CTRL_CTPPR0 0x0028 #define PRU_CTRL_CTPPR1 0x002C /* CTRL register bit-fields */ #define CTRL_CTRL_SOFT_RST_N BIT(0) #define CTRL_CTRL_EN BIT(1) #define CTRL_CTRL_SLEEPING BIT(2) #define CTRL_CTRL_CTR_EN BIT(3) #define CTRL_CTRL_SINGLE_STEP BIT(8) #define CTRL_CTRL_RUNSTATE BIT(15) /* PRU_ICSS_PRU_DEBUG registers */ #define PRU_DEBUG_GPREG(x) (0x0000 + (x) * 4) #define PRU_DEBUG_CT_REG(x) (0x0080 + (x) * 4) /* Bit-field definitions for PRU functional capabilities */ #define PRU_FUNC_CAPS_ETHERNET BIT(0) /** * enum pru_mem - PRU core memory range identifiers */ enum pru_mem { PRU_MEM_IRAM = 0, PRU_MEM_CTRL, PRU_MEM_DEBUG, PRU_MEM_MAX, }; /** * struct pru_private_data - PRU core private data * @id: PRU index * @caps: functional capabilities the PRU core can support * @fw_name: firmware name to be used for the PRU core * @eth_fw_name: firmware name to be used for PRUSS ethernet usecases on IDKs */ struct pru_private_data { u32 id; int caps; const char *fw_name; const char *eth_fw_name; }; /** * struct pru_match_private_data - private data to handle multiple instances * @device_name: device name of the PRU processor core instance * @priv_data: PRU driver private data for this PRU processor core instance */ struct pru_match_private_data { const char *device_name; struct pru_private_data *priv_data; }; /** * struct pru_rproc: PRU remoteproc structure * @id: id of the PRU core within the PRUSS * @pruss: back-reference to parent PRUSS structure * @rproc: remoteproc pointer for this PRU core * @mbox: mailbox channel handle used for vring signalling with MPU * @client: mailbox client to request the mailbox channel * @irq_ring: IRQ number to use for processing vring buffers * @irq_kick: IRQ number to use to perform virtio kick * @mem_regions: data for each of the PRU memory regions * @intc_config: PRU INTC configuration data * @rmw_lock: lock for read, modify, write operations on registers * @iram_da: device address of Instruction RAM for this PRU * @pdram_da: device address of primary Data RAM for this PRU * @sdram_da: device address of secondary Data RAM for this PRU * @shrdram_da: device address of shared Data RAM * @fw_name: name of firmware image used during loading * @dbg_single_step: debug state variable to set PRU into single step mode * @dbg_continuous: debug state variable to restore PRU execution mode * @use_eth: flag to indicate ethernet usecase functionality */ struct pru_rproc { int id; struct pruss *pruss; struct rproc *rproc; struct mbox_chan *mbox; struct mbox_client client; int irq_vring; int irq_kick; struct pruss_mem_region mem_regions[PRU_MEM_MAX]; struct pruss_intc_config intc_config; spinlock_t rmw_lock; /* register access lock */ u32 iram_da; u32 pdram_da; u32 sdram_da; u32 shrdram_da; const char *fw_name; u32 dbg_single_step; u32 dbg_continuous; bool use_eth; }; static bool use_eth_fw = true; /* ignored for non-IDK platforms */ module_param(use_eth_fw, bool, S_IRUGO); MODULE_PARM_DESC(use_eth_fw, "Use Ethernet firmware on applicable PRUs"); static inline u32 pru_control_read_reg(struct pru_rproc *pru, unsigned int reg) { return readl_relaxed(pru->mem_regions[PRU_MEM_CTRL].va + reg); } static inline void pru_control_write_reg(struct pru_rproc *pru, unsigned int reg, u32 val) { writel_relaxed(val, pru->mem_regions[PRU_MEM_CTRL].va + reg); } static inline void pru_control_set_reg(struct pru_rproc *pru, unsigned int reg, u32 mask, u32 set) { u32 val; unsigned long flags; spin_lock_irqsave(&pru->rmw_lock, flags); val = pru_control_read_reg(pru, reg); val &= ~mask; val |= (set & mask); pru_control_write_reg(pru, reg, val); spin_unlock_irqrestore(&pru->rmw_lock, flags); } /** * pru_rproc_set_ctable() - set the constant table index for the PRU * @rproc: the rproc instance of the PRU * @c: constant table index to set * @addr: physical address to set it to */ int pru_rproc_set_ctable(struct rproc *rproc, enum pru_ctable_idx c, u32 addr) { struct pru_rproc *pru = rproc->priv; unsigned reg; u32 mask, set; u16 idx; u16 idx_mask; /* pointer is 16 bit and index is 8-bit so mask out the rest */ idx_mask = (c >= PRU_C28) ? 0xFFFF : 0xFF; /* ctable uses bit 8 and upwards only */ idx = (addr >> 8) & idx_mask; /* configurable ctable (i.e. C24) starts at PRU_CTRL_CTBIR0 */ reg = PRU_CTRL_CTBIR0 + 4 * (c >> 1); mask = idx_mask << (16 * (c & 1)); set = idx << (16 * (c & 1)); pru_control_set_reg(pru, reg, mask, set); return 0; } EXPORT_SYMBOL_GPL(pru_rproc_set_ctable); static inline u32 pru_debug_read_reg(struct pru_rproc *pru, unsigned int reg) { return readl_relaxed(pru->mem_regions[PRU_MEM_DEBUG].va + reg); } static inline void pru_debug_write_reg(struct pru_rproc *pru, unsigned int reg, u32 val) { writel_relaxed(val, pru->mem_regions[PRU_MEM_DEBUG].va + reg); } /* * Convert PRU device address (data spaces only) to kernel virtual address * * Each PRU has access to all data memories within the PRUSS, accessible at * different ranges. So, look through both its primary and secondary Data * RAMs as well as any shared Data RAM to convert a PRU device address to * kernel virtual address. Data RAM0 is primary Data RAM for PRU0 and Data * RAM1 is primary Data RAM for PRU1. */ static void *pru_d_da_to_va(struct pru_rproc *pru, u32 da, int len) { struct pruss_mem_region dram0, dram1, shrd_ram; struct pruss *pruss = pru->pruss; u32 offset; void *va = NULL; if (len <= 0) return NULL; dram0 = pruss->mem_regions[PRUSS_MEM_DRAM0]; dram1 = pruss->mem_regions[PRUSS_MEM_DRAM1]; /* PRU1 has its local RAM addresses reversed */ if (pru->id == 1) swap(dram0, dram1); shrd_ram = pruss->mem_regions[PRUSS_MEM_SHRD_RAM2]; if (da >= pru->pdram_da && da + len <= pru->pdram_da + dram0.size) { offset = da - pru->pdram_da; va = (__force void *)(dram0.va + offset); } else if (da >= pru->sdram_da && da + len <= pru->sdram_da + dram1.size) { offset = da - pru->sdram_da; va = (__force void *)(dram1.va + offset); } else if (da >= pru->shrdram_da && da + len <= pru->shrdram_da + shrd_ram.size) { offset = da - pru->shrdram_da; va = (__force void *)(shrd_ram.va + offset); } return va; } /* * Convert PRU device address (instruction space) to kernel virtual address * * A PRU does not have an unified address space. Each PRU has its very own * private Instruction RAM, and its device address is identical to that of * its primary Data RAM device address. */ static void *pru_i_da_to_va(struct pru_rproc *pru, u32 da, int len) { u32 offset; void *va = NULL; if (len <= 0) return NULL; if (da >= pru->iram_da && da + len <= pru->iram_da + pru->mem_regions[PRU_MEM_IRAM].size) { offset = da - pru->iram_da; va = (__force void *)(pru->mem_regions[PRU_MEM_IRAM].va + offset); } return va; } static int pru_rproc_debug_read_regs(struct seq_file *s, void *data) { struct rproc *rproc = s->private; struct pru_rproc *pru = rproc->priv; int i, nregs = 32; u32 pru_sts; int pru_is_running; seq_puts(s, "============== Control Registers ==============\n"); seq_printf(s, "CTRL := 0x%08x\n", pru_control_read_reg(pru, PRU_CTRL_CTRL)); pru_sts = pru_control_read_reg(pru, PRU_CTRL_STS); seq_printf(s, "STS (PC) := 0x%08x (0x%08x)\n", pru_sts, pru_sts << 2); seq_printf(s, "WAKEUP_EN := 0x%08x\n", pru_control_read_reg(pru, PRU_CTRL_WAKEUP_EN)); seq_printf(s, "CYCLE := 0x%08x\n", pru_control_read_reg(pru, PRU_CTRL_CYCLE)); seq_printf(s, "STALL := 0x%08x\n", pru_control_read_reg(pru, PRU_CTRL_STALL)); seq_printf(s, "CTBIR0 := 0x%08x\n", pru_control_read_reg(pru, PRU_CTRL_CTBIR0)); seq_printf(s, "CTBIR1 := 0x%08x\n", pru_control_read_reg(pru, PRU_CTRL_CTBIR1)); seq_printf(s, "CTPPR0 := 0x%08x\n", pru_control_read_reg(pru, PRU_CTRL_CTPPR0)); seq_printf(s, "CTPPR1 := 0x%08x\n", pru_control_read_reg(pru, PRU_CTRL_CTPPR1)); seq_puts(s, "=============== Debug Registers ===============\n"); pru_is_running = pru_control_read_reg(pru, PRU_CTRL_CTRL) & CTRL_CTRL_RUNSTATE; if (pru_is_running) { seq_puts(s, "PRU is executing, cannot print/access debug registers.\n"); return 0; } for (i = 0; i < nregs; i++) { seq_printf(s, "GPREG%-2d := 0x%08x\tCT_REG%-2d := 0x%08x\n", i, pru_debug_read_reg(pru, PRU_DEBUG_GPREG(i)), i, pru_debug_read_reg(pru, PRU_DEBUG_CT_REG(i))); } return 0; } static int pru_rproc_debug_regs_open(struct inode *inode, struct file *file) { return single_open(file, pru_rproc_debug_read_regs, inode->i_private); } static const struct file_operations pru_rproc_debug_regs_ops = { .open = pru_rproc_debug_regs_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; /* * Control PRU single-step mode * * This is a debug helper function used for controlling the single-step * mode of the PRU. The PRU Debug registers are not accessible when the * PRU is in RUNNING state. * * Writing a non-zero value sets the PRU into single-step mode irrespective * of its previous state. The PRU mode is saved only on the first set into * a single-step mode. Writing a non-zero value will restore the PRU into * its original mode. */ static int pru_rproc_debug_ss_set(void *data, u64 val) { struct rproc *rproc = data; struct pru_rproc *pru = rproc->priv; u32 reg_val; val = val ? 1 : 0; if (!val && !pru->dbg_single_step) return 0; reg_val = pru_control_read_reg(pru, PRU_CTRL_CTRL); if (val && !pru->dbg_single_step) pru->dbg_continuous = reg_val; if (val) reg_val |= CTRL_CTRL_SINGLE_STEP | CTRL_CTRL_EN; else reg_val = pru->dbg_continuous; pru->dbg_single_step = val; pru_control_write_reg(pru, PRU_CTRL_CTRL, reg_val); return 0; } static int pru_rproc_debug_ss_get(void *data, u64 *val) { struct rproc *rproc = data; struct pru_rproc *pru = rproc->priv; *val = pru->dbg_single_step; return 0; } DEFINE_SIMPLE_ATTRIBUTE(pru_rproc_debug_ss_fops, pru_rproc_debug_ss_get, pru_rproc_debug_ss_set, "%llu\n"); /* * Create PRU-specific debugfs entries * * The entries are created only if the parent remoteproc debugfs directory * exists, and will be cleaned up by the remoteproc core. */ static void pru_rproc_create_debug_entries(struct rproc *rproc) { if (!rproc->dbg_dir) return; debugfs_create_file("regs", 0400, rproc->dbg_dir, rproc, &pru_rproc_debug_regs_ops); debugfs_create_file("single_step", 0600, rproc->dbg_dir, rproc, &pru_rproc_debug_ss_fops); } /** * pru_rproc_mbox_callback() - inbound mailbox message handler * @client: mailbox client pointer used for requesting the mailbox channel * @data: mailbox payload * * This handler is invoked by omap's mailbox driver whenever a mailbox * message is received. Usually, the mailbox payload simply contains * the index of the virtqueue that is kicked by the PRU remote processor, * and we let remoteproc core handle it. * * In addition to virtqueue indices, we might also have some out-of-band * values that indicates different events. Those values are deliberately * very big so they don't coincide with virtqueue indices. */ static void pru_rproc_mbox_callback(struct mbox_client *client, void *data) { struct pru_rproc *pru = container_of(client, struct pru_rproc, client); struct device *dev = &pru->rproc->dev; u32 msg = (u32)data; dev_dbg(dev, "mbox msg: 0x%x\n", msg); /* msg contains the index of the triggered vring */ if (rproc_vq_interrupt(pru->rproc, msg) == IRQ_NONE) dev_dbg(dev, "no message was found in vqid %d\n", msg); } /** * pru_rproc_vring_interrupt() - interrupt handler for processing vrings * @irq: irq number associated with the PRU event MPU is listening on * @data: interrupt handler data, will be a PRU rproc structure * * This handler is used by the PRU remoteproc driver when using PRU system * events for processing the virtqueues. Unlike the mailbox IP, there is * no payload associated with an interrupt, so either a unique event is * used for each virtqueue kick, or a both virtqueues are processed on * a single event. The latter is chosen to conserve the usable PRU system * events. */ static irqreturn_t pru_rproc_vring_interrupt(int irq, void *data) { struct pru_rproc *pru = data; dev_dbg(&pru->rproc->dev, "got vring irq\n"); /* process incoming buffers on both the Rx and Tx vrings */ rproc_vq_interrupt(pru->rproc, 0); rproc_vq_interrupt(pru->rproc, 1); return IRQ_HANDLED; } /* kick a virtqueue */ static void pru_rproc_kick(struct rproc *rproc, int vq_id) { struct device *dev = &rproc->dev; struct pru_rproc *pru = rproc->priv; int ret; dev_dbg(dev, "kicking vqid %d on PRU%d\n", vq_id, pru->id); if (pru->mbox) { /* * send the index of the triggered virtqueue in the mailbox * payload */ ret = mbox_send_message(pru->mbox, (void *)vq_id); if (ret < 0) dev_err(dev, "mbox_send_message failed: %d\n", ret); } else if (pru->irq_kick > 0) { ret = pruss_intc_trigger(pru->irq_kick); if (ret < 0) dev_err(dev, "pruss_intc_trigger failed: %d\n", ret); } } /* start a PRU core */ static int pru_rproc_start(struct rproc *rproc) { struct device *dev = &rproc->dev; struct pru_rproc *pru = rproc->priv; u32 val; int ret; dev_dbg(dev, "starting PRU%d: entry-point = 0x%x\n", pru->id, (rproc->bootaddr >> 2)); if (!list_empty(&pru->rproc->rvdevs)) { if (!pru->mbox && (pru->irq_vring <= 0 || pru->irq_kick <= 0)) { dev_err(dev, "virtio vring interrupt mechanisms are not provided\n"); ret = -EINVAL; goto fail; } if (!pru->mbox && pru->irq_vring > 0) { ret = request_threaded_irq(pru->irq_vring, NULL, pru_rproc_vring_interrupt, IRQF_ONESHOT, dev_name(dev), pru); if (ret) { dev_err(dev, "failed to enable vring interrupt, ret = %d\n", ret); goto fail; } } } val = CTRL_CTRL_EN | ((rproc->bootaddr >> 2) << 16); pru_control_write_reg(pru, PRU_CTRL_CTRL, val); return 0; fail: pruss_intc_unconfigure(pru->pruss, &pru->intc_config); return ret; } /* stop/disable a PRU core */ static int pru_rproc_stop(struct rproc *rproc) { struct device *dev = &rproc->dev; struct pru_rproc *pru = rproc->priv; u32 val; dev_dbg(dev, "stopping PRU%d\n", pru->id); val = pru_control_read_reg(pru, PRU_CTRL_CTRL); val &= ~CTRL_CTRL_EN; pru_control_write_reg(pru, PRU_CTRL_CTRL, val); if (!list_empty(&pru->rproc->rvdevs) && !pru->mbox && pru->irq_vring > 0) free_irq(pru->irq_vring, pru); /* undo INTC config */ pruss_intc_unconfigure(pru->pruss, &pru->intc_config); return 0; } /* * parse the custom interrupt map resource and configure the INTC * appropriately */ static int pru_handle_custom_intrmap(struct rproc *rproc, struct fw_rsc_custom_intrmap *intr_rsc) { struct device *dev = rproc->dev.parent; struct pru_rproc *pru = rproc->priv; struct pruss *pruss = pru->pruss; struct pruss_event_chnl *event_chnl_map; int i, ret; s8 sys_evt, chnl, intr_no; dev_dbg(dev, "version %d event_chnl_map_size %d event_chnl_map %p\n", intr_rsc->version, intr_rsc->event_chnl_map_size, intr_rsc->event_chnl_map); if (intr_rsc->version != 0) { dev_err(dev, "only custom ints resource version 0 supported\n"); return -EINVAL; } if (intr_rsc->event_chnl_map_size < 0 || intr_rsc->event_chnl_map_size >= MAX_PRU_SYS_EVENTS) { dev_err(dev, "custom ints resource has more events than present on hardware\n"); return -EINVAL; } /* * XXX: The event_chnl_map mapping is currently a pointer in device * memory, evaluate if this needs to be directly in firmware file. */ event_chnl_map = pru_d_da_to_va(pru, (u32)intr_rsc->event_chnl_map, intr_rsc->event_chnl_map_size * sizeof(*event_chnl_map)); if (!event_chnl_map) { dev_err(dev, "custom ints resource has inadequate event_chnl_map configuration\n"); return -EINVAL; } /* init intc_config to defaults */ for (i = 0; i < ARRAY_SIZE(pru->intc_config.sysev_to_ch); i++) pru->intc_config.sysev_to_ch[i] = -1; for (i = 0; i < ARRAY_SIZE(pru->intc_config.ch_to_host); i++) pru->intc_config.ch_to_host[i] = -1; /* parse and fill in system event to interrupt channel mapping */ for (i = 0; i < intr_rsc->event_chnl_map_size; i++) { sys_evt = event_chnl_map[i].event; chnl = event_chnl_map[i].chnl; if (sys_evt < 0 || sys_evt >= MAX_PRU_SYS_EVENTS) { dev_err(dev, "[%d] bad sys event %d\n", i, sys_evt); return -EINVAL; } if (chnl < 0 || chnl >= MAX_PRU_CHANNELS) { dev_err(dev, "[%d] bad channel value %d\n", i, chnl); return -EINVAL; } pru->intc_config.sysev_to_ch[sys_evt] = chnl; dev_dbg(dev, "sysevt-to-ch[%d] -> %d\n", sys_evt, chnl); } /* parse and handle interrupt channel-to-host interrupt mapping */ for (i = 0; i < MAX_PRU_CHANNELS; i++) { intr_no = intr_rsc->chnl_host_intr_map[i]; if (intr_no < 0) { dev_dbg(dev, "skip intr mapping for chnl %d\n", i); continue; } if (intr_no >= MAX_PRU_HOST_INT) { dev_err(dev, "bad intr mapping for chnl %d, intr_no %d\n", i, intr_no); return -EINVAL; } pru->intc_config.ch_to_host[i] = intr_no; dev_dbg(dev, "chnl-to-host[%d] -> %d\n", i, intr_no); } ret = pruss_intc_configure(pruss, &pru->intc_config); if (ret) dev_err(dev, "failed to configure pruss intc %d\n", ret); return ret; } /* PRU-specific post loading custom resource handler */ static int pru_rproc_handle_custom_rsc(struct rproc *rproc, struct fw_rsc_custom *rsc) { struct device *dev = rproc->dev.parent; int ret = -EINVAL; switch (rsc->sub_type) { case PRUSS_RSC_INTRS: ret = pru_handle_custom_intrmap(rproc, (struct fw_rsc_custom_intrmap *) rsc->data); break; default: dev_err(dev, "%s: handling unknown type %d\n", __func__, rsc->sub_type); } return ret; } /* PRU-specific address translator */ static void *pru_da_to_va(struct rproc *rproc, u64 da, int len, u32 flags) { struct pru_rproc *pru = rproc->priv; void *va; u32 exec_flag; exec_flag = ((flags & RPROC_FLAGS_ELF_SHDR) ? flags & SHF_EXECINSTR : ((flags & RPROC_FLAGS_ELF_PHDR) ? flags & PF_X : 0)); if (exec_flag) va = pru_i_da_to_va(pru, da, len); else va = pru_d_da_to_va(pru, da, len); return va; } static struct rproc_ops pru_rproc_ops = { .start = pru_rproc_start, .stop = pru_rproc_stop, .kick = pru_rproc_kick, .handle_custom_rsc = pru_rproc_handle_custom_rsc, .da_to_va = pru_da_to_va, }; static const struct of_device_id pru_rproc_match[]; static const struct pru_private_data *pru_rproc_get_private_data( struct platform_device *pdev) { const struct pru_match_private_data *data; const struct of_device_id *match; struct pru_private_data *pdata = NULL; match = of_match_device(pru_rproc_match, &pdev->dev); if (!match) return ERR_PTR(-ENODEV); for (data = match->data; data && data->device_name; data++) { if (!strcmp(dev_name(&pdev->dev), data->device_name)) pdata = data->priv_data; } /* fixup PRU capability differences between AM571x and AM572x IDKs */ if (pdata && of_machine_is_compatible("ti,am5718-idk")) pdata->caps = PRU_FUNC_CAPS_ETHERNET; return pdata; } static int pru_rproc_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; struct platform_device *ppdev = to_platform_device(dev->parent); struct pru_rproc *pru; const struct pru_private_data *pdata; struct rproc *rproc = NULL; struct mbox_client *client; struct resource *res; int i, ret; const char *mem_names[PRU_MEM_MAX] = { "iram", "control", "debug" }; bool use_eth = false; u32 mux_sel; if (!np) { dev_err(dev, "Non-DT platform device not supported\n"); return -ENODEV; } pdata = pru_rproc_get_private_data(pdev); if (IS_ERR_OR_NULL(pdata) || !pdata->fw_name) { dev_err(dev, "missing or incomplete PRU-private data\n"); return -ENODEV; } /* * use a different firmware name for PRU cores supporting * PRUSS ethernet on specific boards */ if (of_machine_is_compatible("ti,am3359-icev2") || of_machine_is_compatible("ti,am437x-idk-evm") || of_machine_is_compatible("ti,am5728-idk") || of_machine_is_compatible("ti,am5718-idk") || of_machine_is_compatible("ti,k2g-ice")) { if (use_eth_fw && (pdata->caps & PRU_FUNC_CAPS_ETHERNET)) use_eth = true; } rproc = rproc_alloc(dev, pdev->name, &pru_rproc_ops, (use_eth ? pdata->eth_fw_name : pdata->fw_name), sizeof(*pru)); if (!rproc) { dev_err(dev, "rproc_alloc failed\n"); return -ENOMEM; } /* error recovery is not supported for PRUs */ rproc->recovery_disabled = true; pru = rproc->priv; pru->id = pdata->id; pru->pruss = platform_get_drvdata(ppdev); pru->rproc = rproc; pru->fw_name = use_eth ? pdata->eth_fw_name : pdata->fw_name; pru->use_eth = use_eth; spin_lock_init(&pru->rmw_lock); /* XXX: get this from match data if different in the future */ pru->iram_da = 0; pru->pdram_da = 0; pru->sdram_da = 0x2000; pru->shrdram_da = 0x10000; for (i = 0; i < ARRAY_SIZE(mem_names); i++) { res = platform_get_resource_byname(pdev, IORESOURCE_MEM, mem_names[i]); pru->mem_regions[i].va = devm_ioremap_resource(dev, res); if (IS_ERR(pru->mem_regions[i].va)) { dev_err(dev, "failed to parse and map memory resource %d %s\n", i, mem_names[i]); ret = PTR_ERR(pru->mem_regions[i].va); goto free_rproc; } pru->mem_regions[i].pa = res->start; pru->mem_regions[i].size = resource_size(res); dev_dbg(dev, "memory %8s: pa %pa size 0x%x va %p\n", mem_names[i], &pru->mem_regions[i].pa, pru->mem_regions[i].size, pru->mem_regions[i].va); } platform_set_drvdata(pdev, rproc); client = &pru->client; client->dev = dev; client->tx_done = NULL; client->rx_callback = pru_rproc_mbox_callback; client->tx_block = false; client->knows_txdone = false; pru->mbox = mbox_request_channel(client, 0); if (IS_ERR(pru->mbox)) { ret = PTR_ERR(pru->mbox); pru->mbox = NULL; dev_dbg(dev, "mbox_request_channel failed: %d\n", ret); } pru->irq_vring = platform_get_irq_byname(pdev, "vring"); if (pru->irq_vring <= 0) { ret = pru->irq_vring; if (ret == -EPROBE_DEFER) goto free_rproc; dev_dbg(dev, "unable to get vring interrupt, status = %d\n", ret); } pru->irq_kick = platform_get_irq_byname(pdev, "kick"); if (pru->irq_kick <= 0) { ret = pru->irq_kick; if (ret == -EPROBE_DEFER) goto free_rproc; dev_dbg(dev, "unable to get kick interrupt, status = %d\n", ret); } if (pru->mbox && (pru->irq_vring > 0 || pru->irq_kick > 0)) dev_warn(dev, "both mailbox and vring/kick system events defined\n"); ret = rproc_add(pru->rproc); if (ret) { dev_err(dev, "rproc_add failed: %d\n", ret); goto put_mbox; } if ((of_machine_is_compatible("ti,am5718-idk") || of_machine_is_compatible("ti,k2g-ice")) && pru->use_eth && !of_property_read_u32(np, "ti,pruss-gp-mux-sel", &mux_sel)) { if (mux_sel < PRUSS_GP_MUX_SEL_GP || mux_sel >= PRUSS_GP_MUX_MAX) { dev_err(dev, "invalid gp_mux_sel %d\n", mux_sel); ret = -EINVAL; goto del_rproc; } ret = pruss_cfg_set_gpmux(pru->pruss, pru->id, mux_sel); if (ret) goto del_rproc; } pru_rproc_create_debug_entries(rproc); /* * rproc_add will boot the processor if the corresponding PRU * has a virtio device published in its resource table. If not * present, manually boot the PRU remoteproc, but only after * the remoteproc core is done with loading the firmware image. */ if (!pru->use_eth) { wait_for_completion(&pru->rproc->firmware_loading_complete); if (list_empty(&pru->rproc->rvdevs)) { dev_info(dev, "booting the PRU core manually\n"); ret = rproc_boot(pru->rproc); if (ret) { dev_err(dev, "rproc_boot failed\n"); goto del_rproc; } } } dev_info(dev, "PRU rproc node %s probed successfully\n", np->full_name); return 0; del_rproc: rproc_del(pru->rproc); put_mbox: mbox_free_channel(pru->mbox); free_rproc: rproc_put(rproc); return ret; } static int pru_rproc_remove(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct rproc *rproc = platform_get_drvdata(pdev); struct pru_rproc *pru = rproc->priv; dev_info(dev, "%s: removing rproc %s\n", __func__, rproc->name); if (!pru->use_eth) { if (list_empty(&pru->rproc->rvdevs)) { dev_info(dev, "stopping the manually booted PRU core\n"); rproc_shutdown(pru->rproc); } } mbox_free_channel(pru->mbox); if ((of_machine_is_compatible("ti,am5718-idk") || of_machine_is_compatible("ti,k2g-ice")) && pru->use_eth) pruss_cfg_set_gpmux(pru->pruss, pru->id, PRUSS_GP_MUX_SEL_GP); rproc_del(rproc); rproc_put(rproc); return 0; } /* AM33xx PRU core-specific private data */ static struct pru_private_data am335x_pru0_rproc_pdata = { .id = 0, .caps = PRU_FUNC_CAPS_ETHERNET, .fw_name = "am335x-pru0-fw", .eth_fw_name = "ti-pruss/am335x-pru0-prueth-fw.elf", }; static struct pru_private_data am335x_pru1_rproc_pdata = { .id = 1, .caps = PRU_FUNC_CAPS_ETHERNET, .fw_name = "am335x-pru1-fw", .eth_fw_name = "ti-pruss/am335x-pru1-prueth-fw.elf", }; /* AM437x PRUSS1 PRU core-specific private data */ static struct pru_private_data am437x_pru1_0_rproc_pdata = { .id = 0, .caps = PRU_FUNC_CAPS_ETHERNET, .fw_name = "am437x-pru1_0-fw", .eth_fw_name = "ti-pruss/am437x-pru0-prueth-fw.elf" }; static struct pru_private_data am437x_pru1_1_rproc_pdata = { .id = 1, .caps = PRU_FUNC_CAPS_ETHERNET, .fw_name = "am437x-pru1_1-fw", .eth_fw_name = "ti-pruss/am437x-pru1-prueth-fw.elf" }; /* AM437x PRUSS0 PRU core-specific private data */ static struct pru_private_data am437x_pru0_0_rproc_pdata = { .id = 0, .fw_name = "am437x-pru0_0-fw", }; static struct pru_private_data am437x_pru0_1_rproc_pdata = { .id = 1, .fw_name = "am437x-pru0_1-fw", }; /* AM57xx PRUSS1 PRU core-specific private data */ static struct pru_private_data am57xx_pru1_0_rproc_pdata = { .id = 0, .fw_name = "am57xx-pru1_0-fw", .eth_fw_name = "ti-pruss/am57xx-pru0-prueth-fw.elf" }; static struct pru_private_data am57xx_pru1_1_rproc_pdata = { .id = 1, .fw_name = "am57xx-pru1_1-fw", .eth_fw_name = "ti-pruss/am57xx-pru1-prueth-fw.elf" }; /* AM57xx PRUSS2 PRU core-specific private data */ static struct pru_private_data am57xx_pru2_0_rproc_pdata = { .id = 0, .caps = PRU_FUNC_CAPS_ETHERNET, .fw_name = "am57xx-pru2_0-fw", .eth_fw_name = "ti-pruss/am57xx-pru0-prueth-fw.elf" }; static struct pru_private_data am57xx_pru2_1_rproc_pdata = { .id = 1, .caps = PRU_FUNC_CAPS_ETHERNET, .fw_name = "am57xx-pru2_1-fw", .eth_fw_name = "ti-pruss/am57xx-pru1-prueth-fw.elf" }; /* K2G PRUSS0 PRU core-specific private data */ static struct pru_private_data k2g_pru0_0_rproc_pdata = { .id = 0, .caps = PRU_FUNC_CAPS_ETHERNET, .fw_name = "k2g-pru0_0-fw", .eth_fw_name = "ti-pruss/k2g-pru0-prueth-fw.elf" }; static struct pru_private_data k2g_pru0_1_rproc_pdata = { .id = 1, .caps = PRU_FUNC_CAPS_ETHERNET, .fw_name = "k2g-pru0_1-fw", .eth_fw_name = "ti-pruss/k2g-pru1-prueth-fw.elf" }; static struct pru_private_data k2g_pru1_0_rproc_pdata = { .id = 0, .caps = PRU_FUNC_CAPS_ETHERNET, .fw_name = "k2g-pru1_0-fw", .eth_fw_name = "ti-pruss/k2g-pru0-prueth-fw.elf" }; static struct pru_private_data k2g_pru1_1_rproc_pdata = { .id = 1, .caps = PRU_FUNC_CAPS_ETHERNET, .fw_name = "k2g-pru1_1-fw", .eth_fw_name = "ti-pruss/k2g-pru1-prueth-fw.elf" }; /* AM33xx SoC-specific PRU Device data */ static struct pru_match_private_data am335x_pru_match_data[] = { { .device_name = "4a334000.pru0", .priv_data = &am335x_pru0_rproc_pdata, }, { .device_name = "4a338000.pru1", .priv_data = &am335x_pru1_rproc_pdata, }, { /* sentinel */ }, }; /* AM43xx SoC-specific PRU Device data */ static struct pru_match_private_data am437x_pru_match_data[] = { { .device_name = "54434000.pru0", .priv_data = &am437x_pru1_0_rproc_pdata, }, { .device_name = "54438000.pru1", .priv_data = &am437x_pru1_1_rproc_pdata, }, { .device_name = "54474000.pru0", .priv_data = &am437x_pru0_0_rproc_pdata, }, { .device_name = "54478000.pru1", .priv_data = &am437x_pru0_1_rproc_pdata, }, { /* sentinel */ }, }; /* AM57xx SoC-specific PRU Device data */ static struct pru_match_private_data am57xx_pru_match_data[] = { { .device_name = "4b234000.pru0", .priv_data = &am57xx_pru1_0_rproc_pdata, }, { .device_name = "4b238000.pru1", .priv_data = &am57xx_pru1_1_rproc_pdata, }, { .device_name = "4b2b4000.pru0", .priv_data = &am57xx_pru2_0_rproc_pdata, }, { .device_name = "4b2b8000.pru1", .priv_data = &am57xx_pru2_1_rproc_pdata, }, { /* sentinel */ }, }; /* K2G SoC-specific PRU Device data */ static struct pru_match_private_data k2g_pru_match_data[] = { { .device_name = "20ab4000.pru0", .priv_data = &k2g_pru0_0_rproc_pdata, }, { .device_name = "20ab8000.pru1", .priv_data = &k2g_pru0_1_rproc_pdata, }, { .device_name = "20af4000.pru0", .priv_data = &k2g_pru1_0_rproc_pdata, }, { .device_name = "20af8000.pru1", .priv_data = &k2g_pru1_1_rproc_pdata, }, { /* sentinel */ }, }; static const struct of_device_id pru_rproc_match[] = { { .compatible = "ti,am3352-pru", .data = am335x_pru_match_data, }, { .compatible = "ti,am4372-pru", .data = am437x_pru_match_data, }, { .compatible = "ti,am5728-pru", .data = am57xx_pru_match_data, }, { .compatible = "ti,k2g-pru", .data = k2g_pru_match_data, }, {}, }; MODULE_DEVICE_TABLE(of, pru_rproc_match); static struct platform_driver pru_rproc_driver = { .driver = { .name = "pru-rproc", .of_match_table = pru_rproc_match, }, .probe = pru_rproc_probe, .remove = pru_rproc_remove, }; module_platform_driver(pru_rproc_driver); MODULE_AUTHOR("Suman Anna "); MODULE_DESCRIPTION("PRU-ICSS Remote Processor Driver"); MODULE_LICENSE("GPL v2");