Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+91
View File
@@ -0,0 +1,91 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# TI SOC drivers
#
menuconfig SOC_TI
bool "TI SOC drivers support"
if SOC_TI
config KEYSTONE_NAVIGATOR_QMSS
tristate "Keystone Queue Manager Sub System"
depends on ARCH_KEYSTONE
help
Say y here to support the Keystone multicore Navigator Queue
Manager support. The Queue Manager is a hardware module that
is responsible for accelerating management of the packet queues.
Packets are queued/de-queued by writing/reading descriptor address
to a particular memory mapped location in the Queue Manager module.
If unsure, say N.
config KEYSTONE_NAVIGATOR_DMA
tristate "TI Keystone Navigator Packet DMA support"
depends on ARCH_KEYSTONE
help
Say y tp enable support for the Keystone Navigator Packet DMA on
on Keystone family of devices. It sets up the dma channels for the
Queue Manager Sub System.
If unsure, say N.
config AMX3_PM
tristate "AMx3 Power Management"
depends on SOC_AM33XX || SOC_AM43XX
depends on WKUP_M3_IPC && TI_EMIF_SRAM && SRAM && RTC_DRV_OMAP
help
Enable power management on AM335x and AM437x. Required for suspend to mem
and standby states on both AM335x and AM437x platforms and for deeper cpuidle
c-states on AM335x. Also required for rtc and ddr in self-refresh low
power mode on AM437x platforms.
config WKUP_M3_IPC
tristate "TI AMx3 Wkup-M3 IPC Driver"
depends on WKUP_M3_RPROC
depends on OMAP2PLUS_MBOX
help
TI AM33XX and AM43XX have a Cortex M3, the Wakeup M3, to handle
low power transitions. This IPC driver provides the necessary API
to communicate and use the Wakeup M3 for PM features like suspend
resume and boots it using wkup_m3_rproc driver.
config TI_K3_RINGACC
tristate "K3 Ring accelerator Sub System"
depends on ARCH_K3 || COMPILE_TEST
depends on TI_SCI_INTA_IRQCHIP
help
Say y here to support the K3 Ring accelerator module.
The Ring Accelerator (RINGACC or RA) provides hardware acceleration
to enable straightforward passing of work between a producer
and a consumer. There is one RINGACC module per NAVSS on TI AM65x SoCs
If unsure, say N.
config TI_K3_SOCINFO
bool "K3 SoC Information driver" if COMPILE_TEST
depends on ARCH_K3 || COMPILE_TEST
select SOC_BUS
select MFD_SYSCON
help
Include support for the SoC bus socinfo for the TI K3 Multicore SoC
platforms to provide information about the SoC family and
variant to user space.
config TI_PRUSS
tristate "TI PRU-ICSS Subsystem Platform drivers"
depends on SOC_AM33XX || SOC_AM43XX || SOC_DRA7XX || ARCH_KEYSTONE || ARCH_K3 || COMPILE_TEST
select MFD_SYSCON
help
TI PRU-ICSS Subsystem platform specific support.
Say Y or M here to support the Programmable Realtime Unit (PRU)
processors on various TI SoCs. It's safe to say N here if you're
not interested in the PRU or if you are unsure.
endif # SOC_TI
config TI_SCI_INTA_MSI_DOMAIN
bool
select GENERIC_MSI_IRQ
help
Driver to enable Interrupt Aggregator specific MSI Domain.
+14
View File
@@ -0,0 +1,14 @@
# SPDX-License-Identifier: GPL-2.0
#
# TI Keystone SOC drivers
#
obj-$(CONFIG_KEYSTONE_NAVIGATOR_QMSS) += knav_qmss.o
knav_qmss-y := knav_qmss_queue.o knav_qmss_acc.o
obj-$(CONFIG_KEYSTONE_NAVIGATOR_DMA) += knav_dma.o
obj-$(CONFIG_AMX3_PM) += pm33xx.o
obj-$(CONFIG_WKUP_M3_IPC) += wkup_m3_ipc.o
obj-$(CONFIG_TI_SCI_INTA_MSI_DOMAIN) += ti_sci_inta_msi.o
obj-$(CONFIG_TI_K3_RINGACC) += k3-ringacc.o
obj-$(CONFIG_TI_K3_SOCINFO) += k3-socinfo.o
obj-$(CONFIG_TI_PRUSS) += pruss.o
obj-$(CONFIG_POWER_AVS_OMAP) += smartreflex.o
File diff suppressed because it is too large Load Diff
+257
View File
@@ -0,0 +1,257 @@
// SPDX-License-Identifier: GPL-2.0
/*
* TI K3 SoC info driver
*
* Copyright (C) 2020 Texas Instruments Incorporated - http://www.ti.com
*/
#include <linux/mfd/syscon.h>
#include <linux/nvmem-consumer.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/regmap.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/sys_soc.h>
#define CTRLMMR_WKUP_JTAGID_REG 0
/*
* Bits:
* 31-28 VARIANT Device variant
* 27-12 PARTNO Part number
* 11-1 MFG Indicates TI as manufacturer (0x17)
* 0 Always 1
*/
#define CTRLMMR_WKUP_JTAGID_VARIANT_SHIFT (28)
#define CTRLMMR_WKUP_JTAGID_VARIANT_MASK GENMASK(31, 28)
#define GP_SW1_ADR_MASK GENMASK(3, 0)
#define CTRLMMR_WKUP_JTAGID_PARTNO_SHIFT (12)
#define CTRLMMR_WKUP_JTAGID_PARTNO_MASK GENMASK(27, 12)
#define CTRLMMR_WKUP_JTAGID_MFG_SHIFT (1)
#define CTRLMMR_WKUP_JTAGID_MFG_MASK GENMASK(11, 1)
#define CTRLMMR_WKUP_JTAGID_MFG_TI 0x17
#define JTAG_ID_PARTNO_AM65X 0xBB5A
#define JTAG_ID_PARTNO_J721E 0xBB64
#define JTAG_ID_PARTNO_J7200 0xBB6D
#define JTAG_ID_PARTNO_AM64X 0xBB38
#define JTAG_ID_PARTNO_J721S2 0xBB75
#define JTAG_ID_PARTNO_AM62X 0xBB7E
#define JTAG_ID_PARTNO_J784S4 0xBB80
#define JTAG_ID_PARTNO_AM62AX 0xBB8D
#define JTAG_ID_PARTNO_AM62PX 0xBB9D
#define JTAG_ID_PARTNO_J722S 0xBBA0
#define JTAG_ID_PARTNO_AM62LX 0xBBA7
static const struct k3_soc_id {
unsigned int id;
const char *family_name;
} k3_soc_ids[] = {
{ JTAG_ID_PARTNO_AM65X, "AM65X" },
{ JTAG_ID_PARTNO_J721E, "J721E" },
{ JTAG_ID_PARTNO_J7200, "J7200" },
{ JTAG_ID_PARTNO_AM64X, "AM64X" },
{ JTAG_ID_PARTNO_J721S2, "J721S2"},
{ JTAG_ID_PARTNO_AM62X, "AM62X" },
{ JTAG_ID_PARTNO_J784S4, "J784S4" },
{ JTAG_ID_PARTNO_AM62AX, "AM62AX" },
{ JTAG_ID_PARTNO_AM62PX, "AM62PX" },
{ JTAG_ID_PARTNO_J722S, "J722S" },
{ JTAG_ID_PARTNO_AM62LX, "AM62LX" },
};
static const char * const j721e_rev_string_map[] = {
"1.0", "1.1", "2.0",
};
static const char * const am62lx_rev_string_map[] = {
"1.0", "1.1",
};
static const char * const am62p_gpsw_rev_string_map[] = {
"1.0", "1.1", "1.2",
};
static int
k3_chipinfo_get_gpsw_variant(struct device *dev)
{
u32 gpsw_val = 0;
int ret;
ret = nvmem_cell_read_u32(dev, "gpsw1", &gpsw_val);
if (ret)
return ret;
return gpsw_val & GP_SW1_ADR_MASK;
}
static int
k3_chipinfo_partno_to_names(unsigned int partno,
struct soc_device_attribute *soc_dev_attr)
{
int i;
for (i = 0; i < ARRAY_SIZE(k3_soc_ids); i++)
if (partno == k3_soc_ids[i].id) {
soc_dev_attr->family = k3_soc_ids[i].family_name;
return 0;
}
return -ENODEV;
}
static int
k3_chipinfo_variant_to_sr(struct device *dev, unsigned int partno,
unsigned int variant, struct soc_device_attribute *soc_dev_attr)
{
int gpsw_variant = 0;
switch (partno) {
case JTAG_ID_PARTNO_J721E:
if (variant >= ARRAY_SIZE(j721e_rev_string_map))
goto err_unknown_variant;
soc_dev_attr->revision = kasprintf(GFP_KERNEL, "SR%s",
j721e_rev_string_map[variant]);
break;
case JTAG_ID_PARTNO_AM62LX:
if (variant >= ARRAY_SIZE(am62lx_rev_string_map))
goto err_unknown_variant;
soc_dev_attr->revision = kasprintf(GFP_KERNEL, "SR%s",
am62lx_rev_string_map[variant]);
break;
case JTAG_ID_PARTNO_AM62PX:
/* Check GP_SW1 for silicon revision */
gpsw_variant = k3_chipinfo_get_gpsw_variant(dev);
if (gpsw_variant == -EPROBE_DEFER)
return gpsw_variant;
if (gpsw_variant < 0 || gpsw_variant >= ARRAY_SIZE(am62p_gpsw_rev_string_map)) {
dev_warn(dev, "Failed to get silicon variant (%d), set SR1.0\n",
gpsw_variant);
gpsw_variant = 0;
}
soc_dev_attr->revision = kasprintf(GFP_KERNEL, "SR%s",
am62p_gpsw_rev_string_map[gpsw_variant]);
break;
default:
variant++;
soc_dev_attr->revision = kasprintf(GFP_KERNEL, "SR%x.0",
variant);
}
if (!soc_dev_attr->revision)
return -ENOMEM;
return 0;
err_unknown_variant:
return -ENODEV;
}
static const struct regmap_config k3_chipinfo_regmap_cfg = {
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
};
static int k3_chipinfo_probe(struct platform_device *pdev)
{
struct device_node *node = pdev->dev.of_node;
struct soc_device_attribute *soc_dev_attr;
struct device *dev = &pdev->dev;
struct soc_device *soc_dev;
struct regmap *regmap;
void __iomem *base;
u32 partno_id;
u32 variant;
u32 jtag_id;
u32 mfg;
int ret;
base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(base))
return PTR_ERR(base);
regmap = devm_regmap_init_mmio(dev, base, &k3_chipinfo_regmap_cfg);
if (IS_ERR(regmap))
return PTR_ERR(regmap);
ret = regmap_read(regmap, CTRLMMR_WKUP_JTAGID_REG, &jtag_id);
if (ret < 0)
return ret;
mfg = (jtag_id & CTRLMMR_WKUP_JTAGID_MFG_MASK) >>
CTRLMMR_WKUP_JTAGID_MFG_SHIFT;
if (mfg != CTRLMMR_WKUP_JTAGID_MFG_TI) {
dev_err(dev, "Invalid MFG SoC\n");
return -ENODEV;
}
variant = (jtag_id & CTRLMMR_WKUP_JTAGID_VARIANT_MASK) >>
CTRLMMR_WKUP_JTAGID_VARIANT_SHIFT;
partno_id = (jtag_id & CTRLMMR_WKUP_JTAGID_PARTNO_MASK) >>
CTRLMMR_WKUP_JTAGID_PARTNO_SHIFT;
soc_dev_attr = kzalloc_obj(*soc_dev_attr);
if (!soc_dev_attr)
return -ENOMEM;
ret = k3_chipinfo_partno_to_names(partno_id, soc_dev_attr);
if (ret) {
dev_err(dev, "Unknown SoC JTAGID[0x%08X]: %d\n", jtag_id, ret);
goto err;
}
ret = k3_chipinfo_variant_to_sr(dev, partno_id, variant, soc_dev_attr);
if (ret) {
dev_err(dev, "Unknown SoC SR[0x%08X]: %d\n", jtag_id, ret);
goto err;
}
node = of_find_node_by_path("/");
of_property_read_string(node, "model", &soc_dev_attr->machine);
of_node_put(node);
soc_dev = soc_device_register(soc_dev_attr);
if (IS_ERR(soc_dev)) {
ret = PTR_ERR(soc_dev);
goto err_free_rev;
}
dev_info(dev, "Family:%s rev:%s JTAGID[0x%08x] Detected\n",
soc_dev_attr->family,
soc_dev_attr->revision, jtag_id);
return 0;
err_free_rev:
kfree(soc_dev_attr->revision);
err:
kfree(soc_dev_attr);
return ret;
}
static const struct of_device_id k3_chipinfo_of_match[] = {
{ .compatible = "ti,am654-chipid", },
{ /* sentinel */ },
};
static struct platform_driver k3_chipinfo_driver = {
.driver = {
.name = "k3-chipinfo",
.of_match_table = k3_chipinfo_of_match,
},
.probe = k3_chipinfo_probe,
};
static int __init k3_chipinfo_init(void)
{
return platform_driver_register(&k3_chipinfo_driver);
}
subsys_initcall(k3_chipinfo_init);
+784
View File
@@ -0,0 +1,784 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2014 Texas Instruments Incorporated
* Authors: Santosh Shilimkar <santosh.shilimkar@ti.com>
* Sandeep Nair <sandeep_n@ti.com>
* Cyril Chemparathy <cyril@ti.com>
*/
#include <linux/io.h>
#include <linux/sched.h>
#include <linux/module.h>
#include <linux/dma-direction.h>
#include <linux/interrupt.h>
#include <linux/pm_runtime.h>
#include <linux/of_dma.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/soc/ti/knav_dma.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#define REG_MASK 0xffffffff
#define DMA_LOOPBACK BIT(31)
#define DMA_ENABLE BIT(31)
#define DMA_TEARDOWN BIT(30)
#define DMA_TX_FILT_PSWORDS BIT(29)
#define DMA_TX_FILT_EINFO BIT(30)
#define DMA_TX_PRIO_SHIFT 0
#define DMA_RX_PRIO_SHIFT 16
#define DMA_PRIO_DEFAULT 0
#define DMA_RX_TIMEOUT_DEFAULT 17500 /* cycles */
#define DMA_RX_TIMEOUT_MASK GENMASK(16, 0)
#define DMA_RX_TIMEOUT_SHIFT 0
#define CHAN_HAS_EPIB BIT(30)
#define CHAN_HAS_PSINFO BIT(29)
#define CHAN_ERR_RETRY BIT(28)
#define CHAN_PSINFO_AT_SOP BIT(25)
#define CHAN_SOP_OFF_SHIFT 16
#define CHAN_SOP_OFF_MASK GENMASK(9, 0)
#define DESC_TYPE_SHIFT 26
#define DESC_TYPE_MASK GENMASK(2, 0)
/*
* QMGR & QNUM together make up 14 bits with QMGR as the 2 MSb's in the logical
* navigator cloud mapping scheme.
* using the 14bit physical queue numbers directly maps into this scheme.
*/
#define CHAN_QNUM_MASK GENMASK(14, 0)
#define DMA_MAX_QMS 4
#define DMA_TIMEOUT 1 /* msecs */
#define DMA_INVALID_ID 0xffff
struct reg_global {
u32 revision;
u32 perf_control;
u32 emulation_control;
u32 priority_control;
u32 qm_base_address[DMA_MAX_QMS];
};
struct reg_chan {
u32 control;
u32 mode;
u32 __rsvd[6];
};
struct reg_tx_sched {
u32 prio;
};
struct reg_rx_flow {
u32 control;
u32 tags;
u32 tag_sel;
u32 fdq_sel[2];
u32 thresh[3];
};
struct knav_dma_pool_device {
struct device *dev;
struct list_head list;
};
struct knav_dma_device {
bool loopback, enable_all;
unsigned tx_priority, rx_priority, rx_timeout;
unsigned logical_queue_managers;
unsigned qm_base_address[DMA_MAX_QMS];
struct reg_global __iomem *reg_global;
struct reg_chan __iomem *reg_tx_chan;
struct reg_rx_flow __iomem *reg_rx_flow;
struct reg_chan __iomem *reg_rx_chan;
struct reg_tx_sched __iomem *reg_tx_sched;
unsigned max_rx_chan, max_tx_chan;
unsigned max_rx_flow;
char name[32];
atomic_t ref_count;
struct list_head list;
struct list_head chan_list;
spinlock_t lock;
};
struct knav_dma_chan {
enum dma_transfer_direction direction;
struct knav_dma_device *dma;
atomic_t ref_count;
/* registers */
struct reg_chan __iomem *reg_chan;
struct reg_tx_sched __iomem *reg_tx_sched;
struct reg_rx_flow __iomem *reg_rx_flow;
/* configuration stuff */
unsigned channel, flow;
struct knav_dma_cfg cfg;
struct list_head list;
spinlock_t lock;
};
#define chan_number(ch) ((ch->direction == DMA_MEM_TO_DEV) ? \
ch->channel : ch->flow)
static struct knav_dma_pool_device *kdev;
static bool device_ready;
bool knav_dma_device_ready(void)
{
return device_ready;
}
EXPORT_SYMBOL_GPL(knav_dma_device_ready);
static bool check_config(struct knav_dma_chan *chan, struct knav_dma_cfg *cfg)
{
if (!memcmp(&chan->cfg, cfg, sizeof(*cfg)))
return true;
else
return false;
}
static int chan_start(struct knav_dma_chan *chan,
struct knav_dma_cfg *cfg)
{
u32 v = 0;
spin_lock(&chan->lock);
if ((chan->direction == DMA_MEM_TO_DEV) && chan->reg_chan) {
if (cfg->u.tx.filt_pswords)
v |= DMA_TX_FILT_PSWORDS;
if (cfg->u.tx.filt_einfo)
v |= DMA_TX_FILT_EINFO;
writel_relaxed(v, &chan->reg_chan->mode);
writel_relaxed(DMA_ENABLE, &chan->reg_chan->control);
}
if (chan->reg_tx_sched)
writel_relaxed(cfg->u.tx.priority, &chan->reg_tx_sched->prio);
if (chan->reg_rx_flow) {
v = 0;
if (cfg->u.rx.einfo_present)
v |= CHAN_HAS_EPIB;
if (cfg->u.rx.psinfo_present)
v |= CHAN_HAS_PSINFO;
if (cfg->u.rx.err_mode == DMA_RETRY)
v |= CHAN_ERR_RETRY;
v |= (cfg->u.rx.desc_type & DESC_TYPE_MASK) << DESC_TYPE_SHIFT;
if (cfg->u.rx.psinfo_at_sop)
v |= CHAN_PSINFO_AT_SOP;
v |= (cfg->u.rx.sop_offset & CHAN_SOP_OFF_MASK)
<< CHAN_SOP_OFF_SHIFT;
v |= cfg->u.rx.dst_q & CHAN_QNUM_MASK;
writel_relaxed(v, &chan->reg_rx_flow->control);
writel_relaxed(0, &chan->reg_rx_flow->tags);
writel_relaxed(0, &chan->reg_rx_flow->tag_sel);
v = cfg->u.rx.fdq[0] << 16;
v |= cfg->u.rx.fdq[1] & CHAN_QNUM_MASK;
writel_relaxed(v, &chan->reg_rx_flow->fdq_sel[0]);
v = cfg->u.rx.fdq[2] << 16;
v |= cfg->u.rx.fdq[3] & CHAN_QNUM_MASK;
writel_relaxed(v, &chan->reg_rx_flow->fdq_sel[1]);
writel_relaxed(0, &chan->reg_rx_flow->thresh[0]);
writel_relaxed(0, &chan->reg_rx_flow->thresh[1]);
writel_relaxed(0, &chan->reg_rx_flow->thresh[2]);
}
/* Keep a copy of the cfg */
memcpy(&chan->cfg, cfg, sizeof(*cfg));
spin_unlock(&chan->lock);
return 0;
}
static int chan_teardown(struct knav_dma_chan *chan)
{
unsigned long end, value;
if (!chan->reg_chan)
return 0;
/* indicate teardown */
writel_relaxed(DMA_TEARDOWN, &chan->reg_chan->control);
/* wait for the dma to shut itself down */
end = jiffies + msecs_to_jiffies(DMA_TIMEOUT);
do {
value = readl_relaxed(&chan->reg_chan->control);
if ((value & DMA_ENABLE) == 0)
break;
} while (time_after(end, jiffies));
if (readl_relaxed(&chan->reg_chan->control) & DMA_ENABLE) {
dev_err(kdev->dev, "timeout waiting for teardown\n");
return -ETIMEDOUT;
}
return 0;
}
static void chan_stop(struct knav_dma_chan *chan)
{
spin_lock(&chan->lock);
if (chan->reg_rx_flow) {
/* first detach fdqs, starve out the flow */
writel_relaxed(0, &chan->reg_rx_flow->fdq_sel[0]);
writel_relaxed(0, &chan->reg_rx_flow->fdq_sel[1]);
writel_relaxed(0, &chan->reg_rx_flow->thresh[0]);
writel_relaxed(0, &chan->reg_rx_flow->thresh[1]);
writel_relaxed(0, &chan->reg_rx_flow->thresh[2]);
}
/* teardown the dma channel */
chan_teardown(chan);
/* then disconnect the completion side */
if (chan->reg_rx_flow) {
writel_relaxed(0, &chan->reg_rx_flow->control);
writel_relaxed(0, &chan->reg_rx_flow->tags);
writel_relaxed(0, &chan->reg_rx_flow->tag_sel);
}
memset(&chan->cfg, 0, sizeof(struct knav_dma_cfg));
spin_unlock(&chan->lock);
dev_dbg(kdev->dev, "channel stopped\n");
}
static void dma_hw_enable_all(struct knav_dma_device *dma)
{
int i;
for (i = 0; i < dma->max_tx_chan; i++) {
writel_relaxed(0, &dma->reg_tx_chan[i].mode);
writel_relaxed(DMA_ENABLE, &dma->reg_tx_chan[i].control);
}
}
static void knav_dma_hw_init(struct knav_dma_device *dma)
{
unsigned v;
int i;
spin_lock(&dma->lock);
v = dma->loopback ? DMA_LOOPBACK : 0;
writel_relaxed(v, &dma->reg_global->emulation_control);
v = readl_relaxed(&dma->reg_global->perf_control);
v |= ((dma->rx_timeout & DMA_RX_TIMEOUT_MASK) << DMA_RX_TIMEOUT_SHIFT);
writel_relaxed(v, &dma->reg_global->perf_control);
v = ((dma->tx_priority << DMA_TX_PRIO_SHIFT) |
(dma->rx_priority << DMA_RX_PRIO_SHIFT));
writel_relaxed(v, &dma->reg_global->priority_control);
/* Always enable all Rx channels. Rx paths are managed using flows */
for (i = 0; i < dma->max_rx_chan; i++)
writel_relaxed(DMA_ENABLE, &dma->reg_rx_chan[i].control);
for (i = 0; i < dma->logical_queue_managers; i++)
writel_relaxed(dma->qm_base_address[i],
&dma->reg_global->qm_base_address[i]);
spin_unlock(&dma->lock);
}
static void knav_dma_hw_destroy(struct knav_dma_device *dma)
{
int i;
unsigned v;
spin_lock(&dma->lock);
v = ~DMA_ENABLE & REG_MASK;
for (i = 0; i < dma->max_rx_chan; i++)
writel_relaxed(v, &dma->reg_rx_chan[i].control);
for (i = 0; i < dma->max_tx_chan; i++)
writel_relaxed(v, &dma->reg_tx_chan[i].control);
spin_unlock(&dma->lock);
}
static void dma_debug_show_channels(struct seq_file *s,
struct knav_dma_chan *chan)
{
int i;
seq_printf(s, "\t%s %d:\t",
((chan->direction == DMA_MEM_TO_DEV) ? "tx chan" : "rx flow"),
chan_number(chan));
if (chan->direction == DMA_MEM_TO_DEV) {
seq_printf(s, "einfo - %d, pswords - %d, priority - %d\n",
chan->cfg.u.tx.filt_einfo,
chan->cfg.u.tx.filt_pswords,
chan->cfg.u.tx.priority);
} else {
seq_printf(s, "einfo - %d, psinfo - %d, desc_type - %d\n",
chan->cfg.u.rx.einfo_present,
chan->cfg.u.rx.psinfo_present,
chan->cfg.u.rx.desc_type);
seq_printf(s, "\t\t\tdst_q: [%d], thresh: %d fdq: ",
chan->cfg.u.rx.dst_q,
chan->cfg.u.rx.thresh);
for (i = 0; i < KNAV_DMA_FDQ_PER_CHAN; i++)
seq_printf(s, "[%d]", chan->cfg.u.rx.fdq[i]);
seq_printf(s, "\n");
}
}
static void dma_debug_show_devices(struct seq_file *s,
struct knav_dma_device *dma)
{
struct knav_dma_chan *chan;
list_for_each_entry(chan, &dma->chan_list, list) {
if (atomic_read(&chan->ref_count))
dma_debug_show_channels(s, chan);
}
}
static int knav_dma_debug_show(struct seq_file *s, void *v)
{
struct knav_dma_device *dma;
list_for_each_entry(dma, &kdev->list, list) {
if (atomic_read(&dma->ref_count)) {
seq_printf(s, "%s : max_tx_chan: (%d), max_rx_flows: (%d)\n",
dma->name, dma->max_tx_chan, dma->max_rx_flow);
dma_debug_show_devices(s, dma);
}
}
return 0;
}
DEFINE_SHOW_ATTRIBUTE(knav_dma_debug);
static int of_channel_match_helper(struct device_node *np, const char *name,
const char **dma_instance)
{
struct of_phandle_args args;
struct device_node *dma_node;
int index;
dma_node = of_parse_phandle(np, "ti,navigator-dmas", 0);
if (!dma_node)
return -ENODEV;
*dma_instance = dma_node->name;
index = of_property_match_string(np, "ti,navigator-dma-names", name);
if (index < 0) {
dev_err(kdev->dev, "No 'ti,navigator-dma-names' property\n");
return -ENODEV;
}
if (of_parse_phandle_with_fixed_args(np, "ti,navigator-dmas",
1, index, &args)) {
dev_err(kdev->dev, "Missing the phandle args name %s\n", name);
return -ENODEV;
}
return args.args[0];
}
/**
* knav_dma_open_channel() - try to setup an exclusive slave channel
* @dev: pointer to client device structure
* @name: slave channel name
* @config: dma configuration parameters
*
* Return: Pointer to appropriate DMA channel on success or NULL on error.
*/
void *knav_dma_open_channel(struct device *dev, const char *name,
struct knav_dma_cfg *config)
{
struct knav_dma_device *dma = NULL, *iter1;
struct knav_dma_chan *chan = NULL, *iter2;
int chan_num = -1;
const char *instance;
if (!kdev) {
pr_err("keystone-navigator-dma driver not registered\n");
return NULL;
}
chan_num = of_channel_match_helper(dev->of_node, name, &instance);
if (chan_num < 0) {
dev_err(kdev->dev, "No DMA instance with name %s\n", name);
return NULL;
}
dev_dbg(kdev->dev, "initializing %s channel %d from DMA %s\n",
config->direction == DMA_MEM_TO_DEV ? "transmit" :
config->direction == DMA_DEV_TO_MEM ? "receive" :
"unknown", chan_num, instance);
if (config->direction != DMA_MEM_TO_DEV &&
config->direction != DMA_DEV_TO_MEM) {
dev_err(kdev->dev, "bad direction\n");
return NULL;
}
/* Look for correct dma instance */
list_for_each_entry(iter1, &kdev->list, list) {
if (!strcmp(iter1->name, instance)) {
dma = iter1;
break;
}
}
if (!dma) {
dev_err(kdev->dev, "No DMA instance with name %s\n", instance);
return NULL;
}
/* Look for correct dma channel from dma instance */
list_for_each_entry(iter2, &dma->chan_list, list) {
if (config->direction == DMA_MEM_TO_DEV) {
if (iter2->channel == chan_num) {
chan = iter2;
break;
}
} else {
if (iter2->flow == chan_num) {
chan = iter2;
break;
}
}
}
if (!chan) {
dev_err(kdev->dev, "channel %d is not in DMA %s\n",
chan_num, instance);
return NULL;
}
if (atomic_read(&chan->ref_count) >= 1) {
if (!check_config(chan, config)) {
dev_err(kdev->dev, "channel %d config miss-match\n",
chan_num);
return NULL;
}
}
if (atomic_inc_return(&chan->dma->ref_count) <= 1)
knav_dma_hw_init(chan->dma);
if (atomic_inc_return(&chan->ref_count) <= 1)
chan_start(chan, config);
dev_dbg(kdev->dev, "channel %d opened from DMA %s\n",
chan_num, instance);
return chan;
}
EXPORT_SYMBOL_GPL(knav_dma_open_channel);
/**
* knav_dma_close_channel() - Destroy a dma channel
*
* @channel: dma channel handle
*
*/
void knav_dma_close_channel(void *channel)
{
struct knav_dma_chan *chan = channel;
if (!kdev) {
pr_err("keystone-navigator-dma driver not registered\n");
return;
}
if (atomic_dec_return(&chan->ref_count) <= 0)
chan_stop(chan);
if (atomic_dec_return(&chan->dma->ref_count) <= 0)
knav_dma_hw_destroy(chan->dma);
dev_dbg(kdev->dev, "channel %d or flow %d closed from DMA %s\n",
chan->channel, chan->flow, chan->dma->name);
}
EXPORT_SYMBOL_GPL(knav_dma_close_channel);
static void __iomem *pktdma_get_regs(struct knav_dma_device *dma,
struct device_node *node,
unsigned index, resource_size_t *_size)
{
struct device *dev = kdev->dev;
struct resource res;
void __iomem *regs;
int ret;
ret = of_address_to_resource(node, index, &res);
if (ret) {
dev_err(dev, "Can't translate of node(%pOFn) address for index(%d)\n",
node, index);
return IOMEM_ERR_PTR(ret);
}
regs = devm_ioremap_resource(kdev->dev, &res);
if (IS_ERR(regs))
dev_err(dev, "Failed to map register base for index(%d) node(%pOFn)\n",
index, node);
if (_size)
*_size = resource_size(&res);
return regs;
}
static int pktdma_init_rx_chan(struct knav_dma_chan *chan, u32 flow)
{
struct knav_dma_device *dma = chan->dma;
chan->flow = flow;
chan->reg_rx_flow = dma->reg_rx_flow + flow;
chan->channel = DMA_INVALID_ID;
dev_dbg(kdev->dev, "rx flow(%d) (%p)\n", chan->flow, chan->reg_rx_flow);
return 0;
}
static int pktdma_init_tx_chan(struct knav_dma_chan *chan, u32 channel)
{
struct knav_dma_device *dma = chan->dma;
chan->channel = channel;
chan->reg_chan = dma->reg_tx_chan + channel;
chan->reg_tx_sched = dma->reg_tx_sched + channel;
chan->flow = DMA_INVALID_ID;
dev_dbg(kdev->dev, "tx channel(%d) (%p)\n", chan->channel, chan->reg_chan);
return 0;
}
static int pktdma_init_chan(struct knav_dma_device *dma,
enum dma_transfer_direction dir,
unsigned chan_num)
{
struct device *dev = kdev->dev;
struct knav_dma_chan *chan;
int ret = -EINVAL;
chan = devm_kzalloc(dev, sizeof(*chan), GFP_KERNEL);
if (!chan)
return -ENOMEM;
INIT_LIST_HEAD(&chan->list);
chan->dma = dma;
chan->direction = DMA_TRANS_NONE;
atomic_set(&chan->ref_count, 0);
spin_lock_init(&chan->lock);
if (dir == DMA_MEM_TO_DEV) {
chan->direction = dir;
ret = pktdma_init_tx_chan(chan, chan_num);
} else if (dir == DMA_DEV_TO_MEM) {
chan->direction = dir;
ret = pktdma_init_rx_chan(chan, chan_num);
} else {
dev_err(dev, "channel(%d) direction unknown\n", chan_num);
}
list_add_tail(&chan->list, &dma->chan_list);
return ret;
}
static int dma_init(struct device_node *cloud, struct device_node *dma_node)
{
unsigned max_tx_chan, max_rx_chan, max_rx_flow, max_tx_sched;
struct device_node *node = dma_node;
struct knav_dma_device *dma;
int ret, num_chan = 0;
resource_size_t size;
u32 timeout;
u32 i;
dma = devm_kzalloc(kdev->dev, sizeof(*dma), GFP_KERNEL);
if (!dma) {
dev_err(kdev->dev, "could not allocate driver mem\n");
return -ENOMEM;
}
INIT_LIST_HEAD(&dma->list);
INIT_LIST_HEAD(&dma->chan_list);
ret = of_property_read_variable_u32_array(cloud, "ti,navigator-cloud-address",
dma->qm_base_address, 1, DMA_MAX_QMS);
if (ret < 0) {
dev_err(kdev->dev, "invalid navigator cloud addresses\n");
return -ENODEV;
}
dma->logical_queue_managers = ret;
dma->reg_global = pktdma_get_regs(dma, node, 0, &size);
if (IS_ERR(dma->reg_global))
return PTR_ERR(dma->reg_global);
if (size < sizeof(struct reg_global)) {
dev_err(kdev->dev, "bad size %pa for global regs\n", &size);
return -ENODEV;
}
dma->reg_tx_chan = pktdma_get_regs(dma, node, 1, &size);
if (IS_ERR(dma->reg_tx_chan))
return PTR_ERR(dma->reg_tx_chan);
max_tx_chan = size / sizeof(struct reg_chan);
dma->reg_rx_chan = pktdma_get_regs(dma, node, 2, &size);
if (IS_ERR(dma->reg_rx_chan))
return PTR_ERR(dma->reg_rx_chan);
max_rx_chan = size / sizeof(struct reg_chan);
dma->reg_tx_sched = pktdma_get_regs(dma, node, 3, &size);
if (IS_ERR(dma->reg_tx_sched))
return PTR_ERR(dma->reg_tx_sched);
max_tx_sched = size / sizeof(struct reg_tx_sched);
dma->reg_rx_flow = pktdma_get_regs(dma, node, 4, &size);
if (IS_ERR(dma->reg_rx_flow))
return PTR_ERR(dma->reg_rx_flow);
max_rx_flow = size / sizeof(struct reg_rx_flow);
dma->rx_priority = DMA_PRIO_DEFAULT;
dma->tx_priority = DMA_PRIO_DEFAULT;
dma->enable_all = of_property_read_bool(node, "ti,enable-all");
dma->loopback = of_property_read_bool(node, "ti,loop-back");
ret = of_property_read_u32(node, "ti,rx-retry-timeout", &timeout);
if (ret < 0) {
dev_dbg(kdev->dev, "unspecified rx timeout using value %d\n",
DMA_RX_TIMEOUT_DEFAULT);
timeout = DMA_RX_TIMEOUT_DEFAULT;
}
dma->rx_timeout = timeout;
dma->max_rx_chan = max_rx_chan;
dma->max_rx_flow = max_rx_flow;
dma->max_tx_chan = min(max_tx_chan, max_tx_sched);
atomic_set(&dma->ref_count, 0);
strcpy(dma->name, node->name);
spin_lock_init(&dma->lock);
for (i = 0; i < dma->max_tx_chan; i++) {
if (pktdma_init_chan(dma, DMA_MEM_TO_DEV, i) >= 0)
num_chan++;
}
for (i = 0; i < dma->max_rx_flow; i++) {
if (pktdma_init_chan(dma, DMA_DEV_TO_MEM, i) >= 0)
num_chan++;
}
list_add_tail(&dma->list, &kdev->list);
/*
* For DSP software usecases or userpace transport software, setup all
* the DMA hardware resources.
*/
if (dma->enable_all) {
atomic_inc(&dma->ref_count);
knav_dma_hw_init(dma);
dma_hw_enable_all(dma);
}
dev_info(kdev->dev, "DMA %s registered %d logical channels, flows %d, tx chans: %d, rx chans: %d%s\n",
dma->name, num_chan, dma->max_rx_flow,
dma->max_tx_chan, dma->max_rx_chan,
dma->loopback ? ", loopback" : "");
return 0;
}
static int knav_dma_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *node = pdev->dev.of_node;
int ret = 0;
if (!node)
return dev_err_probe(dev, -EINVAL, "could not find device info\n");
kdev = devm_kzalloc(dev,
sizeof(struct knav_dma_pool_device), GFP_KERNEL);
if (!kdev)
return -ENOMEM;
kdev->dev = dev;
INIT_LIST_HEAD(&kdev->list);
pm_runtime_enable(kdev->dev);
ret = pm_runtime_resume_and_get(kdev->dev);
if (ret < 0) {
dev_err(dev, "unable to enable pktdma, err %d\n", ret);
goto err_pm_disable;
}
/* Initialise all packet dmas */
for_each_child_of_node_scoped(node, child) {
ret = dma_init(node, child);
if (ret) {
dev_err(dev, "init failed with %d\n", ret);
break;
}
}
if (list_empty(&kdev->list)) {
ret = dev_err_probe(dev, -ENODEV, "no valid dma instance\n");
goto err_put_sync;
}
debugfs_create_file("knav_dma", S_IFREG | S_IRUGO, NULL, NULL,
&knav_dma_debug_fops);
device_ready = true;
return ret;
err_put_sync:
pm_runtime_put_sync(kdev->dev);
err_pm_disable:
pm_runtime_disable(kdev->dev);
return ret;
}
static void knav_dma_remove(struct platform_device *pdev)
{
struct knav_dma_device *dma;
list_for_each_entry(dma, &kdev->list, list) {
if (atomic_dec_return(&dma->ref_count) == 0)
knav_dma_hw_destroy(dma);
}
pm_runtime_put_sync(&pdev->dev);
pm_runtime_disable(&pdev->dev);
}
static struct of_device_id of_match[] = {
{ .compatible = "ti,keystone-navigator-dma", },
{},
};
MODULE_DEVICE_TABLE(of, of_match);
static struct platform_driver knav_dma_driver = {
.probe = knav_dma_probe,
.remove = knav_dma_remove,
.driver = {
.name = "keystone-navigator-dma",
.of_match_table = of_match,
},
};
module_platform_driver(knav_dma_driver);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("TI Keystone Navigator Packet DMA driver");
MODULE_AUTHOR("Sandeep Nair <sandeep_n@ti.com>");
MODULE_AUTHOR("Santosh Shilimkar <santosh.shilimkar@ti.com>");
+387
View File
@@ -0,0 +1,387 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Keystone Navigator QMSS driver internal header
*
* Copyright (C) 2014 Texas Instruments Incorporated - http://www.ti.com
* Author: Sandeep Nair <sandeep_n@ti.com>
* Cyril Chemparathy <cyril@ti.com>
* Santosh Shilimkar <santosh.shilimkar@ti.com>
*/
#ifndef __KNAV_QMSS_H__
#define __KNAV_QMSS_H__
#include <linux/percpu.h>
#define THRESH_GTE BIT(7)
#define THRESH_LT 0
#define PDSP_CTRL_PC_MASK 0xffff0000
#define PDSP_CTRL_SOFT_RESET BIT(0)
#define PDSP_CTRL_ENABLE BIT(1)
#define PDSP_CTRL_RUNNING BIT(15)
#define ACC_MAX_CHANNEL 48
#define ACC_DEFAULT_PERIOD 25 /* usecs */
#define ACC_CHANNEL_INT_BASE 2
#define ACC_LIST_ENTRY_TYPE 1
#define ACC_LIST_ENTRY_WORDS (1 << ACC_LIST_ENTRY_TYPE)
#define ACC_LIST_ENTRY_QUEUE_IDX 0
#define ACC_LIST_ENTRY_DESC_IDX (ACC_LIST_ENTRY_WORDS - 1)
#define ACC_CMD_DISABLE_CHANNEL 0x80
#define ACC_CMD_ENABLE_CHANNEL 0x81
#define ACC_CFG_MULTI_QUEUE BIT(21)
#define ACC_INTD_OFFSET_EOI (0x0010)
#define ACC_INTD_OFFSET_COUNT(ch) (0x0300 + 4 * (ch))
#define ACC_INTD_OFFSET_STATUS(ch) (0x0200 + 4 * ((ch) / 32))
#define RANGE_MAX_IRQS 64
#define ACC_DESCS_MAX SZ_1K
#define ACC_DESCS_MASK (ACC_DESCS_MAX - 1)
#define DESC_SIZE_MASK 0xful
#define DESC_PTR_MASK (~DESC_SIZE_MASK)
#define KNAV_NAME_SIZE 32
enum knav_acc_result {
ACC_RET_IDLE,
ACC_RET_SUCCESS,
ACC_RET_INVALID_COMMAND,
ACC_RET_INVALID_CHANNEL,
ACC_RET_INACTIVE_CHANNEL,
ACC_RET_ACTIVE_CHANNEL,
ACC_RET_INVALID_QUEUE,
ACC_RET_INVALID_RET,
};
struct knav_reg_config {
u32 revision;
u32 __pad1;
u32 divert;
u32 link_ram_base0;
u32 link_ram_size0;
u32 link_ram_base1;
u32 __pad2[2];
u32 starvation[];
};
struct knav_reg_region {
u32 base;
u32 start_index;
u32 size_count;
u32 __pad;
};
struct knav_reg_pdsp_regs {
u32 control;
u32 status;
u32 cycle_count;
u32 stall_count;
};
struct knav_reg_acc_command {
u32 command;
u32 queue_mask;
u32 list_dma;
u32 queue_num;
u32 timer_config;
};
struct knav_link_ram_block {
dma_addr_t dma;
void *virt;
size_t size;
};
struct knav_acc_info {
u32 pdsp_id;
u32 start_channel;
u32 list_entries;
u32 pacing_mode;
u32 timer_count;
int mem_size;
int list_size;
struct knav_pdsp_info *pdsp;
};
struct knav_acc_channel {
u32 channel;
u32 list_index;
u32 open_mask;
u32 *list_cpu[2];
dma_addr_t list_dma[2];
char name[KNAV_NAME_SIZE];
atomic_t retrigger_count;
};
struct knav_pdsp_info {
const char *name;
struct knav_reg_pdsp_regs __iomem *regs;
union {
u32 __iomem *command;
struct knav_reg_acc_command __iomem *acc_command;
u32 __iomem *qos_command;
};
void __iomem *intd;
u32 __iomem *iram;
u32 id;
struct list_head list;
bool loaded;
bool started;
};
struct knav_qmgr_info {
unsigned start_queue;
unsigned num_queues;
struct knav_reg_config __iomem *reg_config;
struct knav_reg_region __iomem *reg_region;
struct knav_reg_queue __iomem *reg_push, *reg_pop, *reg_peek;
void __iomem *reg_status;
struct list_head list;
};
#define KNAV_NUM_LINKRAM 2
/**
* struct knav_queue_stats: queue statistics
* pushes: number of push operations
* pops: number of pop operations
* push_errors: number of push errors
* pop_errors: number of pop errors
* notifies: notifier counts
*/
struct knav_queue_stats {
unsigned int pushes;
unsigned int pops;
unsigned int push_errors;
unsigned int pop_errors;
unsigned int notifies;
};
/**
* struct knav_reg_queue: queue registers
* @entry_count: valid entries in the queue
* @byte_count: total byte count in thhe queue
* @packet_size: packet size for the queue
* @ptr_size_thresh: packet pointer size threshold
*/
struct knav_reg_queue {
u32 entry_count;
u32 byte_count;
u32 packet_size;
u32 ptr_size_thresh;
};
/**
* struct knav_region: qmss region info
* @dma_start, dma_end: start and end dma address
* @virt_start, virt_end: start and end virtual address
* @desc_size: descriptor size
* @used_desc: consumed descriptors
* @id: region number
* @num_desc: total descriptors
* @link_index: index of the first descriptor
* @name: region name
* @list: instance in the device's region list
* @pools: list of descriptor pools in the region
*/
struct knav_region {
dma_addr_t dma_start, dma_end;
void *virt_start, *virt_end;
unsigned desc_size;
unsigned used_desc;
unsigned id;
unsigned num_desc;
unsigned link_index;
const char *name;
struct list_head list;
struct list_head pools;
};
/**
* struct knav_pool: qmss pools
* @dev: device pointer
* @region: qmss region info
* @queue: queue registers
* @kdev: qmss device pointer
* @region_offset: offset from the base
* @num_desc: total descriptors
* @desc_size: descriptor size
* @region_id: region number
* @name: pool name
* @list: list head
* @region_inst: instance in the region's pool list
*/
struct knav_pool {
struct device *dev;
struct knav_region *region;
struct knav_queue *queue;
struct knav_device *kdev;
int region_offset;
int num_desc;
int desc_size;
int region_id;
const char *name;
struct list_head list;
struct list_head region_inst;
};
/**
* struct knav_queue_inst: qmss queue instance properties
* @descs: descriptor pointer
* @desc_head, desc_tail, desc_count: descriptor counters
* @acc: accumulator channel pointer
* @kdev: qmss device pointer
* @range: range info
* @qmgr: queue manager info
* @id: queue instance id
* @irq_num: irq line number
* @notify_needed: notifier needed based on queue type
* @num_notifiers: total notifiers
* @handles: list head
* @name: queue instance name
* @irq_name: irq line name
*/
struct knav_queue_inst {
u32 *descs;
atomic_t desc_head, desc_tail, desc_count;
struct knav_acc_channel *acc;
struct knav_device *kdev;
struct knav_range_info *range;
struct knav_qmgr_info *qmgr;
u32 id;
int irq_num;
int notify_needed;
atomic_t num_notifiers;
struct list_head handles;
const char *name;
const char *irq_name;
};
/**
* struct knav_queue: qmss queue properties
* @reg_push, reg_pop, reg_peek: push, pop queue registers
* @inst: qmss queue instance properties
* @notifier_fn: notifier function
* @notifier_fn_arg: notifier function argument
* @notifier_enabled: notier enabled for a give queue
* @rcu: rcu head
* @flags: queue flags
* @list: list head
*/
struct knav_queue {
struct knav_reg_queue __iomem *reg_push, *reg_pop, *reg_peek;
struct knav_queue_inst *inst;
struct knav_queue_stats __percpu *stats;
knav_queue_notify_fn notifier_fn;
void *notifier_fn_arg;
atomic_t notifier_enabled;
struct rcu_head rcu;
unsigned flags;
struct list_head list;
};
enum qmss_version {
QMSS,
QMSS_66AK2G,
};
struct knav_device {
struct device *dev;
unsigned base_id;
unsigned num_queues;
unsigned num_queues_in_use;
unsigned inst_shift;
struct knav_link_ram_block link_rams[KNAV_NUM_LINKRAM];
void *instances;
struct list_head regions;
struct list_head queue_ranges;
struct list_head pools;
struct list_head pdsps;
struct list_head qmgrs;
enum qmss_version version;
};
struct knav_range_ops {
int (*init_range)(struct knav_range_info *range);
int (*free_range)(struct knav_range_info *range);
int (*init_queue)(struct knav_range_info *range,
struct knav_queue_inst *inst);
int (*open_queue)(struct knav_range_info *range,
struct knav_queue_inst *inst, unsigned flags);
int (*close_queue)(struct knav_range_info *range,
struct knav_queue_inst *inst);
int (*set_notify)(struct knav_range_info *range,
struct knav_queue_inst *inst, bool enabled);
};
struct knav_irq_info {
int irq;
struct cpumask *cpu_mask;
};
struct knav_range_info {
const char *name;
struct knav_device *kdev;
unsigned queue_base;
unsigned num_queues;
void *queue_base_inst;
unsigned flags;
struct list_head list;
const struct knav_range_ops *ops;
struct knav_acc_info acc_info;
struct knav_acc_channel *acc;
unsigned num_irqs;
struct knav_irq_info irqs[RANGE_MAX_IRQS];
};
#define RANGE_RESERVED BIT(0)
#define RANGE_HAS_IRQ BIT(1)
#define RANGE_HAS_ACCUMULATOR BIT(2)
#define RANGE_MULTI_QUEUE BIT(3)
#define for_each_region(kdev, region) \
list_for_each_entry(region, &kdev->regions, list)
#define first_region(kdev) \
list_first_entry_or_null(&kdev->regions, \
struct knav_region, list)
#define for_each_queue_range(kdev, range) \
list_for_each_entry(range, &kdev->queue_ranges, list)
#define first_queue_range(kdev) \
list_first_entry_or_null(&kdev->queue_ranges, \
struct knav_range_info, list)
#define for_each_pool(kdev, pool) \
list_for_each_entry(pool, &kdev->pools, list)
#define for_each_pdsp(kdev, pdsp) \
list_for_each_entry(pdsp, &kdev->pdsps, list)
#define for_each_qmgr(kdev, qmgr) \
list_for_each_entry(qmgr, &kdev->qmgrs, list)
static inline struct knav_pdsp_info *
knav_find_pdsp(struct knav_device *kdev, unsigned pdsp_id)
{
struct knav_pdsp_info *pdsp;
for_each_pdsp(kdev, pdsp)
if (pdsp_id == pdsp->id)
return pdsp;
return NULL;
}
extern int knav_init_acc_range(struct knav_device *kdev,
struct device_node *node,
struct knav_range_info *range);
extern void knav_queue_notify(struct knav_queue_inst *inst);
#endif /* __KNAV_QMSS_H__ */
+584
View File
@@ -0,0 +1,584 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Keystone accumulator queue manager
*
* Copyright (C) 2014 Texas Instruments Incorporated - http://www.ti.com
* Author: Sandeep Nair <sandeep_n@ti.com>
* Cyril Chemparathy <cyril@ti.com>
* Santosh Shilimkar <santosh.shilimkar@ti.com>
*/
#include <linux/dma-mapping.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/soc/ti/knav_qmss.h>
#include "knav_qmss.h"
#define knav_range_offset_to_inst(kdev, range, q) \
(range->queue_base_inst + (q << kdev->inst_shift))
static void __knav_acc_notify(struct knav_range_info *range,
struct knav_acc_channel *acc)
{
struct knav_device *kdev = range->kdev;
struct knav_queue_inst *inst;
int range_base, queue;
range_base = kdev->base_id + range->queue_base;
if (range->flags & RANGE_MULTI_QUEUE) {
for (queue = 0; queue < range->num_queues; queue++) {
inst = knav_range_offset_to_inst(kdev, range,
queue);
if (inst->notify_needed) {
inst->notify_needed = 0;
dev_dbg(kdev->dev, "acc-irq: notifying %d\n",
range_base + queue);
knav_queue_notify(inst);
}
}
} else {
queue = acc->channel - range->acc_info.start_channel;
inst = knav_range_offset_to_inst(kdev, range, queue);
dev_dbg(kdev->dev, "acc-irq: notifying %d\n",
range_base + queue);
knav_queue_notify(inst);
}
}
static int knav_acc_set_notify(struct knav_range_info *range,
struct knav_queue_inst *kq,
bool enabled)
{
struct knav_pdsp_info *pdsp = range->acc_info.pdsp;
struct knav_device *kdev = range->kdev;
u32 mask, offset;
/*
* when enabling, we need to re-trigger an interrupt if we
* have descriptors pending
*/
if (!enabled || atomic_read(&kq->desc_count) <= 0)
return 0;
kq->notify_needed = 1;
atomic_inc(&kq->acc->retrigger_count);
mask = BIT(kq->acc->channel % 32);
offset = ACC_INTD_OFFSET_STATUS(kq->acc->channel);
dev_dbg(kdev->dev, "setup-notify: re-triggering irq for %s\n",
kq->acc->name);
writel_relaxed(mask, pdsp->intd + offset);
return 0;
}
static irqreturn_t knav_acc_int_handler(int irq, void *_instdata)
{
struct knav_acc_channel *acc;
struct knav_queue_inst *kq = NULL;
struct knav_range_info *range;
struct knav_pdsp_info *pdsp;
struct knav_acc_info *info;
struct knav_device *kdev;
u32 *list, *list_cpu, val, idx, notifies;
int range_base, channel, queue = 0;
dma_addr_t list_dma;
range = _instdata;
info = &range->acc_info;
kdev = range->kdev;
pdsp = range->acc_info.pdsp;
acc = range->acc;
range_base = kdev->base_id + range->queue_base;
if ((range->flags & RANGE_MULTI_QUEUE) == 0) {
for (queue = 0; queue < range->num_irqs; queue++)
if (range->irqs[queue].irq == irq)
break;
kq = knav_range_offset_to_inst(kdev, range, queue);
acc += queue;
}
channel = acc->channel;
list_dma = acc->list_dma[acc->list_index];
list_cpu = acc->list_cpu[acc->list_index];
dev_dbg(kdev->dev, "acc-irq: channel %d, list %d, virt %p, dma %pad\n",
channel, acc->list_index, list_cpu, &list_dma);
if (atomic_read(&acc->retrigger_count)) {
atomic_dec(&acc->retrigger_count);
__knav_acc_notify(range, acc);
writel_relaxed(1, pdsp->intd + ACC_INTD_OFFSET_COUNT(channel));
/* ack the interrupt */
writel_relaxed(ACC_CHANNEL_INT_BASE + channel,
pdsp->intd + ACC_INTD_OFFSET_EOI);
return IRQ_HANDLED;
}
notifies = readl_relaxed(pdsp->intd + ACC_INTD_OFFSET_COUNT(channel));
WARN_ON(!notifies);
dma_sync_single_for_cpu(kdev->dev, list_dma, info->list_size,
DMA_FROM_DEVICE);
for (list = list_cpu; list < list_cpu + (info->list_size / sizeof(u32));
list += ACC_LIST_ENTRY_WORDS) {
if (ACC_LIST_ENTRY_WORDS == 1) {
dev_dbg(kdev->dev,
"acc-irq: list %d, entry @%p, %08x\n",
acc->list_index, list, list[0]);
} else if (ACC_LIST_ENTRY_WORDS == 2) {
dev_dbg(kdev->dev,
"acc-irq: list %d, entry @%p, %08x %08x\n",
acc->list_index, list, list[0], list[1]);
} else if (ACC_LIST_ENTRY_WORDS == 4) {
dev_dbg(kdev->dev,
"acc-irq: list %d, entry @%p, %08x %08x %08x %08x\n",
acc->list_index, list, list[0], list[1],
list[2], list[3]);
}
val = list[ACC_LIST_ENTRY_DESC_IDX];
if (!val)
break;
if (range->flags & RANGE_MULTI_QUEUE) {
queue = list[ACC_LIST_ENTRY_QUEUE_IDX] >> 16;
if (queue < range_base ||
queue >= range_base + range->num_queues) {
dev_err(kdev->dev,
"bad queue %d, expecting %d-%d\n",
queue, range_base,
range_base + range->num_queues);
break;
}
queue -= range_base;
kq = knav_range_offset_to_inst(kdev, range,
queue);
}
if (atomic_inc_return(&kq->desc_count) >= ACC_DESCS_MAX) {
atomic_dec(&kq->desc_count);
dev_err(kdev->dev,
"acc-irq: queue %d full, entry dropped\n",
queue + range_base);
continue;
}
idx = atomic_inc_return(&kq->desc_tail) & ACC_DESCS_MASK;
kq->descs[idx] = val;
kq->notify_needed = 1;
dev_dbg(kdev->dev, "acc-irq: enqueue %08x at %d, queue %d\n",
val, idx, queue + range_base);
}
__knav_acc_notify(range, acc);
memset(list_cpu, 0, info->list_size);
dma_sync_single_for_device(kdev->dev, list_dma, info->list_size,
DMA_TO_DEVICE);
/* flip to the other list */
acc->list_index ^= 1;
/* reset the interrupt counter */
writel_relaxed(1, pdsp->intd + ACC_INTD_OFFSET_COUNT(channel));
/* ack the interrupt */
writel_relaxed(ACC_CHANNEL_INT_BASE + channel,
pdsp->intd + ACC_INTD_OFFSET_EOI);
return IRQ_HANDLED;
}
static int knav_range_setup_acc_irq(struct knav_range_info *range,
int queue, bool enabled)
{
struct knav_device *kdev = range->kdev;
struct knav_acc_channel *acc;
struct cpumask *cpu_mask;
int ret = 0, irq;
u32 old, new;
if (range->flags & RANGE_MULTI_QUEUE) {
acc = range->acc;
irq = range->irqs[0].irq;
cpu_mask = range->irqs[0].cpu_mask;
} else {
acc = range->acc + queue;
irq = range->irqs[queue].irq;
cpu_mask = range->irqs[queue].cpu_mask;
}
old = acc->open_mask;
if (enabled)
new = old | BIT(queue);
else
new = old & ~BIT(queue);
acc->open_mask = new;
dev_dbg(kdev->dev,
"setup-acc-irq: open mask old %08x, new %08x, channel %s\n",
old, new, acc->name);
if (likely(new == old))
return 0;
if (new && !old) {
dev_dbg(kdev->dev,
"setup-acc-irq: requesting %s for channel %s\n",
acc->name, acc->name);
ret = request_irq(irq, knav_acc_int_handler, 0, acc->name,
range);
if (!ret && cpu_mask) {
ret = irq_set_affinity_hint(irq, cpu_mask);
if (ret) {
dev_warn(range->kdev->dev,
"Failed to set IRQ affinity\n");
return ret;
}
}
}
if (old && !new) {
dev_dbg(kdev->dev, "setup-acc-irq: freeing %s for channel %s\n",
acc->name, acc->name);
ret = irq_set_affinity_hint(irq, NULL);
if (ret)
dev_warn(range->kdev->dev,
"Failed to set IRQ affinity\n");
free_irq(irq, range);
}
return ret;
}
static const char *knav_acc_result_str(enum knav_acc_result result)
{
static const char * const result_str[] = {
[ACC_RET_IDLE] = "idle",
[ACC_RET_SUCCESS] = "success",
[ACC_RET_INVALID_COMMAND] = "invalid command",
[ACC_RET_INVALID_CHANNEL] = "invalid channel",
[ACC_RET_INACTIVE_CHANNEL] = "inactive channel",
[ACC_RET_ACTIVE_CHANNEL] = "active channel",
[ACC_RET_INVALID_QUEUE] = "invalid queue",
[ACC_RET_INVALID_RET] = "invalid return code",
};
if (result >= ARRAY_SIZE(result_str))
return result_str[ACC_RET_INVALID_RET];
else
return result_str[result];
}
static enum knav_acc_result
knav_acc_write(struct knav_device *kdev, struct knav_pdsp_info *pdsp,
struct knav_reg_acc_command *cmd)
{
u32 result;
dev_dbg(kdev->dev, "acc command %08x %08x %08x %08x %08x\n",
cmd->command, cmd->queue_mask, cmd->list_dma,
cmd->queue_num, cmd->timer_config);
writel_relaxed(cmd->timer_config, &pdsp->acc_command->timer_config);
writel_relaxed(cmd->queue_num, &pdsp->acc_command->queue_num);
writel_relaxed(cmd->list_dma, &pdsp->acc_command->list_dma);
writel_relaxed(cmd->queue_mask, &pdsp->acc_command->queue_mask);
writel_relaxed(cmd->command, &pdsp->acc_command->command);
/* wait for the command to clear */
do {
result = readl_relaxed(&pdsp->acc_command->command);
} while ((result >> 8) & 0xff);
return (result >> 24) & 0xff;
}
static void knav_acc_setup_cmd(struct knav_device *kdev,
struct knav_range_info *range,
struct knav_reg_acc_command *cmd,
int queue)
{
struct knav_acc_info *info = &range->acc_info;
struct knav_acc_channel *acc;
int queue_base;
u32 queue_mask;
if (range->flags & RANGE_MULTI_QUEUE) {
acc = range->acc;
queue_base = range->queue_base;
queue_mask = BIT(range->num_queues) - 1;
} else {
acc = range->acc + queue;
queue_base = range->queue_base + queue;
queue_mask = 0;
}
memset(cmd, 0, sizeof(*cmd));
cmd->command = acc->channel;
cmd->queue_mask = queue_mask;
cmd->list_dma = (u32)acc->list_dma[0];
cmd->queue_num = info->list_entries << 16;
cmd->queue_num |= queue_base;
cmd->timer_config = ACC_LIST_ENTRY_TYPE << 18;
if (range->flags & RANGE_MULTI_QUEUE)
cmd->timer_config |= ACC_CFG_MULTI_QUEUE;
cmd->timer_config |= info->pacing_mode << 16;
cmd->timer_config |= info->timer_count;
}
static void knav_acc_stop(struct knav_device *kdev,
struct knav_range_info *range,
int queue)
{
struct knav_reg_acc_command cmd;
struct knav_acc_channel *acc;
enum knav_acc_result result;
acc = range->acc + queue;
knav_acc_setup_cmd(kdev, range, &cmd, queue);
cmd.command |= ACC_CMD_DISABLE_CHANNEL << 8;
result = knav_acc_write(kdev, range->acc_info.pdsp, &cmd);
dev_dbg(kdev->dev, "stopped acc channel %s, result %s\n",
acc->name, knav_acc_result_str(result));
}
static enum knav_acc_result knav_acc_start(struct knav_device *kdev,
struct knav_range_info *range,
int queue)
{
struct knav_reg_acc_command cmd;
struct knav_acc_channel *acc;
enum knav_acc_result result;
acc = range->acc + queue;
knav_acc_setup_cmd(kdev, range, &cmd, queue);
cmd.command |= ACC_CMD_ENABLE_CHANNEL << 8;
result = knav_acc_write(kdev, range->acc_info.pdsp, &cmd);
dev_dbg(kdev->dev, "started acc channel %s, result %s\n",
acc->name, knav_acc_result_str(result));
return result;
}
static int knav_acc_init_range(struct knav_range_info *range)
{
struct knav_device *kdev = range->kdev;
struct knav_acc_channel *acc;
enum knav_acc_result result;
int queue;
for (queue = 0; queue < range->num_queues; queue++) {
acc = range->acc + queue;
knav_acc_stop(kdev, range, queue);
acc->list_index = 0;
result = knav_acc_start(kdev, range, queue);
if (result != ACC_RET_SUCCESS)
return -EIO;
if (range->flags & RANGE_MULTI_QUEUE)
return 0;
}
return 0;
}
static int knav_acc_init_queue(struct knav_range_info *range,
struct knav_queue_inst *kq)
{
unsigned id = kq->id - range->queue_base;
kq->descs = devm_kcalloc(range->kdev->dev,
ACC_DESCS_MAX, sizeof(u32), GFP_KERNEL);
if (!kq->descs)
return -ENOMEM;
kq->acc = range->acc;
if ((range->flags & RANGE_MULTI_QUEUE) == 0)
kq->acc += id;
return 0;
}
static int knav_acc_open_queue(struct knav_range_info *range,
struct knav_queue_inst *inst, unsigned flags)
{
unsigned id = inst->id - range->queue_base;
return knav_range_setup_acc_irq(range, id, true);
}
static int knav_acc_close_queue(struct knav_range_info *range,
struct knav_queue_inst *inst)
{
unsigned id = inst->id - range->queue_base;
return knav_range_setup_acc_irq(range, id, false);
}
static int knav_acc_free_range(struct knav_range_info *range)
{
struct knav_device *kdev = range->kdev;
struct knav_acc_channel *acc;
struct knav_acc_info *info;
int channel, channels;
info = &range->acc_info;
if (range->flags & RANGE_MULTI_QUEUE)
channels = 1;
else
channels = range->num_queues;
for (channel = 0; channel < channels; channel++) {
acc = range->acc + channel;
if (!acc->list_cpu[0])
continue;
dma_unmap_single(kdev->dev, acc->list_dma[0],
info->mem_size, DMA_BIDIRECTIONAL);
free_pages_exact(acc->list_cpu[0], info->mem_size);
}
devm_kfree(range->kdev->dev, range->acc);
return 0;
}
static const struct knav_range_ops knav_acc_range_ops = {
.set_notify = knav_acc_set_notify,
.init_queue = knav_acc_init_queue,
.open_queue = knav_acc_open_queue,
.close_queue = knav_acc_close_queue,
.init_range = knav_acc_init_range,
.free_range = knav_acc_free_range,
};
/**
* knav_init_acc_range: Initialise accumulator ranges
*
* @kdev: qmss device
* @node: device node
* @range: qmms range information
*
* Return: 0 on success, errno otherwise.
*/
int knav_init_acc_range(struct knav_device *kdev,
struct device_node *node,
struct knav_range_info *range)
{
struct knav_acc_channel *acc;
struct knav_pdsp_info *pdsp;
struct knav_acc_info *info;
int ret, channel, channels;
int list_size, mem_size;
dma_addr_t list_dma;
void *list_mem;
u32 config[5];
range->flags |= RANGE_HAS_ACCUMULATOR;
info = &range->acc_info;
ret = of_property_read_u32_array(node, "accumulator", config, 5);
if (ret)
return ret;
info->pdsp_id = config[0];
info->start_channel = config[1];
info->list_entries = config[2];
info->pacing_mode = config[3];
info->timer_count = config[4] / ACC_DEFAULT_PERIOD;
if (info->start_channel > ACC_MAX_CHANNEL) {
dev_err(kdev->dev, "channel %d invalid for range %s\n",
info->start_channel, range->name);
return -EINVAL;
}
if (info->pacing_mode > 3) {
dev_err(kdev->dev, "pacing mode %d invalid for range %s\n",
info->pacing_mode, range->name);
return -EINVAL;
}
pdsp = knav_find_pdsp(kdev, info->pdsp_id);
if (!pdsp) {
dev_err(kdev->dev, "pdsp id %d not found for range %s\n",
info->pdsp_id, range->name);
return -EINVAL;
}
if (!pdsp->started) {
dev_err(kdev->dev, "pdsp id %d not started for range %s\n",
info->pdsp_id, range->name);
return -ENODEV;
}
info->pdsp = pdsp;
channels = range->num_queues;
if (of_property_read_bool(node, "multi-queue")) {
range->flags |= RANGE_MULTI_QUEUE;
channels = 1;
if (range->queue_base & (32 - 1)) {
dev_err(kdev->dev,
"misaligned multi-queue accumulator range %s\n",
range->name);
return -EINVAL;
}
if (range->num_queues > 32) {
dev_err(kdev->dev,
"too many queues in accumulator range %s\n",
range->name);
return -EINVAL;
}
}
/* figure out list size */
list_size = info->list_entries;
list_size *= ACC_LIST_ENTRY_WORDS * sizeof(u32);
info->list_size = list_size;
mem_size = PAGE_ALIGN(list_size * 2);
info->mem_size = mem_size;
range->acc = devm_kcalloc(kdev->dev, channels, sizeof(*range->acc),
GFP_KERNEL);
if (!range->acc)
return -ENOMEM;
for (channel = 0; channel < channels; channel++) {
acc = range->acc + channel;
acc->channel = info->start_channel + channel;
/* allocate memory for the two lists */
list_mem = alloc_pages_exact(mem_size, GFP_KERNEL | GFP_DMA);
if (!list_mem)
return -ENOMEM;
list_dma = dma_map_single(kdev->dev, list_mem, mem_size,
DMA_BIDIRECTIONAL);
if (dma_mapping_error(kdev->dev, list_dma)) {
free_pages_exact(list_mem, mem_size);
return -ENOMEM;
}
memset(list_mem, 0, mem_size);
dma_sync_single_for_device(kdev->dev, list_dma, mem_size,
DMA_TO_DEVICE);
scnprintf(acc->name, sizeof(acc->name), "hwqueue-acc-%d",
acc->channel);
acc->list_cpu[0] = list_mem;
acc->list_cpu[1] = list_mem + list_size;
acc->list_dma[0] = list_dma;
acc->list_dma[1] = list_dma + list_size;
dev_dbg(kdev->dev, "%s: channel %d, dma %pad, virt %8p\n",
acc->name, acc->channel, &list_dma, list_mem);
}
range->ops = &knav_acc_range_ops;
return 0;
}
EXPORT_SYMBOL_GPL(knav_init_acc_range);
File diff suppressed because it is too large Load Diff
+600
View File
@@ -0,0 +1,600 @@
// SPDX-License-Identifier: GPL-2.0
/*
* AM33XX Power Management Routines
*
* Copyright (C) 2012-2018 Texas Instruments Incorporated - http://www.ti.com/
* Vaibhav Bedia, Dave Gerlach
*/
#include <linux/clk.h>
#include <linux/cpu.h>
#include <linux/err.h>
#include <linux/genalloc.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/nvmem-consumer.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/platform_data/pm33xx.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/rtc.h>
#include <linux/rtc/rtc-omap.h>
#include <linux/sizes.h>
#include <linux/sram.h>
#include <linux/suspend.h>
#include <linux/ti-emif-sram.h>
#include <linux/wkup_m3_ipc.h>
#include <asm/proc-fns.h>
#include <asm/suspend.h>
#include <asm/system_misc.h>
#define AMX3_PM_SRAM_SYMBOL_OFFSET(sym) ((unsigned long)(sym) - \
(unsigned long)pm_sram->do_wfi)
#define RTC_SCRATCH_RESUME_REG 0
#define RTC_SCRATCH_MAGIC_REG 1
#define RTC_REG_BOOT_MAGIC 0x8cd0 /* RTC */
#define GIC_INT_SET_PENDING_BASE 0x200
#define AM43XX_GIC_DIST_BASE 0x48241000
static void __iomem *rtc_base_virt;
static struct clk *rtc_fck;
static u32 rtc_magic_val;
static int (*am33xx_do_wfi_sram)(unsigned long unused);
static phys_addr_t am33xx_do_wfi_sram_phys;
static struct gen_pool *sram_pool, *sram_pool_data;
static unsigned long ocmcram_location, ocmcram_location_data;
static struct rtc_device *omap_rtc;
static void __iomem *gic_dist_base;
static struct am33xx_pm_platform_data *pm_ops;
static struct am33xx_pm_sram_addr *pm_sram;
static struct device *pm33xx_dev;
static struct wkup_m3_ipc *m3_ipc;
#ifdef CONFIG_SUSPEND
static int rtc_only_idle;
static int retrigger_irq;
static unsigned long suspend_wfi_flags;
static struct wkup_m3_wakeup_src wakeup_src = {.irq_nr = 0,
.src = "Unknown",
};
static struct wkup_m3_wakeup_src rtc_alarm_wakeup = {
.irq_nr = 108, .src = "RTC Alarm",
};
static struct wkup_m3_wakeup_src rtc_ext_wakeup = {
.irq_nr = 0, .src = "Ext wakeup",
};
#endif
static u32 sram_suspend_address(unsigned long addr)
{
return ((unsigned long)am33xx_do_wfi_sram +
AMX3_PM_SRAM_SYMBOL_OFFSET(addr));
}
static int am33xx_push_sram_idle(void)
{
struct am33xx_pm_ro_sram_data ro_sram_data;
int ret;
u32 table_addr, ro_data_addr;
void *copy_addr;
ro_sram_data.amx3_pm_sram_data_virt = ocmcram_location_data;
ro_sram_data.amx3_pm_sram_data_phys =
gen_pool_virt_to_phys(sram_pool_data, ocmcram_location_data);
ro_sram_data.rtc_base_virt = rtc_base_virt;
/* Save physical address to calculate resume offset during pm init */
am33xx_do_wfi_sram_phys = gen_pool_virt_to_phys(sram_pool,
ocmcram_location);
am33xx_do_wfi_sram = sram_exec_copy(sram_pool, (void *)ocmcram_location,
pm_sram->do_wfi,
*pm_sram->do_wfi_sz);
if (!am33xx_do_wfi_sram) {
dev_err(pm33xx_dev,
"PM: %s: am33xx_do_wfi copy to sram failed\n",
__func__);
return -ENODEV;
}
table_addr =
sram_suspend_address((unsigned long)pm_sram->emif_sram_table);
ret = ti_emif_copy_pm_function_table(sram_pool, (void *)table_addr);
if (ret) {
dev_dbg(pm33xx_dev,
"PM: %s: EMIF function copy failed\n", __func__);
return -EPROBE_DEFER;
}
ro_data_addr =
sram_suspend_address((unsigned long)pm_sram->ro_sram_data);
copy_addr = sram_exec_copy(sram_pool, (void *)ro_data_addr,
&ro_sram_data,
sizeof(ro_sram_data));
if (!copy_addr) {
dev_err(pm33xx_dev,
"PM: %s: ro_sram_data copy to sram failed\n",
__func__);
return -ENODEV;
}
return 0;
}
static int am33xx_do_sram_idle(u32 wfi_flags)
{
if (!m3_ipc || !pm_ops)
return 0;
if (wfi_flags & WFI_FLAG_WAKE_M3)
m3_ipc->ops->prepare_low_power(m3_ipc, WKUP_M3_IDLE);
return pm_ops->cpu_suspend(am33xx_do_wfi_sram, wfi_flags);
}
static int am43xx_map_gic(void)
{
gic_dist_base = ioremap(AM43XX_GIC_DIST_BASE, SZ_4K);
if (!gic_dist_base)
return -ENOMEM;
return 0;
}
#ifdef CONFIG_SUSPEND
static struct wkup_m3_wakeup_src rtc_wake_src(void)
{
u32 i;
i = __raw_readl(rtc_base_virt + 0x44) & 0x40;
if (i) {
retrigger_irq = rtc_alarm_wakeup.irq_nr;
return rtc_alarm_wakeup;
}
retrigger_irq = rtc_ext_wakeup.irq_nr;
return rtc_ext_wakeup;
}
static int am33xx_rtc_only_idle(unsigned long wfi_flags)
{
omap_rtc_power_off_program(&omap_rtc->dev);
am33xx_do_wfi_sram(wfi_flags);
return 0;
}
/*
* Note that the RTC module clock must be re-enabled only for rtc+ddr suspend.
* And looks like the module can stay in SYSC_IDLE_SMART_WKUP mode configured
* by the interconnect code just fine for both rtc+ddr suspend and retention
* suspend.
*/
static int am33xx_pm_suspend(suspend_state_t suspend_state)
{
int i, ret = 0;
if (suspend_state == PM_SUSPEND_MEM &&
pm_ops->check_off_mode_enable()) {
ret = clk_prepare_enable(rtc_fck);
if (ret) {
dev_err(pm33xx_dev, "Failed to enable clock: %i\n", ret);
return ret;
}
pm_ops->save_context();
suspend_wfi_flags |= WFI_FLAG_RTC_ONLY;
clk_save_context();
ret = pm_ops->soc_suspend(suspend_state, am33xx_rtc_only_idle,
suspend_wfi_flags);
suspend_wfi_flags &= ~WFI_FLAG_RTC_ONLY;
dev_info(pm33xx_dev, "Entering RTC Only mode with DDR in self-refresh\n");
if (!ret) {
clk_restore_context();
pm_ops->restore_context();
m3_ipc->ops->set_rtc_only(m3_ipc);
am33xx_push_sram_idle();
}
} else {
ret = pm_ops->soc_suspend(suspend_state, am33xx_do_wfi_sram,
suspend_wfi_flags);
}
if (ret) {
dev_err(pm33xx_dev, "PM: Kernel suspend failure\n");
} else {
i = m3_ipc->ops->request_pm_status(m3_ipc);
switch (i) {
case 0:
dev_info(pm33xx_dev,
"PM: Successfully put all powerdomains to target state\n");
break;
case 1:
dev_err(pm33xx_dev,
"PM: Could not transition all powerdomains to target state\n");
ret = -1;
break;
default:
dev_err(pm33xx_dev,
"PM: CM3 returned unknown result = %d\n", i);
ret = -1;
}
/* print the wakeup reason */
if (rtc_only_idle) {
wakeup_src = rtc_wake_src();
pr_info("PM: Wakeup source %s\n", wakeup_src.src);
} else {
pr_info("PM: Wakeup source %s\n",
m3_ipc->ops->request_wake_src(m3_ipc));
}
}
if (suspend_state == PM_SUSPEND_MEM && pm_ops->check_off_mode_enable())
clk_disable_unprepare(rtc_fck);
return ret;
}
static int am33xx_pm_enter(suspend_state_t suspend_state)
{
int ret = 0;
switch (suspend_state) {
case PM_SUSPEND_MEM:
case PM_SUSPEND_STANDBY:
ret = am33xx_pm_suspend(suspend_state);
break;
default:
ret = -EINVAL;
}
return ret;
}
static int am33xx_pm_begin(suspend_state_t state)
{
int ret = -EINVAL;
struct nvmem_device *nvmem;
if (state == PM_SUSPEND_MEM && pm_ops->check_off_mode_enable()) {
nvmem = devm_nvmem_device_get(&omap_rtc->dev,
"omap_rtc_scratch0");
if (!IS_ERR(nvmem))
nvmem_device_write(nvmem, RTC_SCRATCH_MAGIC_REG * 4, 4,
(void *)&rtc_magic_val);
rtc_only_idle = 1;
} else {
rtc_only_idle = 0;
}
pm_ops->begin_suspend();
switch (state) {
case PM_SUSPEND_MEM:
ret = m3_ipc->ops->prepare_low_power(m3_ipc, WKUP_M3_DEEPSLEEP);
break;
case PM_SUSPEND_STANDBY:
ret = m3_ipc->ops->prepare_low_power(m3_ipc, WKUP_M3_STANDBY);
break;
}
return ret;
}
static void am33xx_pm_end(void)
{
u32 val = 0;
struct nvmem_device *nvmem;
nvmem = devm_nvmem_device_get(&omap_rtc->dev, "omap_rtc_scratch0");
if (IS_ERR(nvmem))
return;
m3_ipc->ops->finish_low_power(m3_ipc);
if (rtc_only_idle) {
if (retrigger_irq) {
/*
* 32 bits of Interrupt Set-Pending correspond to 32
* 32 interrupts. Compute the bit offset of the
* Interrupt and set that particular bit
* Compute the register offset by dividing interrupt
* number by 32 and mutiplying by 4
*/
writel_relaxed(1 << (retrigger_irq & 31),
gic_dist_base + GIC_INT_SET_PENDING_BASE
+ retrigger_irq / 32 * 4);
}
nvmem_device_write(nvmem, RTC_SCRATCH_MAGIC_REG * 4, 4,
(void *)&val);
}
rtc_only_idle = 0;
pm_ops->finish_suspend();
}
static int am33xx_pm_valid(suspend_state_t state)
{
switch (state) {
case PM_SUSPEND_STANDBY:
case PM_SUSPEND_MEM:
return 1;
default:
return 0;
}
}
static const struct platform_suspend_ops am33xx_pm_ops = {
.begin = am33xx_pm_begin,
.end = am33xx_pm_end,
.enter = am33xx_pm_enter,
.valid = am33xx_pm_valid,
};
#endif /* CONFIG_SUSPEND */
static void am33xx_pm_set_ipc_ops(void)
{
u32 resume_address;
int temp;
temp = ti_emif_get_mem_type();
if (temp < 0) {
dev_err(pm33xx_dev, "PM: Cannot determine memory type, no PM available\n");
return;
}
m3_ipc->ops->set_mem_type(m3_ipc, temp);
/* Physical resume address to be used by ROM code */
resume_address = am33xx_do_wfi_sram_phys +
*pm_sram->resume_offset + 0x4;
m3_ipc->ops->set_resume_address(m3_ipc, (void *)resume_address);
}
static void am33xx_pm_free_sram(void)
{
gen_pool_free(sram_pool, ocmcram_location, *pm_sram->do_wfi_sz);
gen_pool_free(sram_pool_data, ocmcram_location_data,
sizeof(struct am33xx_pm_ro_sram_data));
}
/*
* Push the minimal suspend-resume code to SRAM
*/
static int am33xx_pm_alloc_sram(void)
{
struct device_node *np __free(device_node) =
of_find_compatible_node(NULL, NULL, "ti,omap3-mpu");
if (!np) {
np = of_find_compatible_node(NULL, NULL, "ti,omap4-mpu");
if (!np)
return dev_err_probe(pm33xx_dev, -ENODEV,
"PM: %s: Unable to find device node for mpu\n",
__func__);
}
sram_pool = of_gen_pool_get(np, "pm-sram", 0);
if (!sram_pool)
return dev_err_probe(pm33xx_dev, -ENODEV,
"PM: %s: Unable to get sram pool for ocmcram\n",
__func__);
sram_pool_data = of_gen_pool_get(np, "pm-sram", 1);
if (!sram_pool_data)
return dev_err_probe(pm33xx_dev, -ENODEV,
"PM: %s: Unable to get sram data pool for ocmcram\n",
__func__);
ocmcram_location = gen_pool_alloc(sram_pool, *pm_sram->do_wfi_sz);
if (!ocmcram_location)
return dev_err_probe(pm33xx_dev, -ENOMEM,
"PM: %s: Unable to allocate memory from ocmcram\n",
__func__);
ocmcram_location_data = gen_pool_alloc(sram_pool_data,
sizeof(struct emif_regs_amx3));
if (!ocmcram_location_data) {
gen_pool_free(sram_pool, ocmcram_location, *pm_sram->do_wfi_sz);
return dev_err_probe(pm33xx_dev, -ENOMEM,
"PM: Unable to allocate memory from ocmcram\n");
}
return 0;
}
static int am33xx_pm_rtc_setup(void)
{
struct device_node *np;
unsigned long val = 0;
struct nvmem_device *nvmem;
int error;
np = of_find_node_by_name(NULL, "rtc");
if (of_device_is_available(np)) {
/* RTC interconnect target module clock */
rtc_fck = of_clk_get_by_name(np->parent, "fck");
if (IS_ERR(rtc_fck))
return PTR_ERR(rtc_fck);
rtc_base_virt = of_iomap(np, 0);
if (!rtc_base_virt) {
pr_warn("PM: could not iomap rtc\n");
error = -ENODEV;
goto err_clk_put;
}
omap_rtc = rtc_class_open("rtc0");
if (!omap_rtc) {
pr_warn("PM: rtc0 not available\n");
error = -EPROBE_DEFER;
goto err_iounmap;
}
nvmem = devm_nvmem_device_get(&omap_rtc->dev,
"omap_rtc_scratch0");
if (!IS_ERR(nvmem)) {
nvmem_device_read(nvmem, RTC_SCRATCH_MAGIC_REG * 4,
4, (void *)&rtc_magic_val);
if ((rtc_magic_val & 0xffff) != RTC_REG_BOOT_MAGIC)
pr_warn("PM: bootloader does not support rtc-only!\n");
nvmem_device_write(nvmem, RTC_SCRATCH_MAGIC_REG * 4,
4, (void *)&val);
val = pm_sram->resume_address;
nvmem_device_write(nvmem, RTC_SCRATCH_RESUME_REG * 4,
4, (void *)&val);
}
} else {
pr_warn("PM: no-rtc available, rtc-only mode disabled.\n");
}
return 0;
err_iounmap:
iounmap(rtc_base_virt);
err_clk_put:
clk_put(rtc_fck);
return error;
}
static int am33xx_pm_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int ret;
if (!of_machine_is_compatible("ti,am33xx") &&
!of_machine_is_compatible("ti,am43"))
return -ENODEV;
pm_ops = dev->platform_data;
if (!pm_ops) {
dev_err(dev, "PM: Cannot get core PM ops!\n");
return -ENODEV;
}
ret = am43xx_map_gic();
if (ret) {
pr_err("PM: Could not ioremap GIC base\n");
return ret;
}
pm_sram = pm_ops->get_sram_addrs();
if (!pm_sram) {
dev_err(dev, "PM: Cannot get PM asm function addresses!!\n");
return -ENODEV;
}
m3_ipc = wkup_m3_ipc_get();
if (!m3_ipc) {
pr_err("PM: Cannot get wkup_m3_ipc handle\n");
return -EPROBE_DEFER;
}
pm33xx_dev = dev;
ret = am33xx_pm_alloc_sram();
if (ret)
goto err_wkup_m3_ipc_put;
ret = am33xx_pm_rtc_setup();
if (ret)
goto err_free_sram;
ret = am33xx_push_sram_idle();
if (ret)
goto err_unsetup_rtc;
am33xx_pm_set_ipc_ops();
#ifdef CONFIG_SUSPEND
suspend_set_ops(&am33xx_pm_ops);
/*
* For a system suspend we must flush the caches, we want
* the DDR in self-refresh, we want to save the context
* of the EMIF, and we want the wkup_m3 to handle low-power
* transition.
*/
suspend_wfi_flags |= WFI_FLAG_FLUSH_CACHE;
suspend_wfi_flags |= WFI_FLAG_SELF_REFRESH;
suspend_wfi_flags |= WFI_FLAG_SAVE_EMIF;
suspend_wfi_flags |= WFI_FLAG_WAKE_M3;
#endif /* CONFIG_SUSPEND */
pm_runtime_enable(dev);
ret = pm_runtime_resume_and_get(dev);
if (ret < 0)
goto err_pm_runtime_disable;
ret = pm_ops->init(am33xx_do_sram_idle);
if (ret) {
dev_err(dev, "Unable to call core pm init!\n");
ret = -ENODEV;
goto err_pm_runtime_put;
}
return 0;
err_pm_runtime_put:
pm_runtime_put_sync(dev);
err_pm_runtime_disable:
pm_runtime_disable(dev);
err_unsetup_rtc:
iounmap(rtc_base_virt);
clk_put(rtc_fck);
err_free_sram:
am33xx_pm_free_sram();
pm33xx_dev = NULL;
err_wkup_m3_ipc_put:
wkup_m3_ipc_put(m3_ipc);
return ret;
}
static void am33xx_pm_remove(struct platform_device *pdev)
{
pm_runtime_put_sync(&pdev->dev);
pm_runtime_disable(&pdev->dev);
if (pm_ops->deinit)
pm_ops->deinit();
suspend_set_ops(NULL);
wkup_m3_ipc_put(m3_ipc);
am33xx_pm_free_sram();
iounmap(rtc_base_virt);
clk_put(rtc_fck);
}
static struct platform_driver am33xx_pm_driver = {
.driver = {
.name = "pm33xx",
},
.probe = am33xx_pm_probe,
.remove = am33xx_pm_remove,
};
module_platform_driver(am33xx_pm_driver);
MODULE_ALIAS("platform:pm33xx");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("am33xx power management driver");
+601
View File
@@ -0,0 +1,601 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* PRU-ICSS platform driver for various TI SoCs
*
* Copyright (C) 2014-2020 Texas Instruments Incorporated - http://www.ti.com/
* Author(s):
* Suman Anna <s-anna@ti.com>
* Andrew F. Davis <afd@ti.com>
* Tero Kristo <t-kristo@ti.com>
*/
#include <linux/clk-provider.h>
#include <linux/dma-mapping.h>
#include <linux/io.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/pruss_driver.h>
#include <linux/regmap.h>
#include <linux/remoteproc.h>
#include <linux/slab.h>
#include "pruss.h"
/**
* struct pruss_private_data - PRUSS driver private data
* @has_no_sharedram: flag to indicate the absence of PRUSS Shared Data RAM
* @has_core_mux_clock: flag to indicate the presence of PRUSS core clock
*/
struct pruss_private_data {
bool has_no_sharedram;
bool has_core_mux_clock;
};
/**
* pruss_get() - get the pruss for a given PRU remoteproc
* @rproc: remoteproc handle of a PRU instance
*
* Finds the parent pruss device for a PRU given the @rproc handle of the
* PRU remote processor. This function increments the pruss device's refcount,
* so always use pruss_put() to decrement it back once pruss isn't needed
* anymore.
*
* This API doesn't check if @rproc is valid or not. It is expected the caller
* will have done a pru_rproc_get() on @rproc, before calling this API to make
* sure that @rproc is valid.
*
* Return: pruss handle on success, and an ERR_PTR on failure using one
* of the following error values
* -EINVAL if invalid parameter
* -ENODEV if PRU device or PRUSS device is not found
*/
struct pruss *pruss_get(struct rproc *rproc)
{
struct pruss *pruss;
struct device *dev;
struct platform_device *ppdev;
if (IS_ERR_OR_NULL(rproc))
return ERR_PTR(-EINVAL);
dev = &rproc->dev;
/* make sure it is PRU rproc */
if (!dev->parent || !is_pru_rproc(dev->parent))
return ERR_PTR(-ENODEV);
ppdev = to_platform_device(dev->parent->parent);
pruss = platform_get_drvdata(ppdev);
if (!pruss)
return ERR_PTR(-ENODEV);
get_device(pruss->dev);
return pruss;
}
EXPORT_SYMBOL_GPL(pruss_get);
/**
* pruss_put() - decrement pruss device's usecount
* @pruss: pruss handle
*
* Complimentary function for pruss_get(). Needs to be called
* after the PRUSS is used, and only if the pruss_get() succeeds.
*/
void pruss_put(struct pruss *pruss)
{
if (IS_ERR_OR_NULL(pruss))
return;
put_device(pruss->dev);
}
EXPORT_SYMBOL_GPL(pruss_put);
/**
* pruss_request_mem_region() - request a memory resource
* @pruss: the pruss instance
* @mem_id: the memory resource id
* @region: pointer to memory region structure to be filled in
*
* This function allows a client driver to request a memory resource,
* and if successful, will let the client driver own the particular
* memory region until released using the pruss_release_mem_region()
* API.
*
* Return: 0 if requested memory region is available (in such case pointer to
* memory region is returned via @region), an error otherwise
*/
int pruss_request_mem_region(struct pruss *pruss, enum pruss_mem mem_id,
struct pruss_mem_region *region)
{
if (!pruss || !region || mem_id >= PRUSS_MEM_MAX)
return -EINVAL;
mutex_lock(&pruss->lock);
if (pruss->mem_in_use[mem_id]) {
mutex_unlock(&pruss->lock);
return -EBUSY;
}
*region = pruss->mem_regions[mem_id];
pruss->mem_in_use[mem_id] = region;
mutex_unlock(&pruss->lock);
return 0;
}
EXPORT_SYMBOL_GPL(pruss_request_mem_region);
/**
* pruss_release_mem_region() - release a memory resource
* @pruss: the pruss instance
* @region: the memory region to release
*
* This function is the complimentary function to
* pruss_request_mem_region(), and allows the client drivers to
* release back a memory resource.
*
* Return: 0 on success, an error code otherwise
*/
int pruss_release_mem_region(struct pruss *pruss,
struct pruss_mem_region *region)
{
int id;
if (!pruss || !region)
return -EINVAL;
mutex_lock(&pruss->lock);
/* find out the memory region being released */
for (id = 0; id < PRUSS_MEM_MAX; id++) {
if (pruss->mem_in_use[id] == region)
break;
}
if (id == PRUSS_MEM_MAX) {
mutex_unlock(&pruss->lock);
return -EINVAL;
}
pruss->mem_in_use[id] = NULL;
mutex_unlock(&pruss->lock);
return 0;
}
EXPORT_SYMBOL_GPL(pruss_release_mem_region);
/**
* pruss_cfg_get_gpmux() - get the current GPMUX value for a PRU device
* @pruss: pruss instance
* @pru_id: PRU identifier (0-1)
* @mux: pointer to store the current mux value into
*
* Return: 0 on success, or an error code otherwise
*/
int pruss_cfg_get_gpmux(struct pruss *pruss, enum pruss_pru_id pru_id, u8 *mux)
{
int ret;
u32 val;
if (pru_id >= PRUSS_NUM_PRUS || !mux)
return -EINVAL;
ret = pruss_cfg_read(pruss, PRUSS_CFG_GPCFG(pru_id), &val);
if (!ret)
*mux = (u8)((val & PRUSS_GPCFG_PRU_MUX_SEL_MASK) >>
PRUSS_GPCFG_PRU_MUX_SEL_SHIFT);
return ret;
}
EXPORT_SYMBOL_GPL(pruss_cfg_get_gpmux);
/**
* pruss_cfg_set_gpmux() - set the GPMUX value for a PRU device
* @pruss: pruss instance
* @pru_id: PRU identifier (0-1)
* @mux: new mux value for PRU
*
* Return: 0 on success, or an error code otherwise
*/
int pruss_cfg_set_gpmux(struct pruss *pruss, enum pruss_pru_id pru_id, u8 mux)
{
if (mux >= PRUSS_GP_MUX_SEL_MAX ||
pru_id >= PRUSS_NUM_PRUS)
return -EINVAL;
return pruss_cfg_update(pruss, PRUSS_CFG_GPCFG(pru_id),
PRUSS_GPCFG_PRU_MUX_SEL_MASK,
(u32)mux << PRUSS_GPCFG_PRU_MUX_SEL_SHIFT);
}
EXPORT_SYMBOL_GPL(pruss_cfg_set_gpmux);
/**
* pruss_cfg_gpimode() - set the GPI mode of the PRU
* @pruss: the pruss instance handle
* @pru_id: id of the PRU core within the PRUSS
* @mode: GPI mode to set
*
* Sets the GPI mode for a given PRU by programming the
* corresponding PRUSS_CFG_GPCFGx register
*
* Return: 0 on success, or an error code otherwise
*/
int pruss_cfg_gpimode(struct pruss *pruss, enum pruss_pru_id pru_id,
enum pruss_gpi_mode mode)
{
if (pru_id >= PRUSS_NUM_PRUS || mode >= PRUSS_GPI_MODE_MAX)
return -EINVAL;
return pruss_cfg_update(pruss, PRUSS_CFG_GPCFG(pru_id),
PRUSS_GPCFG_PRU_GPI_MODE_MASK,
mode << PRUSS_GPCFG_PRU_GPI_MODE_SHIFT);
}
EXPORT_SYMBOL_GPL(pruss_cfg_gpimode);
/**
* pruss_cfg_miirt_enable() - Enable/disable MII RT Events
* @pruss: the pruss instance
* @enable: enable/disable
*
* Enable/disable the MII RT Events for the PRUSS.
*
* Return: 0 on success, or an error code otherwise
*/
int pruss_cfg_miirt_enable(struct pruss *pruss, bool enable)
{
u32 set = enable ? PRUSS_MII_RT_EVENT_EN : 0;
return pruss_cfg_update(pruss, PRUSS_CFG_MII_RT,
PRUSS_MII_RT_EVENT_EN, set);
}
EXPORT_SYMBOL_GPL(pruss_cfg_miirt_enable);
/**
* pruss_cfg_xfr_enable() - Enable/disable XIN XOUT shift functionality
* @pruss: the pruss instance
* @pru_type: PRU core type identifier
* @enable: enable/disable
*
* Return: 0 on success, or an error code otherwise
*/
int pruss_cfg_xfr_enable(struct pruss *pruss, enum pru_type pru_type,
bool enable)
{
u32 mask, set;
switch (pru_type) {
case PRU_TYPE_PRU:
mask = PRUSS_SPP_XFER_SHIFT_EN;
break;
case PRU_TYPE_RTU:
mask = PRUSS_SPP_RTU_XFR_SHIFT_EN;
break;
default:
return -EINVAL;
}
set = enable ? mask : 0;
return pruss_cfg_update(pruss, PRUSS_CFG_SPP, mask, set);
}
EXPORT_SYMBOL_GPL(pruss_cfg_xfr_enable);
static void pruss_of_free_clk_provider(void *data)
{
struct device_node *clk_mux_np = data;
of_clk_del_provider(clk_mux_np);
of_node_put(clk_mux_np);
}
static void pruss_clk_unregister_mux(void *data)
{
clk_unregister_mux(data);
}
static int pruss_clk_mux_setup(struct pruss *pruss, struct clk *clk_mux,
char *mux_name, struct device_node *clks_np)
{
struct device_node *clk_mux_np;
struct device *dev = pruss->dev;
char *clk_mux_name;
unsigned int num_parents;
const char **parent_names;
void __iomem *reg;
u32 reg_offset;
int ret;
clk_mux_np = of_get_child_by_name(clks_np, mux_name);
if (!clk_mux_np) {
dev_err(dev, "%pOF is missing its '%s' node\n", clks_np,
mux_name);
return -ENODEV;
}
num_parents = of_clk_get_parent_count(clk_mux_np);
if (num_parents < 1) {
dev_err(dev, "mux-clock %pOF must have parents\n", clk_mux_np);
ret = -EINVAL;
goto put_clk_mux_np;
}
parent_names = devm_kcalloc(dev, sizeof(*parent_names), num_parents,
GFP_KERNEL);
if (!parent_names) {
ret = -ENOMEM;
goto put_clk_mux_np;
}
of_clk_parent_fill(clk_mux_np, parent_names, num_parents);
clk_mux_name = devm_kasprintf(dev, GFP_KERNEL, "%s.%pOFn",
dev_name(dev), clk_mux_np);
if (!clk_mux_name) {
ret = -ENOMEM;
goto put_clk_mux_np;
}
ret = of_property_read_u32(clk_mux_np, "reg", &reg_offset);
if (ret)
goto put_clk_mux_np;
reg = pruss->cfg_base + reg_offset;
clk_mux = clk_register_mux(NULL, clk_mux_name, parent_names,
num_parents, 0, reg, 0, 1, 0, NULL);
if (IS_ERR(clk_mux)) {
ret = PTR_ERR(clk_mux);
goto put_clk_mux_np;
}
ret = devm_add_action_or_reset(dev, pruss_clk_unregister_mux, clk_mux);
if (ret) {
dev_err(dev, "failed to add clkmux unregister action %d", ret);
goto put_clk_mux_np;
}
ret = of_clk_add_provider(clk_mux_np, of_clk_src_simple_get, clk_mux);
if (ret)
goto put_clk_mux_np;
ret = devm_add_action_or_reset(dev, pruss_of_free_clk_provider,
clk_mux_np);
if (ret)
dev_err(dev, "failed to add clkmux free action %d", ret);
return ret;
put_clk_mux_np:
of_node_put(clk_mux_np);
return ret;
}
static int pruss_clk_init(struct pruss *pruss, struct device_node *cfg_node)
{
struct device *dev = pruss->dev;
struct device_node *clks_np __free(device_node) =
of_get_child_by_name(cfg_node, "clocks");
const struct pruss_private_data *data = of_device_get_match_data(dev);
int ret;
if (!clks_np)
return dev_err_probe(dev, -ENODEV,
"%pOF is missing its 'clocks' node\n",
cfg_node);
if (data && data->has_core_mux_clock) {
ret = pruss_clk_mux_setup(pruss, pruss->core_clk_mux,
"coreclk-mux", clks_np);
if (ret)
return dev_err_probe(dev, ret,
"failed to setup coreclk-mux\n");
}
ret = pruss_clk_mux_setup(pruss, pruss->iep_clk_mux, "iepclk-mux",
clks_np);
if (ret)
return dev_err_probe(dev, ret, "failed to setup iepclk-mux\n");
return 0;
}
static int pruss_of_setup_memories(struct device *dev, struct pruss *pruss)
{
struct device_node *np = dev_of_node(dev);
struct device_node *child __free(device_node) =
of_get_child_by_name(np, "memories");
const struct pruss_private_data *data = of_device_get_match_data(dev);
const char *mem_names[PRUSS_MEM_MAX] = { "dram0", "dram1", "shrdram2" };
int i;
if (!child)
return dev_err_probe(dev, -ENODEV,
"%pOF is missing its 'memories' node\n",
child);
for (i = 0; i < PRUSS_MEM_MAX; i++) {
struct resource res;
int index;
/*
* On AM437x one of two PRUSS units don't contain Shared RAM,
* skip it
*/
if (data && data->has_no_sharedram && i == PRUSS_MEM_SHRD_RAM2)
continue;
index = of_property_match_string(child, "reg-names",
mem_names[i]);
if (index < 0)
return index;
if (of_address_to_resource(child, index, &res))
return -EINVAL;
pruss->mem_regions[i].va = devm_ioremap(dev, res.start,
resource_size(&res));
if (!pruss->mem_regions[i].va)
return dev_err_probe(dev, -ENOMEM,
"failed to parse and map memory resource %d %s\n",
i, mem_names[i]);
pruss->mem_regions[i].pa = res.start;
pruss->mem_regions[i].size = resource_size(&res);
dev_dbg(dev, "memory %8s: pa %pa size 0x%zx va %p\n",
mem_names[i], &pruss->mem_regions[i].pa,
pruss->mem_regions[i].size, pruss->mem_regions[i].va);
}
return 0;
}
static struct regmap_config regmap_conf = {
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
};
static int pruss_cfg_of_init(struct device *dev, struct pruss *pruss)
{
struct device_node *np = dev_of_node(dev);
struct device_node *child __free(device_node) =
of_get_child_by_name(np, "cfg");
struct resource res;
int ret;
if (!child)
return dev_err_probe(dev, -ENODEV,
"%pOF is missing its 'cfg' node\n", child);
if (of_address_to_resource(child, 0, &res))
return -ENOMEM;
pruss->cfg_base = devm_ioremap(dev, res.start, resource_size(&res));
if (!pruss->cfg_base)
return -ENOMEM;
regmap_conf.name = kasprintf(GFP_KERNEL, "%pOFn@%llx", child,
(u64)res.start);
regmap_conf.max_register = resource_size(&res) - 4;
pruss->cfg_regmap = devm_regmap_init_mmio(dev, pruss->cfg_base,
&regmap_conf);
kfree(regmap_conf.name);
if (IS_ERR(pruss->cfg_regmap))
return dev_err_probe(dev, PTR_ERR(pruss->cfg_regmap),
"regmap_init_mmio failed for cfg\n");
ret = pruss_clk_init(pruss, child);
if (ret)
return dev_err_probe(dev, ret, "pruss_clk_init failed\n");
return 0;
}
static int pruss_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct pruss *pruss;
int ret;
ret = dma_set_coherent_mask(dev, DMA_BIT_MASK(32));
if (ret) {
dev_err(dev, "failed to set the DMA coherent mask");
return ret;
}
pruss = devm_kzalloc(dev, sizeof(*pruss), GFP_KERNEL);
if (!pruss)
return -ENOMEM;
pruss->dev = dev;
mutex_init(&pruss->lock);
ret = pruss_of_setup_memories(dev, pruss);
if (ret < 0)
return ret;
platform_set_drvdata(pdev, pruss);
pm_runtime_enable(dev);
ret = pm_runtime_resume_and_get(dev);
if (ret < 0) {
dev_err(dev, "couldn't enable module\n");
goto rpm_disable;
}
ret = pruss_cfg_of_init(dev, pruss);
if (ret < 0)
goto rpm_put;
ret = devm_of_platform_populate(dev);
if (ret) {
dev_err(dev, "failed to register child devices\n");
goto rpm_put;
}
return 0;
rpm_put:
pm_runtime_put_sync(dev);
rpm_disable:
pm_runtime_disable(dev);
return ret;
}
static void pruss_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
devm_of_platform_depopulate(dev);
pm_runtime_put_sync(dev);
pm_runtime_disable(dev);
}
/* instance-specific driver private data */
static const struct pruss_private_data am437x_pruss1_data = {
.has_no_sharedram = false,
};
static const struct pruss_private_data am437x_pruss0_data = {
.has_no_sharedram = true,
};
static const struct pruss_private_data am65x_j721e_pruss_data = {
.has_core_mux_clock = true,
};
static const struct of_device_id pruss_of_match[] = {
{ .compatible = "ti,am3356-pruss" },
{ .compatible = "ti,am4376-pruss0", .data = &am437x_pruss0_data, },
{ .compatible = "ti,am4376-pruss1", .data = &am437x_pruss1_data, },
{ .compatible = "ti,am5728-pruss" },
{ .compatible = "ti,k2g-pruss" },
{ .compatible = "ti,am654-icssg", .data = &am65x_j721e_pruss_data, },
{ .compatible = "ti,j721e-icssg", .data = &am65x_j721e_pruss_data, },
{ .compatible = "ti,am642-icssg", .data = &am65x_j721e_pruss_data, },
{ .compatible = "ti,am625-pruss", .data = &am65x_j721e_pruss_data, },
{},
};
MODULE_DEVICE_TABLE(of, pruss_of_match);
static struct platform_driver pruss_driver = {
.driver = {
.name = "pruss",
.of_match_table = pruss_of_match,
},
.probe = pruss_probe,
.remove = pruss_remove,
};
module_platform_driver(pruss_driver);
MODULE_AUTHOR("Suman Anna <s-anna@ti.com>");
MODULE_DESCRIPTION("PRU-ICSS Subsystem Driver");
MODULE_LICENSE("GPL v2");
+88
View File
@@ -0,0 +1,88 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* PRU-ICSS Subsystem user interfaces
*
* Copyright (C) 2015-2023 Texas Instruments Incorporated - http://www.ti.com
* MD Danish Anwar <danishanwar@ti.com>
*/
#ifndef _SOC_TI_PRUSS_H_
#define _SOC_TI_PRUSS_H_
#include <linux/bits.h>
#include <linux/regmap.h>
/*
* PRU_ICSS_CFG registers
* SYSCFG, ISRP, ISP, IESP, IECP, SCRP applicable on AMxxxx devices only
*/
#define PRUSS_CFG_REVID 0x00
#define PRUSS_CFG_SYSCFG 0x04
#define PRUSS_CFG_GPCFG(x) (0x08 + (x) * 4)
#define PRUSS_CFG_CGR 0x10
#define PRUSS_CFG_ISRP 0x14
#define PRUSS_CFG_ISP 0x18
#define PRUSS_CFG_IESP 0x1C
#define PRUSS_CFG_IECP 0x20
#define PRUSS_CFG_SCRP 0x24
#define PRUSS_CFG_PMAO 0x28
#define PRUSS_CFG_MII_RT 0x2C
#define PRUSS_CFG_IEPCLK 0x30
#define PRUSS_CFG_SPP 0x34
#define PRUSS_CFG_PIN_MX 0x40
/* PRUSS_GPCFG register bits */
#define PRUSS_GPCFG_PRU_GPI_MODE_MASK GENMASK(1, 0)
#define PRUSS_GPCFG_PRU_GPI_MODE_SHIFT 0
#define PRUSS_GPCFG_PRU_MUX_SEL_SHIFT 26
#define PRUSS_GPCFG_PRU_MUX_SEL_MASK GENMASK(29, 26)
/* PRUSS_MII_RT register bits */
#define PRUSS_MII_RT_EVENT_EN BIT(0)
/* PRUSS_SPP register bits */
#define PRUSS_SPP_XFER_SHIFT_EN BIT(1)
#define PRUSS_SPP_PRU1_PAD_HP_EN BIT(0)
#define PRUSS_SPP_RTU_XFR_SHIFT_EN BIT(3)
/**
* pruss_cfg_read() - read a PRUSS CFG sub-module register
* @pruss: the pruss instance handle
* @reg: register offset within the CFG sub-module
* @val: pointer to return the value in
*
* Reads a given register within the PRUSS CFG sub-module and
* returns it through the passed-in @val pointer
*
* Return: 0 on success, or an error code otherwise
*/
static int pruss_cfg_read(struct pruss *pruss, unsigned int reg, unsigned int *val)
{
if (IS_ERR_OR_NULL(pruss))
return -EINVAL;
return regmap_read(pruss->cfg_regmap, reg, val);
}
/**
* pruss_cfg_update() - configure a PRUSS CFG sub-module register
* @pruss: the pruss instance handle
* @reg: register offset within the CFG sub-module
* @mask: bit mask to use for programming the @val
* @val: value to write
*
* Programs a given register within the PRUSS CFG sub-module
*
* Return: 0 on success, or an error code otherwise
*/
static int pruss_cfg_update(struct pruss *pruss, unsigned int reg,
unsigned int mask, unsigned int val)
{
if (IS_ERR_OR_NULL(pruss))
return -EINVAL;
return regmap_update_bits(pruss->cfg_regmap, reg, mask, val);
}
#endif /* _SOC_TI_PRUSS_H_ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,117 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Texas Instruments' K3 Interrupt Aggregator MSI bus
*
* Copyright (C) 2018-2019 Texas Instruments Incorporated - http://www.ti.com/
* Lokesh Vutla <lokeshvutla@ti.com>
*/
#include <linux/irq.h>
#include <linux/irqdomain.h>
#include <linux/msi.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>
#include <linux/soc/ti/ti_sci_inta_msi.h>
#include <linux/soc/ti/ti_sci_protocol.h>
static void ti_sci_inta_msi_write_msg(struct irq_data *data,
struct msi_msg *msg)
{
/* Nothing to do */
}
static void ti_sci_inta_msi_compose_msi_msg(struct irq_data *data,
struct msi_msg *msg)
{
/* Nothing to do */
}
static void ti_sci_inta_msi_update_chip_ops(struct msi_domain_info *info)
{
struct irq_chip *chip = info->chip;
if (WARN_ON(!chip))
return;
chip->irq_request_resources = irq_chip_request_resources_parent;
chip->irq_release_resources = irq_chip_release_resources_parent;
chip->irq_compose_msi_msg = ti_sci_inta_msi_compose_msi_msg;
chip->irq_write_msi_msg = ti_sci_inta_msi_write_msg;
chip->irq_set_type = irq_chip_set_type_parent;
chip->irq_unmask = irq_chip_unmask_parent;
chip->irq_mask = irq_chip_mask_parent;
chip->irq_ack = irq_chip_ack_parent;
}
struct irq_domain *ti_sci_inta_msi_create_irq_domain(struct fwnode_handle *fwnode,
struct msi_domain_info *info,
struct irq_domain *parent)
{
struct irq_domain *domain;
ti_sci_inta_msi_update_chip_ops(info);
info->flags |= MSI_FLAG_FREE_MSI_DESCS;
domain = msi_create_irq_domain(fwnode, info, parent);
if (domain)
irq_domain_update_bus_token(domain, DOMAIN_BUS_TI_SCI_INTA_MSI);
return domain;
}
EXPORT_SYMBOL_GPL(ti_sci_inta_msi_create_irq_domain);
static int ti_sci_inta_msi_alloc_descs(struct device *dev,
struct ti_sci_resource *res)
{
struct msi_desc msi_desc;
int set, i, count = 0;
memset(&msi_desc, 0, sizeof(msi_desc));
msi_desc.nvec_used = 1;
for (set = 0; set < res->sets; set++) {
for (i = 0; i < res->desc[set].num; i++, count++) {
msi_desc.msi_index = res->desc[set].start + i;
if (msi_insert_msi_desc(dev, &msi_desc))
goto fail;
}
for (i = 0; i < res->desc[set].num_sec; i++, count++) {
msi_desc.msi_index = res->desc[set].start_sec + i;
if (msi_insert_msi_desc(dev, &msi_desc))
goto fail;
}
}
return count;
fail:
msi_free_msi_descs(dev);
return -ENOMEM;
}
int ti_sci_inta_msi_domain_alloc_irqs(struct device *dev,
struct ti_sci_resource *res)
{
struct platform_device *pdev = to_platform_device(dev);
int ret, nvec;
if (pdev->id < 0)
return -ENODEV;
ret = msi_setup_device_data(dev);
if (ret)
return ret;
guard(msi_descs_lock)(dev);
nvec = ti_sci_inta_msi_alloc_descs(dev, res);
if (nvec <= 0)
return nvec;
/* Use alloc ALL as it's unclear whether there are gaps in the indices */
ret = msi_domain_alloc_irqs_all_locked(dev, MSI_DEFAULT_DOMAIN, nvec);
if (ret)
dev_err(dev, "Failed to allocate IRQs %d\n", ret);
return ret;
}
EXPORT_SYMBOL_GPL(ti_sci_inta_msi_domain_alloc_irqs);
+768
View File
@@ -0,0 +1,768 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AMx3 Wkup M3 IPC driver
*
* Copyright (C) 2015 Texas Instruments, Inc.
*
* Dave Gerlach <d-gerlach@ti.com>
*/
#include <linux/debugfs.h>
#include <linux/err.h>
#include <linux/firmware.h>
#include <linux/kernel.h>
#include <linux/kthread.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/remoteproc.h>
#include <linux/suspend.h>
#include <linux/wkup_m3_ipc.h>
#define AM33XX_CTRL_IPC_REG_COUNT 0x8
#define AM33XX_CTRL_IPC_REG_OFFSET(m) (0x4 + 4 * (m))
/* AM33XX M3_TXEV_EOI register */
#define AM33XX_CONTROL_M3_TXEV_EOI 0x00
#define AM33XX_M3_TXEV_ACK (0x1 << 0)
#define AM33XX_M3_TXEV_ENABLE (0x0 << 0)
#define IPC_CMD_DS0 0x4
#define IPC_CMD_STANDBY 0xc
#define IPC_CMD_IDLE 0x10
#define IPC_CMD_RESET 0xe
#define DS_IPC_DEFAULT 0xffffffff
#define M3_VERSION_UNKNOWN 0x0000ffff
#define M3_BASELINE_VERSION 0x191
#define M3_STATUS_RESP_MASK (0xffff << 16)
#define M3_FW_VERSION_MASK 0xffff
#define M3_WAKE_SRC_MASK 0xff
#define IPC_MEM_TYPE_SHIFT (0x0)
#define IPC_MEM_TYPE_MASK (0x7 << 0)
#define IPC_VTT_STAT_SHIFT (0x3)
#define IPC_VTT_STAT_MASK (0x1 << 3)
#define IPC_VTT_GPIO_PIN_SHIFT (0x4)
#define IPC_VTT_GPIO_PIN_MASK (0x3f << 4)
#define IPC_IO_ISOLATION_STAT_SHIFT (10)
#define IPC_IO_ISOLATION_STAT_MASK (0x1 << 10)
#define IPC_DBG_HALT_SHIFT (11)
#define IPC_DBG_HALT_MASK (0x1 << 11)
#define M3_STATE_UNKNOWN 0
#define M3_STATE_RESET 1
#define M3_STATE_INITED 2
#define M3_STATE_MSG_FOR_LP 3
#define M3_STATE_MSG_FOR_RESET 4
#define WKUP_M3_SD_FW_MAGIC 0x570C
#define WKUP_M3_DMEM_START 0x80000
#define WKUP_M3_AUXDATA_OFFSET 0x1000
#define WKUP_M3_AUXDATA_SIZE 0xFF
static struct wkup_m3_ipc *m3_ipc_state;
static const struct wkup_m3_wakeup_src wakeups[] = {
{.irq_nr = 16, .src = "PRCM"},
{.irq_nr = 35, .src = "USB0_PHY"},
{.irq_nr = 36, .src = "USB1_PHY"},
{.irq_nr = 40, .src = "I2C0"},
{.irq_nr = 41, .src = "RTC Timer"},
{.irq_nr = 42, .src = "RTC Alarm"},
{.irq_nr = 43, .src = "Timer0"},
{.irq_nr = 44, .src = "Timer1"},
{.irq_nr = 45, .src = "UART"},
{.irq_nr = 46, .src = "GPIO0"},
{.irq_nr = 48, .src = "MPU_WAKE"},
{.irq_nr = 49, .src = "WDT0"},
{.irq_nr = 50, .src = "WDT1"},
{.irq_nr = 51, .src = "ADC_TSC"},
{.irq_nr = 0, .src = "Unknown"},
};
/**
* wkup_m3_copy_aux_data - Copy auxiliary data to special region of m3 dmem
* @data - pointer to data
* @sz - size of data to copy (limit 256 bytes)
*
* Copies any additional blob of data to the wkup_m3 dmem to be used by the
* firmware
*/
static unsigned long wkup_m3_copy_aux_data(struct wkup_m3_ipc *m3_ipc,
const void *data, int sz)
{
unsigned long aux_data_dev_addr;
void *aux_data_addr;
aux_data_dev_addr = WKUP_M3_DMEM_START + WKUP_M3_AUXDATA_OFFSET;
aux_data_addr = rproc_da_to_va(m3_ipc->rproc,
aux_data_dev_addr,
WKUP_M3_AUXDATA_SIZE,
NULL);
memcpy(aux_data_addr, data, sz);
return WKUP_M3_AUXDATA_OFFSET;
}
static void wkup_m3_scale_data_fw_cb(const struct firmware *fw, void *context)
{
unsigned long val, aux_base;
struct wkup_m3_scale_data_header hdr;
struct wkup_m3_ipc *m3_ipc = context;
struct device *dev = m3_ipc->dev;
if (!fw) {
dev_err(dev, "Voltage scale fw name given but file missing.\n");
return;
}
memcpy(&hdr, fw->data, sizeof(hdr));
if (hdr.magic != WKUP_M3_SD_FW_MAGIC) {
dev_err(dev, "PM: Voltage Scale Data binary does not appear valid.\n");
goto release_sd_fw;
}
aux_base = wkup_m3_copy_aux_data(m3_ipc, fw->data + sizeof(hdr),
fw->size - sizeof(hdr));
val = (aux_base + hdr.sleep_offset);
val |= ((aux_base + hdr.wake_offset) << 16);
m3_ipc->volt_scale_offsets = val;
release_sd_fw:
release_firmware(fw);
};
static int wkup_m3_init_scale_data(struct wkup_m3_ipc *m3_ipc,
struct device *dev)
{
int ret = 0;
/*
* If no name is provided, user has already been warned, pm will
* still work so return 0
*/
if (!m3_ipc->sd_fw_name)
return ret;
ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT,
m3_ipc->sd_fw_name, dev, GFP_ATOMIC,
m3_ipc, wkup_m3_scale_data_fw_cb);
return ret;
}
#ifdef CONFIG_DEBUG_FS
static void wkup_m3_set_halt_late(bool enabled)
{
if (enabled)
m3_ipc_state->halt = (1 << IPC_DBG_HALT_SHIFT);
else
m3_ipc_state->halt = 0;
}
static int option_get(void *data, u64 *val)
{
u32 *option = data;
*val = *option;
return 0;
}
static int option_set(void *data, u64 val)
{
u32 *option = data;
*option = val;
if (option == &m3_ipc_state->halt) {
if (val)
wkup_m3_set_halt_late(true);
else
wkup_m3_set_halt_late(false);
}
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(wkup_m3_ipc_option_fops, option_get, option_set,
"%llu\n");
static int wkup_m3_ipc_dbg_init(struct wkup_m3_ipc *m3_ipc)
{
m3_ipc->dbg_path = debugfs_create_dir("wkup_m3_ipc", NULL);
if (IS_ERR(m3_ipc->dbg_path))
return -EINVAL;
(void)debugfs_create_file("enable_late_halt", 0644,
m3_ipc->dbg_path,
&m3_ipc->halt,
&wkup_m3_ipc_option_fops);
return 0;
}
static inline void wkup_m3_ipc_dbg_destroy(struct wkup_m3_ipc *m3_ipc)
{
debugfs_remove_recursive(m3_ipc->dbg_path);
}
#else
static inline int wkup_m3_ipc_dbg_init(struct wkup_m3_ipc *m3_ipc)
{
return 0;
}
static inline void wkup_m3_ipc_dbg_destroy(struct wkup_m3_ipc *m3_ipc)
{
}
#endif /* CONFIG_DEBUG_FS */
static void am33xx_txev_eoi(struct wkup_m3_ipc *m3_ipc)
{
writel(AM33XX_M3_TXEV_ACK,
m3_ipc->ipc_mem_base + AM33XX_CONTROL_M3_TXEV_EOI);
}
static void am33xx_txev_enable(struct wkup_m3_ipc *m3_ipc)
{
writel(AM33XX_M3_TXEV_ENABLE,
m3_ipc->ipc_mem_base + AM33XX_CONTROL_M3_TXEV_EOI);
}
static void wkup_m3_ctrl_ipc_write(struct wkup_m3_ipc *m3_ipc,
u32 val, int ipc_reg_num)
{
if (WARN(ipc_reg_num < 0 || ipc_reg_num > AM33XX_CTRL_IPC_REG_COUNT,
"ipc register operation out of range"))
return;
writel(val, m3_ipc->ipc_mem_base +
AM33XX_CTRL_IPC_REG_OFFSET(ipc_reg_num));
}
static unsigned int wkup_m3_ctrl_ipc_read(struct wkup_m3_ipc *m3_ipc,
int ipc_reg_num)
{
if (WARN(ipc_reg_num < 0 || ipc_reg_num > AM33XX_CTRL_IPC_REG_COUNT,
"ipc register operation out of range"))
return 0;
return readl(m3_ipc->ipc_mem_base +
AM33XX_CTRL_IPC_REG_OFFSET(ipc_reg_num));
}
static int wkup_m3_fw_version_read(struct wkup_m3_ipc *m3_ipc)
{
int val;
val = wkup_m3_ctrl_ipc_read(m3_ipc, 2);
return val & M3_FW_VERSION_MASK;
}
static irqreturn_t wkup_m3_txev_handler(int irq, void *ipc_data)
{
struct wkup_m3_ipc *m3_ipc = ipc_data;
struct device *dev = m3_ipc->dev;
int ver = 0;
am33xx_txev_eoi(m3_ipc);
switch (m3_ipc->state) {
case M3_STATE_RESET:
ver = wkup_m3_fw_version_read(m3_ipc);
if (ver == M3_VERSION_UNKNOWN ||
ver < M3_BASELINE_VERSION) {
dev_warn(dev, "CM3 Firmware Version %x not supported\n",
ver);
} else {
dev_info(dev, "CM3 Firmware Version = 0x%x\n", ver);
}
m3_ipc->state = M3_STATE_INITED;
wkup_m3_init_scale_data(m3_ipc, dev);
complete(&m3_ipc->sync_complete);
break;
case M3_STATE_MSG_FOR_RESET:
m3_ipc->state = M3_STATE_INITED;
complete(&m3_ipc->sync_complete);
break;
case M3_STATE_MSG_FOR_LP:
complete(&m3_ipc->sync_complete);
break;
case M3_STATE_UNKNOWN:
dev_warn(dev, "Unknown CM3 State\n");
}
am33xx_txev_enable(m3_ipc);
return IRQ_HANDLED;
}
static int wkup_m3_ping(struct wkup_m3_ipc *m3_ipc)
{
struct device *dev = m3_ipc->dev;
int ret;
if (!m3_ipc->mbox) {
dev_err(dev,
"No IPC channel to communicate with wkup_m3!\n");
return -EIO;
}
/*
* Write a dummy message to the mailbox in order to trigger the RX
* interrupt to alert the M3 that data is available in the IPC
* registers. We must enable the IRQ here and disable it after in
* the RX callback to avoid multiple interrupts being received
* by the CM3.
*/
ret = mbox_send_message(m3_ipc->mbox, NULL);
if (ret < 0) {
dev_err(dev, "%s: mbox_send_message() failed: %d\n",
__func__, ret);
return ret;
}
ret = wait_for_completion_timeout(&m3_ipc->sync_complete,
msecs_to_jiffies(500));
if (!ret) {
dev_err(dev, "MPU<->CM3 sync failure\n");
m3_ipc->state = M3_STATE_UNKNOWN;
return -EIO;
}
mbox_client_txdone(m3_ipc->mbox, 0);
return 0;
}
static int wkup_m3_ping_noirq(struct wkup_m3_ipc *m3_ipc)
{
struct device *dev = m3_ipc->dev;
int ret;
if (!m3_ipc->mbox) {
dev_err(dev,
"No IPC channel to communicate with wkup_m3!\n");
return -EIO;
}
ret = mbox_send_message(m3_ipc->mbox, NULL);
if (ret < 0) {
dev_err(dev, "%s: mbox_send_message() failed: %d\n",
__func__, ret);
return ret;
}
mbox_client_txdone(m3_ipc->mbox, 0);
return 0;
}
static int wkup_m3_is_available(struct wkup_m3_ipc *m3_ipc)
{
return ((m3_ipc->state != M3_STATE_RESET) &&
(m3_ipc->state != M3_STATE_UNKNOWN));
}
static void wkup_m3_set_vtt_gpio(struct wkup_m3_ipc *m3_ipc, int gpio)
{
m3_ipc->vtt_conf = (1 << IPC_VTT_STAT_SHIFT) |
(gpio << IPC_VTT_GPIO_PIN_SHIFT);
}
static void wkup_m3_set_io_isolation(struct wkup_m3_ipc *m3_ipc)
{
m3_ipc->isolation_conf = (1 << IPC_IO_ISOLATION_STAT_SHIFT);
}
/* Public functions */
/**
* wkup_m3_set_mem_type - Pass wkup_m3 which type of memory is in use
* @m3_ipc: Pointer to wkup_m3_ipc context
* @mem_type: memory type value read directly from emif
*
* wkup_m3 must know what memory type is in use to properly suspend
* and resume.
*/
static void wkup_m3_set_mem_type(struct wkup_m3_ipc *m3_ipc, int mem_type)
{
m3_ipc->mem_type = mem_type;
}
/**
* wkup_m3_set_resume_address - Pass wkup_m3 resume address
* @m3_ipc: Pointer to wkup_m3_ipc context
* @addr: Physical address from which resume code should execute
*/
static void wkup_m3_set_resume_address(struct wkup_m3_ipc *m3_ipc, void *addr)
{
m3_ipc->resume_addr = (unsigned long)addr;
}
/**
* wkup_m3_request_pm_status - Retrieve wkup_m3 status code after suspend
* @m3_ipc: Pointer to wkup_m3_ipc context
*
* Returns code representing the status of a low power mode transition.
* 0 - Successful transition
* 1 - Failure to transition to low power state
*/
static int wkup_m3_request_pm_status(struct wkup_m3_ipc *m3_ipc)
{
unsigned int i;
int val;
val = wkup_m3_ctrl_ipc_read(m3_ipc, 1);
i = M3_STATUS_RESP_MASK & val;
i >>= __ffs(M3_STATUS_RESP_MASK);
return i;
}
/**
* wkup_m3_prepare_low_power - Request preparation for transition to
* low power state
* @m3_ipc: Pointer to wkup_m3_ipc context
* @state: A kernel suspend state to enter, either MEM or STANDBY
*
* Returns 0 if preparation was successful, otherwise returns error code
*/
static int wkup_m3_prepare_low_power(struct wkup_m3_ipc *m3_ipc, int state)
{
struct device *dev = m3_ipc->dev;
int m3_power_state;
int ret = 0;
if (!wkup_m3_is_available(m3_ipc))
return -ENODEV;
switch (state) {
case WKUP_M3_DEEPSLEEP:
m3_power_state = IPC_CMD_DS0;
wkup_m3_ctrl_ipc_write(m3_ipc, m3_ipc->volt_scale_offsets, 5);
break;
case WKUP_M3_STANDBY:
m3_power_state = IPC_CMD_STANDBY;
wkup_m3_ctrl_ipc_write(m3_ipc, DS_IPC_DEFAULT, 5);
break;
case WKUP_M3_IDLE:
m3_power_state = IPC_CMD_IDLE;
wkup_m3_ctrl_ipc_write(m3_ipc, DS_IPC_DEFAULT, 5);
break;
default:
return 1;
}
/* Program each required IPC register then write defaults to others */
wkup_m3_ctrl_ipc_write(m3_ipc, m3_ipc->resume_addr, 0);
wkup_m3_ctrl_ipc_write(m3_ipc, m3_power_state, 1);
wkup_m3_ctrl_ipc_write(m3_ipc, m3_ipc->mem_type |
m3_ipc->vtt_conf |
m3_ipc->isolation_conf |
m3_ipc->halt, 4);
wkup_m3_ctrl_ipc_write(m3_ipc, DS_IPC_DEFAULT, 2);
wkup_m3_ctrl_ipc_write(m3_ipc, DS_IPC_DEFAULT, 3);
wkup_m3_ctrl_ipc_write(m3_ipc, DS_IPC_DEFAULT, 6);
wkup_m3_ctrl_ipc_write(m3_ipc, DS_IPC_DEFAULT, 7);
m3_ipc->state = M3_STATE_MSG_FOR_LP;
if (state == WKUP_M3_IDLE)
ret = wkup_m3_ping_noirq(m3_ipc);
else
ret = wkup_m3_ping(m3_ipc);
if (ret) {
dev_err(dev, "Unable to ping CM3\n");
return ret;
}
return 0;
}
/**
* wkup_m3_finish_low_power - Return m3 to reset state
* @m3_ipc: Pointer to wkup_m3_ipc context
*
* Returns 0 if reset was successful, otherwise returns error code
*/
static int wkup_m3_finish_low_power(struct wkup_m3_ipc *m3_ipc)
{
struct device *dev = m3_ipc->dev;
int ret = 0;
if (!wkup_m3_is_available(m3_ipc))
return -ENODEV;
wkup_m3_ctrl_ipc_write(m3_ipc, IPC_CMD_RESET, 1);
wkup_m3_ctrl_ipc_write(m3_ipc, DS_IPC_DEFAULT, 2);
m3_ipc->state = M3_STATE_MSG_FOR_RESET;
ret = wkup_m3_ping(m3_ipc);
if (ret) {
dev_err(dev, "Unable to ping CM3\n");
return ret;
}
return 0;
}
/**
* wkup_m3_request_wake_src - Get the wakeup source info passed from wkup_m3
* @m3_ipc: Pointer to wkup_m3_ipc context
*/
static const char *wkup_m3_request_wake_src(struct wkup_m3_ipc *m3_ipc)
{
unsigned int wakeup_src_idx;
int j, val;
val = wkup_m3_ctrl_ipc_read(m3_ipc, 6);
wakeup_src_idx = val & M3_WAKE_SRC_MASK;
for (j = 0; j < ARRAY_SIZE(wakeups) - 1; j++) {
if (wakeups[j].irq_nr == wakeup_src_idx)
return wakeups[j].src;
}
return wakeups[j].src;
}
/**
* wkup_m3_set_rtc_only - Set the rtc_only flag
* @m3_ipc: Pointer to wkup_m3_ipc context
*/
static void wkup_m3_set_rtc_only(struct wkup_m3_ipc *m3_ipc)
{
if (m3_ipc_state)
m3_ipc_state->is_rtc_only = true;
}
static struct wkup_m3_ipc_ops ipc_ops = {
.set_mem_type = wkup_m3_set_mem_type,
.set_resume_address = wkup_m3_set_resume_address,
.prepare_low_power = wkup_m3_prepare_low_power,
.finish_low_power = wkup_m3_finish_low_power,
.request_pm_status = wkup_m3_request_pm_status,
.request_wake_src = wkup_m3_request_wake_src,
.set_rtc_only = wkup_m3_set_rtc_only,
};
/**
* wkup_m3_ipc_get - Return handle to wkup_m3_ipc
*
* Returns NULL if the wkup_m3 is not yet available, otherwise returns
* pointer to wkup_m3_ipc struct.
*/
struct wkup_m3_ipc *wkup_m3_ipc_get(void)
{
if (m3_ipc_state)
get_device(m3_ipc_state->dev);
else
return NULL;
return m3_ipc_state;
}
EXPORT_SYMBOL_GPL(wkup_m3_ipc_get);
/**
* wkup_m3_ipc_put - Free handle to wkup_m3_ipc returned from wkup_m3_ipc_get
* @m3_ipc: A pointer to wkup_m3_ipc struct returned by wkup_m3_ipc_get
*/
void wkup_m3_ipc_put(struct wkup_m3_ipc *m3_ipc)
{
if (m3_ipc_state)
put_device(m3_ipc_state->dev);
}
EXPORT_SYMBOL_GPL(wkup_m3_ipc_put);
static int wkup_m3_rproc_boot_thread(void *arg)
{
struct wkup_m3_ipc *m3_ipc = arg;
struct device *dev = m3_ipc->dev;
int ret;
init_completion(&m3_ipc->sync_complete);
ret = rproc_boot(m3_ipc->rproc);
if (ret)
dev_err(dev, "rproc_boot failed\n");
else
m3_ipc_state = m3_ipc;
return 0;
}
static int wkup_m3_ipc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int irq, ret, temp;
phandle rproc_phandle;
struct rproc *m3_rproc;
struct task_struct *task;
struct wkup_m3_ipc *m3_ipc;
struct device_node *np = dev->of_node;
m3_ipc = devm_kzalloc(dev, sizeof(*m3_ipc), GFP_KERNEL);
if (!m3_ipc)
return -ENOMEM;
m3_ipc->ipc_mem_base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(m3_ipc->ipc_mem_base))
return PTR_ERR(m3_ipc->ipc_mem_base);
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
ret = devm_request_irq(dev, irq, wkup_m3_txev_handler,
0, "wkup_m3_txev", m3_ipc);
if (ret) {
dev_err(dev, "request_irq failed\n");
return ret;
}
m3_ipc->mbox_client.dev = dev;
m3_ipc->mbox_client.tx_done = NULL;
m3_ipc->mbox_client.tx_prepare = NULL;
m3_ipc->mbox_client.rx_callback = NULL;
m3_ipc->mbox_client.tx_block = false;
m3_ipc->mbox_client.knows_txdone = false;
m3_ipc->mbox = mbox_request_channel(&m3_ipc->mbox_client, 0);
if (IS_ERR(m3_ipc->mbox))
return dev_err_probe(dev, PTR_ERR(m3_ipc->mbox),
"IPC Request for A8->M3 Channel failed!\n");
if (of_property_read_u32(dev->of_node, "ti,rproc", &rproc_phandle)) {
dev_err(&pdev->dev, "could not get rproc phandle\n");
ret = -ENODEV;
goto err_free_mbox;
}
m3_rproc = rproc_get_by_phandle(rproc_phandle);
if (!m3_rproc) {
dev_err(&pdev->dev, "could not get rproc handle\n");
ret = -EPROBE_DEFER;
goto err_free_mbox;
}
m3_ipc->rproc = m3_rproc;
m3_ipc->dev = dev;
m3_ipc->state = M3_STATE_RESET;
m3_ipc->ops = &ipc_ops;
if (!of_property_read_u32(np, "ti,vtt-gpio-pin", &temp)) {
if (temp >= 0 && temp <= 31)
wkup_m3_set_vtt_gpio(m3_ipc, temp);
else
dev_warn(dev, "Invalid VTT GPIO(%d) pin\n", temp);
}
if (of_property_read_bool(np, "ti,set-io-isolation"))
wkup_m3_set_io_isolation(m3_ipc);
ret = of_property_read_string(np, "firmware-name",
&m3_ipc->sd_fw_name);
if (ret) {
dev_dbg(dev, "Voltage scaling data blob not provided from DT.\n");
}
/*
* Wait for firmware loading completion in a thread so we
* can boot the wkup_m3 as soon as it's ready without holding
* up kernel boot
*/
task = kthread_run(wkup_m3_rproc_boot_thread, m3_ipc,
"wkup_m3_rproc_loader");
if (IS_ERR(task)) {
dev_err(dev, "can't create rproc_boot thread\n");
ret = PTR_ERR(task);
goto err_put_rproc;
}
wkup_m3_ipc_dbg_init(m3_ipc);
return 0;
err_put_rproc:
rproc_put(m3_rproc);
err_free_mbox:
mbox_free_channel(m3_ipc->mbox);
return ret;
}
static void wkup_m3_ipc_remove(struct platform_device *pdev)
{
wkup_m3_ipc_dbg_destroy(m3_ipc_state);
mbox_free_channel(m3_ipc_state->mbox);
rproc_shutdown(m3_ipc_state->rproc);
rproc_put(m3_ipc_state->rproc);
m3_ipc_state = NULL;
}
static int __maybe_unused wkup_m3_ipc_suspend(struct device *dev)
{
/*
* Nothing needs to be done on suspend even with rtc_only flag set
*/
return 0;
}
static int __maybe_unused wkup_m3_ipc_resume(struct device *dev)
{
if (m3_ipc_state->is_rtc_only) {
rproc_shutdown(m3_ipc_state->rproc);
rproc_boot(m3_ipc_state->rproc);
}
m3_ipc_state->is_rtc_only = false;
return 0;
}
static const struct dev_pm_ops wkup_m3_ipc_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(wkup_m3_ipc_suspend, wkup_m3_ipc_resume)
};
static const struct of_device_id wkup_m3_ipc_of_match[] = {
{ .compatible = "ti,am3352-wkup-m3-ipc", },
{ .compatible = "ti,am4372-wkup-m3-ipc", },
{},
};
MODULE_DEVICE_TABLE(of, wkup_m3_ipc_of_match);
static struct platform_driver wkup_m3_ipc_driver = {
.probe = wkup_m3_ipc_probe,
.remove = wkup_m3_ipc_remove,
.driver = {
.name = "wkup_m3_ipc",
.of_match_table = wkup_m3_ipc_of_match,
.pm = &wkup_m3_ipc_pm_ops,
},
};
module_platform_driver(wkup_m3_ipc_driver);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("wkup m3 remote processor ipc driver");
MODULE_AUTHOR("Dave Gerlach <d-gerlach@ti.com>");