diff options
Diffstat (limited to 'drivers/soundwire')
-rw-r--r-- | drivers/soundwire/Kconfig | 12 | ||||
-rw-r--r-- | drivers/soundwire/Makefile | 8 | ||||
-rw-r--r-- | drivers/soundwire/bus.c | 82 | ||||
-rw-r--r-- | drivers/soundwire/bus.h | 24 | ||||
-rw-r--r-- | drivers/soundwire/bus_type.c | 3 | ||||
-rw-r--r-- | drivers/soundwire/cadence_master.c | 543 | ||||
-rw-r--r-- | drivers/soundwire/cadence_master.h | 45 | ||||
-rw-r--r-- | drivers/soundwire/debugfs.c | 151 | ||||
-rw-r--r-- | drivers/soundwire/intel.c | 433 | ||||
-rw-r--r-- | drivers/soundwire/intel.h | 13 | ||||
-rw-r--r-- | drivers/soundwire/intel_init.c | 42 | ||||
-rw-r--r-- | drivers/soundwire/mipi_disco.c | 18 | ||||
-rw-r--r-- | drivers/soundwire/qcom.c | 861 | ||||
-rw-r--r-- | drivers/soundwire/slave.c | 152 | ||||
-rw-r--r-- | drivers/soundwire/stream.c | 113 |
15 files changed, 1999 insertions, 501 deletions
diff --git a/drivers/soundwire/Kconfig b/drivers/soundwire/Kconfig index f518273cfbe3..fa2b4ab92ed9 100644 --- a/drivers/soundwire/Kconfig +++ b/drivers/soundwire/Kconfig @@ -5,6 +5,7 @@ menuconfig SOUNDWIRE tristate "SoundWire support" + depends on ACPI || OF help SoundWire is a 2-Pin interface with data and clock line ratified by the MIPI Alliance. SoundWire is used for transporting data @@ -23,11 +24,20 @@ config SOUNDWIRE_CADENCE config SOUNDWIRE_INTEL tristate "Intel SoundWire Master driver" select SOUNDWIRE_CADENCE - depends on X86 && ACPI && SND_SOC + depends on ACPI && SND_SOC help SoundWire Intel Master driver. If you have an Intel platform which has a SoundWire Master then enable this config option to get the SoundWire support for that device. +config SOUNDWIRE_QCOM + tristate "Qualcomm SoundWire Master driver" + depends on SLIMBUS + depends on SND_SOC + help + SoundWire Qualcomm Master driver. + If you have an Qualcomm platform which has a SoundWire Master then + enable this config option to get the SoundWire support for that + device endif diff --git a/drivers/soundwire/Makefile b/drivers/soundwire/Makefile index 45b7e5001653..e2cdff990e9f 100644 --- a/drivers/soundwire/Makefile +++ b/drivers/soundwire/Makefile @@ -7,6 +7,10 @@ soundwire-bus-objs := bus_type.o bus.o slave.o mipi_disco.o stream.o obj-$(CONFIG_SOUNDWIRE) += soundwire-bus.o +ifdef CONFIG_DEBUG_FS +soundwire-bus-objs += debugfs.o +endif + #Cadence Objs soundwire-cadence-objs := cadence_master.o obj-$(CONFIG_SOUNDWIRE_CADENCE) += soundwire-cadence.o @@ -17,3 +21,7 @@ obj-$(CONFIG_SOUNDWIRE_INTEL) += soundwire-intel.o soundwire-intel-init-objs := intel_init.o obj-$(CONFIG_SOUNDWIRE_INTEL) += soundwire-intel-init.o + +#Qualcomm driver +soundwire-qcom-objs := qcom.o +obj-$(CONFIG_SOUNDWIRE_QCOM) += soundwire-qcom.o diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c index fe745830a261..6106577fb3ed 100644 --- a/drivers/soundwire/bus.c +++ b/drivers/soundwire/bus.c @@ -49,6 +49,8 @@ int sdw_add_bus_master(struct sdw_bus *bus) } } + sdw_bus_debugfs_init(bus); + /* * Device numbers in SoundWire are 0 through 15. Enumeration device * number (0), Broadcast device number (15), Group numbers (12 and @@ -77,6 +79,8 @@ int sdw_add_bus_master(struct sdw_bus *bus) */ if (IS_ENABLED(CONFIG_ACPI) && ACPI_HANDLE(bus->dev)) ret = sdw_acpi_find_slaves(bus); + else if (IS_ENABLED(CONFIG_OF) && bus->dev->of_node) + ret = sdw_of_find_slaves(bus); else ret = -ENOTSUPP; /* No ACPI/DT so error out */ @@ -109,6 +113,8 @@ static int sdw_delete_slave(struct device *dev, void *data) struct sdw_slave *slave = dev_to_sdw_dev(dev); struct sdw_bus *bus = slave->bus; + sdw_slave_debugfs_exit(slave); + mutex_lock(&bus->bus_lock); if (slave->dev_num) /* clear dev_num if assigned */ @@ -130,6 +136,8 @@ static int sdw_delete_slave(struct device *dev, void *data) void sdw_delete_bus_master(struct sdw_bus *bus) { device_for_each_child(bus->dev, NULL, sdw_delete_slave); + + sdw_bus_debugfs_exit(bus); } EXPORT_SYMBOL(sdw_delete_bus_master); @@ -414,10 +422,11 @@ static struct sdw_slave *sdw_get_slave(struct sdw_bus *bus, int i) static int sdw_compare_devid(struct sdw_slave *slave, struct sdw_slave_id id) { - if (slave->id.unique_id != id.unique_id || - slave->id.mfg_id != id.mfg_id || + if (slave->id.mfg_id != id.mfg_id || slave->id.part_id != id.part_id || - slave->id.class_id != id.class_id) + slave->id.class_id != id.class_id || + (slave->id.unique_id != SDW_IGNORED_UNIQUE_ID && + slave->id.unique_id != id.unique_id)) return -ENODEV; return 0; @@ -447,35 +456,45 @@ err: static int sdw_assign_device_num(struct sdw_slave *slave) { int ret, dev_num; + bool new_device = false; /* check first if device number is assigned, if so reuse that */ if (!slave->dev_num) { - mutex_lock(&slave->bus->bus_lock); - dev_num = sdw_get_device_num(slave); - mutex_unlock(&slave->bus->bus_lock); - if (dev_num < 0) { - dev_err(slave->bus->dev, "Get dev_num failed: %d\n", - dev_num); - return dev_num; + if (!slave->dev_num_sticky) { + mutex_lock(&slave->bus->bus_lock); + dev_num = sdw_get_device_num(slave); + mutex_unlock(&slave->bus->bus_lock); + if (dev_num < 0) { + dev_err(slave->bus->dev, "Get dev_num failed: %d\n", + dev_num); + return dev_num; + } + slave->dev_num = dev_num; + slave->dev_num_sticky = dev_num; + new_device = true; + } else { + slave->dev_num = slave->dev_num_sticky; } - } else { + } + + if (!new_device) dev_info(slave->bus->dev, - "Slave already registered dev_num:%d\n", + "Slave already registered, reusing dev_num:%d\n", slave->dev_num); - /* Clear the slave->dev_num to transfer message on device 0 */ - dev_num = slave->dev_num; - slave->dev_num = 0; - } + /* Clear the slave->dev_num to transfer message on device 0 */ + dev_num = slave->dev_num; + slave->dev_num = 0; ret = sdw_write(slave, SDW_SCP_DEVNUMBER, dev_num); if (ret < 0) { - dev_err(&slave->dev, "Program device_num failed: %d\n", ret); + dev_err(&slave->dev, "Program device_num %d failed: %d\n", + dev_num, ret); return ret; } /* After xfer of msg, restore dev_num */ - slave->dev_num = dev_num; + slave->dev_num = slave->dev_num_sticky; return 0; } @@ -527,6 +546,7 @@ static int sdw_program_device_num(struct sdw_bus *bus) do { ret = sdw_transfer(bus, &msg); if (ret == -ENODATA) { /* end of device id reads */ + dev_dbg(bus->dev, "No more devices to enumerate\n"); ret = 0; break; } @@ -803,7 +823,7 @@ static int sdw_handle_port_interrupt(struct sdw_slave *slave, static int sdw_handle_slave_alerts(struct sdw_slave *slave) { struct sdw_slave_intr_status slave_intr; - u8 clear = 0, bit, port_status[15]; + u8 clear = 0, bit, port_status[15] = {0}; int port_num, stat, ret, count = 0; unsigned long port; bool slave_notify = false; @@ -968,10 +988,34 @@ int sdw_handle_slave_status(struct sdw_bus *bus, struct sdw_slave *slave; int i, ret = 0; + /* first check if any Slaves fell off the bus */ + for (i = 1; i <= SDW_MAX_DEVICES; i++) { + mutex_lock(&bus->bus_lock); + if (test_bit(i, bus->assigned) == false) { + mutex_unlock(&bus->bus_lock); + continue; + } + mutex_unlock(&bus->bus_lock); + + slave = sdw_get_slave(bus, i); + if (!slave) + continue; + + if (status[i] == SDW_SLAVE_UNATTACHED && + slave->status != SDW_SLAVE_UNATTACHED) + sdw_modify_slave_status(slave, SDW_SLAVE_UNATTACHED); + } + if (status[0] == SDW_SLAVE_ATTACHED) { + dev_dbg(bus->dev, "Slave attached, programming device number\n"); ret = sdw_program_device_num(bus); if (ret) dev_err(bus->dev, "Slave attach failed: %d\n", ret); + /* + * programming a device number will have side effects, + * so we deal with other devices at a later time + */ + return ret; } /* Continue to check other slave statuses */ diff --git a/drivers/soundwire/bus.h b/drivers/soundwire/bus.h index 3048ca153f22..cb482da914da 100644 --- a/drivers/soundwire/bus.h +++ b/drivers/soundwire/bus.h @@ -15,9 +15,26 @@ static inline int sdw_acpi_find_slaves(struct sdw_bus *bus) } #endif +int sdw_of_find_slaves(struct sdw_bus *bus); void sdw_extract_slave_id(struct sdw_bus *bus, u64 addr, struct sdw_slave_id *id); +#ifdef CONFIG_DEBUG_FS +void sdw_bus_debugfs_init(struct sdw_bus *bus); +void sdw_bus_debugfs_exit(struct sdw_bus *bus); +void sdw_slave_debugfs_init(struct sdw_slave *slave); +void sdw_slave_debugfs_exit(struct sdw_slave *slave); +void sdw_debugfs_init(void); +void sdw_debugfs_exit(void); +#else +static inline void sdw_bus_debugfs_init(struct sdw_bus *bus) {} +static inline void sdw_bus_debugfs_exit(struct sdw_bus *bus) {} +static inline void sdw_slave_debugfs_init(struct sdw_slave *slave) {} +static inline void sdw_slave_debugfs_exit(struct sdw_slave *slave) {} +static inline void sdw_debugfs_init(void) {} +static inline void sdw_debugfs_exit(void) {} +#endif + enum { SDW_MSG_FLAG_READ = 0, SDW_MSG_FLAG_WRITE, @@ -49,8 +66,11 @@ struct sdw_msg { #define SDW_DOUBLE_RATE_FACTOR 2 -extern int rows[SDW_FRAME_ROWS]; -extern int cols[SDW_FRAME_COLS]; +extern int sdw_rows[SDW_FRAME_ROWS]; +extern int sdw_cols[SDW_FRAME_COLS]; + +int sdw_find_row_index(int row); +int sdw_find_col_index(int col); /** * sdw_port_runtime: Runtime port parameters for Master or Slave diff --git a/drivers/soundwire/bus_type.c b/drivers/soundwire/bus_type.c index 2655602f0cfb..4a465f55039f 100644 --- a/drivers/soundwire/bus_type.c +++ b/drivers/soundwire/bus_type.c @@ -6,6 +6,7 @@ #include <linux/pm_domain.h> #include <linux/soundwire/sdw.h> #include <linux/soundwire/sdw_type.h> +#include "bus.h" /** * sdw_get_device_id - find the matching SoundWire device id @@ -177,11 +178,13 @@ EXPORT_SYMBOL_GPL(sdw_unregister_driver); static int __init sdw_bus_init(void) { + sdw_debugfs_init(); return bus_register(&sdw_bus_type); } static void __exit sdw_bus_exit(void) { + sdw_debugfs_exit(); bus_unregister(&sdw_bus_type); } diff --git a/drivers/soundwire/cadence_master.c b/drivers/soundwire/cadence_master.c index 60e8bdee5c75..9bec270d0fa4 100644 --- a/drivers/soundwire/cadence_master.c +++ b/drivers/soundwire/cadence_master.c @@ -8,6 +8,7 @@ #include <linux/delay.h> #include <linux/device.h> +#include <linux/debugfs.h> #include <linux/interrupt.h> #include <linux/io.h> #include <linux/module.h> @@ -19,6 +20,10 @@ #include "bus.h" #include "cadence_master.h" +static int interrupt_mask; +module_param_named(cnds_mcp_int_mask, interrupt_mask, int, 0444); +MODULE_PARM_DESC(cdns_mcp_int_mask, "Cadence MCP IntMask"); + #define CDNS_MCP_CONFIG 0x0 #define CDNS_MCP_CONFIG_MCMD_RETRY GENMASK(27, 24) @@ -47,6 +52,8 @@ #define CDNS_MCP_SSPSTAT 0xC #define CDNS_MCP_FRAME_SHAPE 0x10 #define CDNS_MCP_FRAME_SHAPE_INIT 0x14 +#define CDNS_MCP_FRAME_SHAPE_COL_MASK GENMASK(2, 0) +#define CDNS_MCP_FRAME_SHAPE_ROW_OFFSET 3 #define CDNS_MCP_CONFIG_UPDATE 0x18 #define CDNS_MCP_CONFIG_UPDATE_BIT BIT(0) @@ -56,6 +63,7 @@ #define CDNS_MCP_SSP_CTRL1 0x28 #define CDNS_MCP_CLK_CTRL0 0x30 #define CDNS_MCP_CLK_CTRL1 0x38 +#define CDNS_MCP_CLK_MCLKD_MASK GENMASK(7, 0) #define CDNS_MCP_STAT 0x40 @@ -66,6 +74,7 @@ #define CDNS_MCP_INTMASK 0x48 #define CDNS_MCP_INT_IRQ BIT(31) +#define CDNS_MCP_INT_RESERVED1 GENMASK(30, 17) #define CDNS_MCP_INT_WAKEUP BIT(16) #define CDNS_MCP_INT_SLAVE_RSVD BIT(15) #define CDNS_MCP_INT_SLAVE_ALERT BIT(14) @@ -75,9 +84,14 @@ #define CDNS_MCP_INT_DPINT BIT(11) #define CDNS_MCP_INT_CTRL_CLASH BIT(10) #define CDNS_MCP_INT_DATA_CLASH BIT(9) +#define CDNS_MCP_INT_PARITY BIT(8) #define CDNS_MCP_INT_CMD_ERR BIT(7) +#define CDNS_MCP_INT_RESERVED2 GENMASK(6, 4) +#define CDNS_MCP_INT_RX_NE BIT(3) #define CDNS_MCP_INT_RX_WL BIT(2) #define CDNS_MCP_INT_TXE BIT(1) +#define CDNS_MCP_INT_TXF BIT(0) +#define CDNS_MCP_INT_RESERVED (CDNS_MCP_INT_RESERVED1 | CDNS_MCP_INT_RESERVED2) #define CDNS_MCP_INTSET 0x4C @@ -169,15 +183,9 @@ #define CDNS_PDI_CONFIG_PORT GENMASK(4, 0) /* Driver defaults */ - -#define CDNS_DEFAULT_CLK_DIVIDER 0 -#define CDNS_DEFAULT_FRAME_SHAPE 0x30 #define CDNS_DEFAULT_SSP_INTERVAL 0x18 #define CDNS_TX_TIMEOUT 2000 -#define CDNS_PCM_PDI_OFFSET 0x2 -#define CDNS_PDM_PDI_OFFSET 0x6 - #define CDNS_SCP_RX_FIFOLEVEL 0x2 /* @@ -224,6 +232,147 @@ static int cdns_clear_bit(struct sdw_cdns *cdns, int offset, u32 value) } /* + * all changes to the MCP_CONFIG, MCP_CONTROL, MCP_CMDCTRL and MCP_PHYCTRL + * need to be confirmed with a write to MCP_CONFIG_UPDATE + */ +static int cdns_update_config(struct sdw_cdns *cdns) +{ + int ret; + + ret = cdns_clear_bit(cdns, CDNS_MCP_CONFIG_UPDATE, + CDNS_MCP_CONFIG_UPDATE_BIT); + if (ret < 0) + dev_err(cdns->dev, "Config update timedout\n"); + + return ret; +} + +/* + * debugfs + */ +#ifdef CONFIG_DEBUG_FS + +#define RD_BUF (2 * PAGE_SIZE) + +static ssize_t cdns_sprintf(struct sdw_cdns *cdns, + char *buf, size_t pos, unsigned int reg) +{ + return scnprintf(buf + pos, RD_BUF - pos, + "%4x\t%8x\n", reg, cdns_readl(cdns, reg)); +} + +static int cdns_reg_show(struct seq_file *s, void *data) +{ + struct sdw_cdns *cdns = s->private; + char *buf; + ssize_t ret; + int num_ports; + int i, j; + + buf = kzalloc(RD_BUF, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = scnprintf(buf, RD_BUF, "Register Value\n"); + ret += scnprintf(buf + ret, RD_BUF - ret, "\nMCP Registers\n"); + /* 8 MCP registers */ + for (i = CDNS_MCP_CONFIG; i <= CDNS_MCP_PHYCTRL; i += sizeof(u32)) + ret += cdns_sprintf(cdns, buf, ret, i); + + ret += scnprintf(buf + ret, RD_BUF - ret, + "\nStatus & Intr Registers\n"); + /* 13 Status & Intr registers (offsets 0x70 and 0x74 not defined) */ + for (i = CDNS_MCP_STAT; i <= CDNS_MCP_FIFOSTAT; i += sizeof(u32)) + ret += cdns_sprintf(cdns, buf, ret, i); + + ret += scnprintf(buf + ret, RD_BUF - ret, + "\nSSP & Clk ctrl Registers\n"); + ret += cdns_sprintf(cdns, buf, ret, CDNS_MCP_SSP_CTRL0); + ret += cdns_sprintf(cdns, buf, ret, CDNS_MCP_SSP_CTRL1); + ret += cdns_sprintf(cdns, buf, ret, CDNS_MCP_CLK_CTRL0); + ret += cdns_sprintf(cdns, buf, ret, CDNS_MCP_CLK_CTRL1); + + ret += scnprintf(buf + ret, RD_BUF - ret, + "\nDPn B0 Registers\n"); + + num_ports = cdns->num_ports; + + for (i = 0; i < num_ports; i++) { + ret += scnprintf(buf + ret, RD_BUF - ret, + "\nDP-%d\n", i); + for (j = CDNS_DPN_B0_CONFIG(i); + j < CDNS_DPN_B0_ASYNC_CTRL(i); j += sizeof(u32)) + ret += cdns_sprintf(cdns, buf, ret, j); + } + + ret += scnprintf(buf + ret, RD_BUF - ret, + "\nDPn B1 Registers\n"); + for (i = 0; i < num_ports; i++) { + ret += scnprintf(buf + ret, RD_BUF - ret, + "\nDP-%d\n", i); + + for (j = CDNS_DPN_B1_CONFIG(i); + j < CDNS_DPN_B1_ASYNC_CTRL(i); j += sizeof(u32)) + ret += cdns_sprintf(cdns, buf, ret, j); + } + + ret += scnprintf(buf + ret, RD_BUF - ret, + "\nDPn Control Registers\n"); + for (i = 0; i < num_ports; i++) + ret += cdns_sprintf(cdns, buf, ret, + CDNS_PORTCTRL + i * CDNS_PORT_OFFSET); + + ret += scnprintf(buf + ret, RD_BUF - ret, + "\nPDIn Config Registers\n"); + + /* number of PDI and ports is interchangeable */ + for (i = 0; i < num_ports; i++) + ret += cdns_sprintf(cdns, buf, ret, CDNS_PDI_CONFIG(i)); + + seq_printf(s, "%s", buf); + kfree(buf); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(cdns_reg); + +static int cdns_hw_reset(void *data, u64 value) +{ + struct sdw_cdns *cdns = data; + int ret; + + if (value != 1) + return -EINVAL; + + /* Userspace changed the hardware state behind the kernel's back */ + add_taint(TAINT_USER, LOCKDEP_STILL_OK); + + ret = sdw_cdns_exit_reset(cdns); + + dev_dbg(cdns->dev, "link hw_reset done: %d\n", ret); + + return ret; +} + +DEFINE_DEBUGFS_ATTRIBUTE(cdns_hw_reset_fops, NULL, cdns_hw_reset, "%llu\n"); + +/** + * sdw_cdns_debugfs_init() - Cadence debugfs init + * @cdns: Cadence instance + * @root: debugfs root + */ +void sdw_cdns_debugfs_init(struct sdw_cdns *cdns, struct dentry *root) +{ + debugfs_create_file("cdns-registers", 0400, root, cdns, &cdns_reg_fops); + + debugfs_create_file("cdns-hw-reset", 0200, root, cdns, + &cdns_hw_reset_fops); +} +EXPORT_SYMBOL_GPL(sdw_cdns_debugfs_init); + +#endif /* CONFIG_DEBUG_FS */ + +/* * IO Calls */ static enum sdw_command_response @@ -298,7 +447,8 @@ _cdns_xfer_msg(struct sdw_cdns *cdns, struct sdw_msg *msg, int cmd, time = wait_for_completion_timeout(&cdns->tx_complete, msecs_to_jiffies(CDNS_TX_TIMEOUT)); if (!time) { - dev_err(cdns->dev, "IO transfer timed out\n"); + dev_err(cdns->dev, "IO transfer timed out, cmd %d device %d addr %x len %d\n", + cmd, msg->dev_num, msg->addr, msg->len); msg->len = 0; return SDW_CMD_TIMEOUT; } @@ -526,13 +676,36 @@ static int cdns_update_slave_status(struct sdw_cdns *cdns, /* first check if Slave reported multiple status */ if (set_status > 1) { + u32 val; + dev_warn_ratelimited(cdns->dev, - "Slave reported multiple Status: %d\n", - mask); - /* - * TODO: we need to reread the status here by - * issuing a PING cmd - */ + "Slave %d reported multiple Status: %d\n", + i, mask); + + /* check latest status extracted from PING commands */ + val = cdns_readl(cdns, CDNS_MCP_SLAVE_STAT); + val >>= (i * 2); + + switch (val & 0x3) { + case 0: + status[i] = SDW_SLAVE_UNATTACHED; + break; + case 1: + status[i] = SDW_SLAVE_ATTACHED; + break; + case 2: + status[i] = SDW_SLAVE_ALERT; + break; + case 3: + default: + status[i] = SDW_SLAVE_RESERVED; + break; + } + + dev_warn_ratelimited(cdns->dev, + "Slave %d status updated to %d\n", + i, status[i]); + } } @@ -559,6 +732,10 @@ irqreturn_t sdw_cdns_irq(int irq, void *dev_id) int_status = cdns_readl(cdns, CDNS_MCP_INTSTAT); + /* check for reserved values read as zero */ + if (int_status & CDNS_MCP_INT_RESERVED) + return IRQ_NONE; + if (!(int_status & CDNS_MCP_INT_IRQ)) return IRQ_NONE; @@ -575,10 +752,14 @@ irqreturn_t sdw_cdns_irq(int irq, void *dev_id) } } + if (int_status & CDNS_MCP_INT_PARITY) { + /* Parity error detected by Master */ + dev_err_ratelimited(cdns->dev, "Parity error\n"); + } + if (int_status & CDNS_MCP_INT_CTRL_CLASH) { /* Slave is driving bit slot during control word */ dev_err_ratelimited(cdns->dev, "Bus clash for control word\n"); - int_status |= CDNS_MCP_INT_CTRL_CLASH; } if (int_status & CDNS_MCP_INT_DATA_CLASH) { @@ -587,7 +768,6 @@ irqreturn_t sdw_cdns_irq(int irq, void *dev_id) * ownership of data bits or Slave gone bonkers */ dev_err_ratelimited(cdns->dev, "Bus clash for data word\n"); - int_status |= CDNS_MCP_INT_DATA_CLASH; } if (int_status & CDNS_MCP_INT_SLAVE_MASK) { @@ -635,40 +815,87 @@ EXPORT_SYMBOL(sdw_cdns_thread); /* * init routines */ -static int _cdns_enable_interrupt(struct sdw_cdns *cdns) -{ - u32 mask; - cdns_writel(cdns, CDNS_MCP_SLAVE_INTMASK0, - CDNS_MCP_SLAVE_INTMASK0_MASK); - cdns_writel(cdns, CDNS_MCP_SLAVE_INTMASK1, - CDNS_MCP_SLAVE_INTMASK1_MASK); - - mask = CDNS_MCP_INT_SLAVE_RSVD | CDNS_MCP_INT_SLAVE_ALERT | - CDNS_MCP_INT_SLAVE_ATTACH | CDNS_MCP_INT_SLAVE_NATTACH | - CDNS_MCP_INT_CTRL_CLASH | CDNS_MCP_INT_DATA_CLASH | - CDNS_MCP_INT_RX_WL | CDNS_MCP_INT_IRQ | CDNS_MCP_INT_DPINT; - - cdns_writel(cdns, CDNS_MCP_INTMASK, mask); - - return 0; +/** + * sdw_cdns_exit_reset() - Program reset parameters and start bus operations + * @cdns: Cadence instance + */ +int sdw_cdns_exit_reset(struct sdw_cdns *cdns) +{ + /* program maximum length reset to be safe */ + cdns_updatel(cdns, CDNS_MCP_CONTROL, + CDNS_MCP_CONTROL_RST_DELAY, + CDNS_MCP_CONTROL_RST_DELAY); + + /* use hardware generated reset */ + cdns_updatel(cdns, CDNS_MCP_CONTROL, + CDNS_MCP_CONTROL_HW_RST, + CDNS_MCP_CONTROL_HW_RST); + + /* enable bus operations with clock and data */ + cdns_updatel(cdns, CDNS_MCP_CONFIG, + CDNS_MCP_CONFIG_OP, + CDNS_MCP_CONFIG_OP_NORMAL); + + /* commit changes */ + return cdns_update_config(cdns); } +EXPORT_SYMBOL(sdw_cdns_exit_reset); /** - * sdw_cdns_enable_interrupt() - Enable SDW interrupts and update config + * sdw_cdns_enable_interrupt() - Enable SDW interrupts * @cdns: Cadence instance + * @state: True if we are trying to enable interrupt. */ -int sdw_cdns_enable_interrupt(struct sdw_cdns *cdns) +int sdw_cdns_enable_interrupt(struct sdw_cdns *cdns, bool state) { - int ret; + u32 slave_intmask0 = 0; + u32 slave_intmask1 = 0; + u32 mask = 0; - _cdns_enable_interrupt(cdns); - ret = cdns_clear_bit(cdns, CDNS_MCP_CONFIG_UPDATE, - CDNS_MCP_CONFIG_UPDATE_BIT); - if (ret < 0) - dev_err(cdns->dev, "Config update timedout\n"); + if (!state) + goto update_masks; - return ret; + slave_intmask0 = CDNS_MCP_SLAVE_INTMASK0_MASK; + slave_intmask1 = CDNS_MCP_SLAVE_INTMASK1_MASK; + + /* enable detection of all slave state changes */ + mask = CDNS_MCP_INT_SLAVE_MASK; + + /* enable detection of bus issues */ + mask |= CDNS_MCP_INT_CTRL_CLASH | CDNS_MCP_INT_DATA_CLASH | + CDNS_MCP_INT_PARITY; + + /* no detection of port interrupts for now */ + + /* enable detection of RX fifo level */ + mask |= CDNS_MCP_INT_RX_WL; + + /* + * CDNS_MCP_INT_IRQ needs to be set otherwise all previous + * settings are irrelevant + */ + mask |= CDNS_MCP_INT_IRQ; + + if (interrupt_mask) /* parameter override */ + mask = interrupt_mask; + +update_masks: + /* clear slave interrupt status before enabling interrupt */ + if (state) { + u32 slave_state; + + slave_state = cdns_readl(cdns, CDNS_MCP_SLAVE_INTSTAT0); + cdns_writel(cdns, CDNS_MCP_SLAVE_INTSTAT0, slave_state); + slave_state = cdns_readl(cdns, CDNS_MCP_SLAVE_INTSTAT1); + cdns_writel(cdns, CDNS_MCP_SLAVE_INTSTAT1, slave_state); + } + + cdns_writel(cdns, CDNS_MCP_SLAVE_INTMASK0, slave_intmask0); + cdns_writel(cdns, CDNS_MCP_SLAVE_INTMASK1, slave_intmask1); + cdns_writel(cdns, CDNS_MCP_INTMASK, mask); + + return 0; } EXPORT_SYMBOL(sdw_cdns_enable_interrupt); @@ -688,7 +915,6 @@ static int cdns_allocate_pdi(struct sdw_cdns *cdns, for (i = 0; i < num; i++) { pdi[i].num = i + pdi_offset; - pdi[i].assigned = false; } *stream = pdi; @@ -705,7 +931,8 @@ int sdw_cdns_pdi_init(struct sdw_cdns *cdns, struct sdw_cdns_stream_config config) { struct sdw_cdns_streams *stream; - int offset, i, ret; + int offset; + int ret; cdns->pcm.num_bd = config.pcm_bd; cdns->pcm.num_in = config.pcm_in; @@ -717,11 +944,8 @@ int sdw_cdns_pdi_init(struct sdw_cdns *cdns, /* Allocate PDIs for PCMs */ stream = &cdns->pcm; - /* First two PDIs are reserved for bulk transfers */ - if (stream->num_bd < CDNS_PCM_PDI_OFFSET) - return -EINVAL; - stream->num_bd -= CDNS_PCM_PDI_OFFSET; - offset = CDNS_PCM_PDI_OFFSET; + /* we allocate PDI0 and PDI1 which are used for Bulk */ + offset = 0; ret = cdns_allocate_pdi(cdns, &stream->bd, stream->num_bd, offset); @@ -748,7 +972,6 @@ int sdw_cdns_pdi_init(struct sdw_cdns *cdns, /* Allocate PDIs for PDMs */ stream = &cdns->pdm; - offset = CDNS_PDM_PDI_OFFSET; ret = cdns_allocate_pdi(cdns, &stream->bd, stream->num_bd, offset); if (ret) @@ -765,6 +988,7 @@ int sdw_cdns_pdi_init(struct sdw_cdns *cdns, ret = cdns_allocate_pdi(cdns, &stream->out, stream->num_out, offset); + if (ret) return ret; @@ -772,51 +996,69 @@ int sdw_cdns_pdi_init(struct sdw_cdns *cdns, stream->num_pdi = stream->num_bd + stream->num_in + stream->num_out; cdns->num_ports += stream->num_pdi; - cdns->ports = devm_kcalloc(cdns->dev, cdns->num_ports, - sizeof(*cdns->ports), GFP_KERNEL); - if (!cdns->ports) { - ret = -ENOMEM; - return ret; - } - - for (i = 0; i < cdns->num_ports; i++) { - cdns->ports[i].assigned = false; - cdns->ports[i].num = i + 1; /* Port 0 reserved for bulk */ - } - return 0; } EXPORT_SYMBOL(sdw_cdns_pdi_init); +static u32 cdns_set_initial_frame_shape(int n_rows, int n_cols) +{ + u32 val; + int c; + int r; + + r = sdw_find_row_index(n_rows); + c = sdw_find_col_index(n_cols) & CDNS_MCP_FRAME_SHAPE_COL_MASK; + + val = (r << CDNS_MCP_FRAME_SHAPE_ROW_OFFSET) | c; + + return val; +} + /** * sdw_cdns_init() - Cadence initialization * @cdns: Cadence instance */ -int sdw_cdns_init(struct sdw_cdns *cdns) +int sdw_cdns_init(struct sdw_cdns *cdns, bool clock_stop_exit) { + struct sdw_bus *bus = &cdns->bus; + struct sdw_master_prop *prop = &bus->prop; u32 val; + int divider; int ret; - /* Exit clock stop */ - ret = cdns_clear_bit(cdns, CDNS_MCP_CONTROL, - CDNS_MCP_CONTROL_CLK_STOP_CLR); - if (ret < 0) { - dev_err(cdns->dev, "Couldn't exit from clock stop\n"); - return ret; + if (clock_stop_exit) { + ret = cdns_clear_bit(cdns, CDNS_MCP_CONTROL, + CDNS_MCP_CONTROL_CLK_STOP_CLR); + if (ret < 0) { + dev_err(cdns->dev, "Couldn't exit from clock stop\n"); + return ret; + } } /* Set clock divider */ - val = cdns_readl(cdns, CDNS_MCP_CLK_CTRL0); - val |= CDNS_DEFAULT_CLK_DIVIDER; - cdns_writel(cdns, CDNS_MCP_CLK_CTRL0, val); + divider = (prop->mclk_freq / prop->max_clk_freq) - 1; - /* Set the default frame shape */ - cdns_writel(cdns, CDNS_MCP_FRAME_SHAPE_INIT, CDNS_DEFAULT_FRAME_SHAPE); + cdns_updatel(cdns, CDNS_MCP_CLK_CTRL0, + CDNS_MCP_CLK_MCLKD_MASK, divider); + cdns_updatel(cdns, CDNS_MCP_CLK_CTRL1, + CDNS_MCP_CLK_MCLKD_MASK, divider); + + /* + * Frame shape changes after initialization have to be done + * with the bank switch mechanism + */ + val = cdns_set_initial_frame_shape(prop->default_row, + prop->default_col); + cdns_writel(cdns, CDNS_MCP_FRAME_SHAPE_INIT, val); /* Set SSP interval to default value */ cdns_writel(cdns, CDNS_MCP_SSP_CTRL0, CDNS_DEFAULT_SSP_INTERVAL); cdns_writel(cdns, CDNS_MCP_SSP_CTRL1, CDNS_DEFAULT_SSP_INTERVAL); + /* flush command FIFOs */ + cdns_updatel(cdns, CDNS_MCP_CONTROL, CDNS_MCP_CONTROL_CMD_RST, + CDNS_MCP_CONTROL_CMD_RST); + /* Set cmd accept mode */ cdns_updatel(cdns, CDNS_MCP_CONTROL, CDNS_MCP_CONTROL_CMD_ACCEPT, CDNS_MCP_CONTROL_CMD_ACCEPT); @@ -839,20 +1081,18 @@ int sdw_cdns_init(struct sdw_cdns *cdns) /* Set cmd mode for Tx and Rx cmds */ val &= ~CDNS_MCP_CONFIG_CMD; - /* Set operation to normal */ - val &= ~CDNS_MCP_CONFIG_OP; - val |= CDNS_MCP_CONFIG_OP_NORMAL; - cdns_writel(cdns, CDNS_MCP_CONFIG, val); - return 0; + /* commit changes */ + return cdns_update_config(cdns); } EXPORT_SYMBOL(sdw_cdns_init); int cdns_bus_conf(struct sdw_bus *bus, struct sdw_bus_params *params) { + struct sdw_master_prop *prop = &bus->prop; struct sdw_cdns *cdns = bus_to_cdns(bus); - int mcp_clkctrl_off, mcp_clkctrl; + int mcp_clkctrl_off; int divider; if (!params->curr_dr_freq) { @@ -860,16 +1100,16 @@ int cdns_bus_conf(struct sdw_bus *bus, struct sdw_bus_params *params) return -EINVAL; } - divider = (params->max_dr_freq / params->curr_dr_freq) - 1; + divider = prop->mclk_freq * SDW_DOUBLE_RATE_FACTOR / + params->curr_dr_freq; + divider--; /* divider is 1/(N+1) */ if (params->next_bank) mcp_clkctrl_off = CDNS_MCP_CLK_CTRL1; else mcp_clkctrl_off = CDNS_MCP_CLK_CTRL0; - mcp_clkctrl = cdns_readl(cdns, mcp_clkctrl_off); - mcp_clkctrl |= divider; - cdns_writel(cdns, mcp_clkctrl_off, mcp_clkctrl); + cdns_updatel(cdns, mcp_clkctrl_off, CDNS_MCP_CLK_MCLKD_MASK, divider); return 0; } @@ -1023,23 +1263,25 @@ EXPORT_SYMBOL(cdns_set_sdw_stream); * cdns_find_pdi() - Find a free PDI * * @cdns: Cadence instance + * @offset: Starting offset * @num: Number of PDIs * @pdi: PDI instances + * @dai_id: DAI id * - * Find and return a free PDI for a given PDI array + * Find a PDI for a given PDI array. The PDI num and dai_id are + * expected to match, return NULL otherwise. */ static struct sdw_cdns_pdi *cdns_find_pdi(struct sdw_cdns *cdns, + unsigned int offset, unsigned int num, - struct sdw_cdns_pdi *pdi) + struct sdw_cdns_pdi *pdi, + int dai_id) { int i; - for (i = 0; i < num; i++) { - if (pdi[i].assigned) - continue; - pdi[i].assigned = true; - return &pdi[i]; - } + for (i = offset; i < offset + num; i++) + if (pdi[i].num == dai_id) + return &pdi[i]; return NULL; } @@ -1048,13 +1290,11 @@ static struct sdw_cdns_pdi *cdns_find_pdi(struct sdw_cdns *cdns, * sdw_cdns_config_stream: Configure a stream * * @cdns: Cadence instance - * @port: Cadence data port * @ch: Channel count * @dir: Data direction * @pdi: PDI to be used */ void sdw_cdns_config_stream(struct sdw_cdns *cdns, - struct sdw_cdns_port *port, u32 ch, u32 dir, struct sdw_cdns_pdi *pdi) { u32 offset, val = 0; @@ -1062,127 +1302,52 @@ void sdw_cdns_config_stream(struct sdw_cdns *cdns, if (dir == SDW_DATA_DIR_RX) val = CDNS_PORTCTRL_DIRN; - offset = CDNS_PORTCTRL + port->num * CDNS_PORT_OFFSET; + offset = CDNS_PORTCTRL + pdi->num * CDNS_PORT_OFFSET; cdns_updatel(cdns, offset, CDNS_PORTCTRL_DIRN, val); - val = port->num; + val = pdi->num; val |= ((1 << ch) - 1) << SDW_REG_SHIFT(CDNS_PDI_CONFIG_CHANNEL); cdns_writel(cdns, CDNS_PDI_CONFIG(pdi->num), val); } EXPORT_SYMBOL(sdw_cdns_config_stream); /** - * cdns_get_num_pdi() - Get number of PDIs required - * - * @cdns: Cadence instance - * @pdi: PDI to be used - * @num: Number of PDIs - * @ch_count: Channel count - */ -static int cdns_get_num_pdi(struct sdw_cdns *cdns, - struct sdw_cdns_pdi *pdi, - unsigned int num, u32 ch_count) -{ - int i, pdis = 0; - - for (i = 0; i < num; i++) { - if (pdi[i].assigned) - continue; - - if (pdi[i].ch_count < ch_count) - ch_count -= pdi[i].ch_count; - else - ch_count = 0; - - pdis++; - - if (!ch_count) - break; - } - - if (ch_count) - return 0; - - return pdis; -} - -/** - * sdw_cdns_get_stream() - Get stream information + * sdw_cdns_alloc_pdi() - Allocate a PDI * * @cdns: Cadence instance * @stream: Stream to be allocated * @ch: Channel count * @dir: Data direction + * @dai_id: DAI id */ -int sdw_cdns_get_stream(struct sdw_cdns *cdns, - struct sdw_cdns_streams *stream, - u32 ch, u32 dir) -{ - int pdis = 0; - - if (dir == SDW_DATA_DIR_RX) - pdis = cdns_get_num_pdi(cdns, stream->in, stream->num_in, ch); - else - pdis = cdns_get_num_pdi(cdns, stream->out, stream->num_out, ch); - - /* check if we found PDI, else find in bi-directional */ - if (!pdis) - pdis = cdns_get_num_pdi(cdns, stream->bd, stream->num_bd, ch); - - return pdis; -} -EXPORT_SYMBOL(sdw_cdns_get_stream); - -/** - * sdw_cdns_alloc_stream() - Allocate a stream - * - * @cdns: Cadence instance - * @stream: Stream to be allocated - * @port: Cadence data port - * @ch: Channel count - * @dir: Data direction - */ -int sdw_cdns_alloc_stream(struct sdw_cdns *cdns, - struct sdw_cdns_streams *stream, - struct sdw_cdns_port *port, u32 ch, u32 dir) +struct sdw_cdns_pdi *sdw_cdns_alloc_pdi(struct sdw_cdns *cdns, + struct sdw_cdns_streams *stream, + u32 ch, u32 dir, int dai_id) { struct sdw_cdns_pdi *pdi = NULL; if (dir == SDW_DATA_DIR_RX) - pdi = cdns_find_pdi(cdns, stream->num_in, stream->in); + pdi = cdns_find_pdi(cdns, 0, stream->num_in, stream->in, + dai_id); else - pdi = cdns_find_pdi(cdns, stream->num_out, stream->out); + pdi = cdns_find_pdi(cdns, 0, stream->num_out, stream->out, + dai_id); /* check if we found a PDI, else find in bi-directional */ if (!pdi) - pdi = cdns_find_pdi(cdns, stream->num_bd, stream->bd); - - if (!pdi) - return -EIO; - - port->pdi = pdi; - pdi->l_ch_num = 0; - pdi->h_ch_num = ch - 1; - pdi->dir = dir; - pdi->ch_count = ch; - - return 0; -} -EXPORT_SYMBOL(sdw_cdns_alloc_stream); - -void sdw_cdns_shutdown(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - struct sdw_cdns_dma_data *dma; - - dma = snd_soc_dai_get_dma_data(dai, substream); - if (!dma) - return; + pdi = cdns_find_pdi(cdns, 2, stream->num_bd, stream->bd, + dai_id); + + if (pdi) { + pdi->l_ch_num = 0; + pdi->h_ch_num = ch - 1; + pdi->dir = dir; + pdi->ch_count = ch; + } - snd_soc_dai_set_dma_data(dai, substream, NULL); - kfree(dma); + return pdi; } -EXPORT_SYMBOL(sdw_cdns_shutdown); +EXPORT_SYMBOL(sdw_cdns_alloc_pdi); MODULE_LICENSE("Dual BSD/GPL"); MODULE_DESCRIPTION("Cadence Soundwire Library"); diff --git a/drivers/soundwire/cadence_master.h b/drivers/soundwire/cadence_master.h index fe2af62958b1..001457cbe5ad 100644 --- a/drivers/soundwire/cadence_master.h +++ b/drivers/soundwire/cadence_master.h @@ -8,7 +8,6 @@ /** * struct sdw_cdns_pdi: PDI (Physical Data Interface) instance * - * @assigned: pdi assigned * @num: pdi number * @intel_alh_id: link identifier * @l_ch_num: low channel for PDI @@ -18,7 +17,6 @@ * @type: stream type, PDM or PCM */ struct sdw_cdns_pdi { - bool assigned; int num; int intel_alh_id; int l_ch_num; @@ -29,23 +27,6 @@ struct sdw_cdns_pdi { }; /** - * struct sdw_cdns_port: Cadence port structure - * - * @num: port number - * @assigned: port assigned - * @ch: channel count - * @direction: data port direction - * @pdi: pdi for this port - */ -struct sdw_cdns_port { - unsigned int num; - bool assigned; - unsigned int ch; - enum sdw_data_direction direction; - struct sdw_cdns_pdi *pdi; -}; - -/** * struct sdw_cdns_streams: Cadence stream data structure * * @num_bd: number of bidirectional streams @@ -95,8 +76,8 @@ struct sdw_cdns_stream_config { * struct sdw_cdns_dma_data: Cadence DMA data * * @name: SoundWire stream name - * @nr_ports: Number of ports - * @port: Ports + * @stream: stream runtime + * @pdi: PDI used for this dai * @bus: Bus handle * @stream_type: Stream type * @link_id: Master link id @@ -104,8 +85,7 @@ struct sdw_cdns_stream_config { struct sdw_cdns_dma_data { char *name; struct sdw_stream_runtime *stream; - int nr_ports; - struct sdw_cdns_port **port; + struct sdw_cdns_pdi *pdi; struct sdw_bus *bus; enum sdw_stream_type stream_type; int link_id; @@ -158,22 +138,25 @@ extern struct sdw_master_ops sdw_cdns_master_ops; irqreturn_t sdw_cdns_irq(int irq, void *dev_id); irqreturn_t sdw_cdns_thread(int irq, void *dev_id); -int sdw_cdns_init(struct sdw_cdns *cdns); +int sdw_cdns_init(struct sdw_cdns *cdns, bool clock_stop_exit); int sdw_cdns_pdi_init(struct sdw_cdns *cdns, struct sdw_cdns_stream_config config); -int sdw_cdns_enable_interrupt(struct sdw_cdns *cdns); +int sdw_cdns_exit_reset(struct sdw_cdns *cdns); +int sdw_cdns_enable_interrupt(struct sdw_cdns *cdns, bool state); + +#ifdef CONFIG_DEBUG_FS +void sdw_cdns_debugfs_init(struct sdw_cdns *cdns, struct dentry *root); +#endif int sdw_cdns_get_stream(struct sdw_cdns *cdns, struct sdw_cdns_streams *stream, u32 ch, u32 dir); -int sdw_cdns_alloc_stream(struct sdw_cdns *cdns, - struct sdw_cdns_streams *stream, - struct sdw_cdns_port *port, u32 ch, u32 dir); -void sdw_cdns_config_stream(struct sdw_cdns *cdns, struct sdw_cdns_port *port, +struct sdw_cdns_pdi *sdw_cdns_alloc_pdi(struct sdw_cdns *cdns, + struct sdw_cdns_streams *stream, + u32 ch, u32 dir, int dai_id); +void sdw_cdns_config_stream(struct sdw_cdns *cdns, u32 ch, u32 dir, struct sdw_cdns_pdi *pdi); -void sdw_cdns_shutdown(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai); int sdw_cdns_pcm_set_stream(struct snd_soc_dai *dai, void *stream, int direction); int sdw_cdns_pdm_set_stream(struct snd_soc_dai *dai, diff --git a/drivers/soundwire/debugfs.c b/drivers/soundwire/debugfs.c new file mode 100644 index 000000000000..fb1140e82b86 --- /dev/null +++ b/drivers/soundwire/debugfs.c @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright(c) 2017-2019 Intel Corporation. + +#include <linux/device.h> +#include <linux/debugfs.h> +#include <linux/mod_devicetable.h> +#include <linux/slab.h> +#include <linux/soundwire/sdw.h> +#include <linux/soundwire/sdw_registers.h> +#include "bus.h" + +static struct dentry *sdw_debugfs_root; + +void sdw_bus_debugfs_init(struct sdw_bus *bus) +{ + char name[16]; + + if (!sdw_debugfs_root) + return; + + /* create the debugfs master-N */ + snprintf(name, sizeof(name), "master-%d", bus->link_id); + bus->debugfs = debugfs_create_dir(name, sdw_debugfs_root); +} + +void sdw_bus_debugfs_exit(struct sdw_bus *bus) +{ + debugfs_remove_recursive(bus->debugfs); +} + +#define RD_BUF (3 * PAGE_SIZE) + +static ssize_t sdw_sprintf(struct sdw_slave *slave, + char *buf, size_t pos, unsigned int reg) +{ + int value; + + value = sdw_read(slave, reg); + + if (value < 0) + return scnprintf(buf + pos, RD_BUF - pos, "%3x\tXX\n", reg); + else + return scnprintf(buf + pos, RD_BUF - pos, + "%3x\t%2x\n", reg, value); +} + +static int sdw_slave_reg_show(struct seq_file *s_file, void *data) +{ + struct sdw_slave *slave = s_file->private; + char *buf; + ssize_t ret; + int i, j; + + buf = kzalloc(RD_BUF, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = scnprintf(buf, RD_BUF, "Register Value\n"); + + /* DP0 non-banked registers */ + ret += scnprintf(buf + ret, RD_BUF - ret, "\nDP0\n"); + for (i = SDW_DP0_INT; i <= SDW_DP0_PREPARECTRL; i++) + ret += sdw_sprintf(slave, buf, ret, i); + + /* DP0 Bank 0 registers */ + ret += scnprintf(buf + ret, RD_BUF - ret, "Bank0\n"); + ret += sdw_sprintf(slave, buf, ret, SDW_DP0_CHANNELEN); + for (i = SDW_DP0_SAMPLECTRL1; i <= SDW_DP0_LANECTRL; i++) + ret += sdw_sprintf(slave, buf, ret, i); + + /* DP0 Bank 1 registers */ + ret += scnprintf(buf + ret, RD_BUF - ret, "Bank1\n"); + ret += sdw_sprintf(slave, buf, ret, + SDW_DP0_CHANNELEN + SDW_BANK1_OFFSET); + for (i = SDW_DP0_SAMPLECTRL1 + SDW_BANK1_OFFSET; + i <= SDW_DP0_LANECTRL + SDW_BANK1_OFFSET; i++) + ret += sdw_sprintf(slave, buf, ret, i); + + /* SCP registers */ + ret += scnprintf(buf + ret, RD_BUF - ret, "\nSCP\n"); + for (i = SDW_SCP_INT1; i <= SDW_SCP_BANKDELAY; i++) + ret += sdw_sprintf(slave, buf, ret, i); + for (i = SDW_SCP_DEVID_0; i <= SDW_SCP_DEVID_5; i++) + ret += sdw_sprintf(slave, buf, ret, i); + + /* + * SCP Bank 0/1 registers are read-only and cannot be + * retrieved from the Slave. The Master typically keeps track + * of the current frame size so the information can be found + * in other places + */ + + /* DP1..14 registers */ + for (i = 1; SDW_VALID_PORT_RANGE(i); i++) { + + /* DPi registers */ + ret += scnprintf(buf + ret, RD_BUF - ret, "\nDP%d\n", i); + for (j = SDW_DPN_INT(i); j <= SDW_DPN_PREPARECTRL(i); j++) + ret += sdw_sprintf(slave, buf, ret, j); + + /* DPi Bank0 registers */ + ret += scnprintf(buf + ret, RD_BUF - ret, "Bank0\n"); + for (j = SDW_DPN_CHANNELEN_B0(i); + j <= SDW_DPN_LANECTRL_B0(i); j++) + ret += sdw_sprintf(slave, buf, ret, j); + + /* DPi Bank1 registers */ + ret += scnprintf(buf + ret, RD_BUF - ret, "Bank1\n"); + for (j = SDW_DPN_CHANNELEN_B1(i); + j <= SDW_DPN_LANECTRL_B1(i); j++) + ret += sdw_sprintf(slave, buf, ret, j); + } + + seq_printf(s_file, "%s", buf); + kfree(buf); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(sdw_slave_reg); + +void sdw_slave_debugfs_init(struct sdw_slave *slave) +{ + struct dentry *master; + struct dentry *d; + char name[32]; + + master = slave->bus->debugfs; + + /* create the debugfs slave-name */ + snprintf(name, sizeof(name), "%s", dev_name(&slave->dev)); + d = debugfs_create_dir(name, master); + + debugfs_create_file("registers", 0400, d, slave, &sdw_slave_reg_fops); + + slave->debugfs = d; +} + +void sdw_slave_debugfs_exit(struct sdw_slave *slave) +{ + debugfs_remove_recursive(slave->debugfs); +} + +void sdw_debugfs_init(void) +{ + sdw_debugfs_root = debugfs_create_dir("soundwire", NULL); +} + +void sdw_debugfs_exit(void) +{ + debugfs_remove_recursive(sdw_debugfs_root); +} diff --git a/drivers/soundwire/intel.c b/drivers/soundwire/intel.c index 317873bc0555..06ef3a3ac080 100644 --- a/drivers/soundwire/intel.c +++ b/drivers/soundwire/intel.c @@ -6,9 +6,11 @@ */ #include <linux/acpi.h> +#include <linux/debugfs.h> #include <linux/delay.h> #include <linux/module.h> #include <linux/interrupt.h> +#include <linux/io.h> #include <linux/platform_device.h> #include <sound/pcm_params.h> #include <sound/soc.h> @@ -16,6 +18,7 @@ #include <linux/soundwire/sdw.h> #include <linux/soundwire/sdw_intel.h> #include "cadence_master.h" +#include "bus.h" #include "intel.h" /* Intel SHIM Registers Definition */ @@ -83,11 +86,14 @@ /* Intel ALH Register definitions */ #define SDW_ALH_STRMZCFG(x) (0x000 + (0x4 * (x))) +#define SDW_ALH_NUM_STREAMS 64 #define SDW_ALH_STRMZCFG_DMAT_VAL 0x3 #define SDW_ALH_STRMZCFG_DMAT GENMASK(7, 0) #define SDW_ALH_STRMZCFG_CHN GENMASK(19, 16) +#define SDW_INTEL_QUIRK_MASK_BUS_DISABLE BIT(1) + enum intel_pdi_type { INTEL_PDI_IN = 0, INTEL_PDI_OUT = 1, @@ -98,6 +104,9 @@ struct sdw_intel { struct sdw_cdns cdns; int instance; struct sdw_intel_link_res *res; +#ifdef CONFIG_DEBUG_FS + struct dentry *debugfs; +#endif }; #define cdns_to_intel(_cdns) container_of(_cdns, struct sdw_intel, cdns) @@ -162,6 +171,118 @@ static int intel_set_bit(void __iomem *base, int offset, u32 value, u32 mask) } /* + * debugfs + */ +#ifdef CONFIG_DEBUG_FS + +#define RD_BUF (2 * PAGE_SIZE) + +static ssize_t intel_sprintf(void __iomem *mem, bool l, + char *buf, size_t pos, unsigned int reg) +{ + int value; + + if (l) + value = intel_readl(mem, reg); + else + value = intel_readw(mem, reg); + + return scnprintf(buf + pos, RD_BUF - pos, "%4x\t%4x\n", reg, value); +} + +static int intel_reg_show(struct seq_file *s_file, void *data) +{ + struct sdw_intel *sdw = s_file->private; + void __iomem *s = sdw->res->shim; + void __iomem *a = sdw->res->alh; + char *buf; + ssize_t ret; + int i, j; + unsigned int links, reg; + + buf = kzalloc(RD_BUF, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + links = intel_readl(s, SDW_SHIM_LCAP) & GENMASK(2, 0); + + ret = scnprintf(buf, RD_BUF, "Register Value\n"); + ret += scnprintf(buf + ret, RD_BUF - ret, "\nShim\n"); + + for (i = 0; i < links; i++) { + reg = SDW_SHIM_LCAP + i * 4; + ret += intel_sprintf(s, true, buf, ret, reg); + } + + for (i = 0; i < links; i++) { + ret += scnprintf(buf + ret, RD_BUF - ret, "\nLink%d\n", i); + ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_CTLSCAP(i)); + ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_CTLS0CM(i)); + ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_CTLS1CM(i)); + ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_CTLS2CM(i)); + ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_CTLS3CM(i)); + ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_PCMSCAP(i)); + + ret += scnprintf(buf + ret, RD_BUF - ret, "\n PCMSyCH registers\n"); + + /* + * the value 10 is the number of PDIs. We will need a + * cleanup to remove hard-coded Intel configurations + * from cadence_master.c + */ + for (j = 0; j < 10; j++) { + ret += intel_sprintf(s, false, buf, ret, + SDW_SHIM_PCMSYCHM(i, j)); + ret += intel_sprintf(s, false, buf, ret, + SDW_SHIM_PCMSYCHC(i, j)); + } + ret += scnprintf(buf + ret, RD_BUF - ret, "\n PDMSCAP, IOCTL, CTMCTL\n"); + + ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_PDMSCAP(i)); + ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_IOCTL(i)); + ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_CTMCTL(i)); + } + + ret += scnprintf(buf + ret, RD_BUF - ret, "\nWake registers\n"); + ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_WAKEEN); + ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_WAKESTS); + + ret += scnprintf(buf + ret, RD_BUF - ret, "\nALH STRMzCFG\n"); + for (i = 0; i < SDW_ALH_NUM_STREAMS; i++) + ret += intel_sprintf(a, true, buf, ret, SDW_ALH_STRMZCFG(i)); + + seq_printf(s_file, "%s", buf); + kfree(buf); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(intel_reg); + +static void intel_debugfs_init(struct sdw_intel *sdw) +{ + struct dentry *root = sdw->cdns.bus.debugfs; + + if (!root) + return; + + sdw->debugfs = debugfs_create_dir("intel-sdw", root); + + debugfs_create_file("intel-registers", 0400, sdw->debugfs, sdw, + &intel_reg_fops); + + sdw_cdns_debugfs_init(&sdw->cdns, sdw->debugfs); +} + +static void intel_debugfs_exit(struct sdw_intel *sdw) +{ + debugfs_remove_recursive(sdw->debugfs); +} +#else +static void intel_debugfs_init(struct sdw_intel *sdw) {} +static void intel_debugfs_exit(struct sdw_intel *sdw) {} +#endif /* CONFIG_DEBUG_FS */ + +/* * shim ops */ @@ -289,6 +410,16 @@ intel_pdi_get_ch_cap(struct sdw_intel *sdw, unsigned int pdi_num, bool pcm) if (pcm) { count = intel_readw(shim, SDW_SHIM_PCMSYCHC(link_id, pdi_num)); + + /* + * WORKAROUND: on all existing Intel controllers, pdi + * number 2 reports channel count as 1 even though it + * supports 8 channels. Performing hardcoding for pdi + * number 2. + */ + if (pdi_num == 2) + count = 7; + } else { count = intel_readw(shim, SDW_SHIM_PDMSCAP(link_id)); count = ((count & SDW_SHIM_PDMSCAP_CPSS) >> @@ -349,7 +480,10 @@ intel_pdi_shim_configure(struct sdw_intel *sdw, struct sdw_cdns_pdi *pdi) unsigned int link_id = sdw->instance; int pdi_conf = 0; - pdi->intel_alh_id = (link_id * 16) + pdi->num + 5; + /* the Bulk and PCM streams are not contiguous */ + pdi->intel_alh_id = (link_id * 16) + pdi->num + 3; + if (pdi->num >= 2) + pdi->intel_alh_id += 2; /* * Program stream parameters to stream SHIM register @@ -378,7 +512,10 @@ intel_pdi_alh_configure(struct sdw_intel *sdw, struct sdw_cdns_pdi *pdi) unsigned int link_id = sdw->instance; unsigned int conf; - pdi->intel_alh_id = (link_id * 16) + pdi->num + 5; + /* the Bulk and PCM streams are not contiguous */ + pdi->intel_alh_id = (link_id * 16) + pdi->num + 3; + if (pdi->num >= 2) + pdi->intel_alh_id += 2; /* Program Stream config ALH register */ conf = intel_readl(alh, SDW_ALH_STRMZCFG(pdi->intel_alh_id)); @@ -392,15 +529,24 @@ intel_pdi_alh_configure(struct sdw_intel *sdw, struct sdw_cdns_pdi *pdi) intel_writel(alh, SDW_ALH_STRMZCFG(pdi->intel_alh_id), conf); } -static int intel_config_stream(struct sdw_intel *sdw, +static int intel_params_stream(struct sdw_intel *sdw, struct snd_pcm_substream *substream, struct snd_soc_dai *dai, - struct snd_pcm_hw_params *hw_params, int link_id) + struct snd_pcm_hw_params *hw_params, + int link_id, int alh_stream_id) { - if (sdw->res->ops && sdw->res->ops->config_stream) - return sdw->res->ops->config_stream(sdw->res->arg, - substream, dai, hw_params, link_id); - + struct sdw_intel_link_res *res = sdw->res; + struct sdw_intel_stream_params_data params_data; + + params_data.substream = substream; + params_data.dai = dai; + params_data.hw_params = hw_params; + params_data.link_id = link_id; + params_data.alh_stream_id = alh_stream_id; + + if (res->ops && res->ops->params_stream && res->dev) + return res->ops->params_stream(res->dev, + ¶ms_data); return -EIO; } @@ -471,66 +617,6 @@ static int intel_post_bank_switch(struct sdw_bus *bus) * DAI routines */ -static struct sdw_cdns_port *intel_alloc_port(struct sdw_intel *sdw, - u32 ch, u32 dir, bool pcm) -{ - struct sdw_cdns *cdns = &sdw->cdns; - struct sdw_cdns_port *port = NULL; - int i, ret = 0; - - for (i = 0; i < cdns->num_ports; i++) { - if (cdns->ports[i].assigned) - continue; - - port = &cdns->ports[i]; - port->assigned = true; - port->direction = dir; - port->ch = ch; - break; - } - - if (!port) { - dev_err(cdns->dev, "Unable to find a free port\n"); - return NULL; - } - - if (pcm) { - ret = sdw_cdns_alloc_stream(cdns, &cdns->pcm, port, ch, dir); - if (ret) - goto out; - - intel_pdi_shim_configure(sdw, port->pdi); - sdw_cdns_config_stream(cdns, port, ch, dir, port->pdi); - - intel_pdi_alh_configure(sdw, port->pdi); - - } else { - ret = sdw_cdns_alloc_stream(cdns, &cdns->pdm, port, ch, dir); - } - -out: - if (ret) { - port->assigned = false; - port = NULL; - } - - return port; -} - -static void intel_port_cleanup(struct sdw_cdns_dma_data *dma) -{ - int i; - - for (i = 0; i < dma->nr_ports; i++) { - if (dma->port[i]) { - dma->port[i]->pdi->assigned = false; - dma->port[i]->pdi = NULL; - dma->port[i]->assigned = false; - dma->port[i] = NULL; - } - } -} - static int intel_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) @@ -538,9 +624,11 @@ static int intel_hw_params(struct snd_pcm_substream *substream, struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai); struct sdw_intel *sdw = cdns_to_intel(cdns); struct sdw_cdns_dma_data *dma; + struct sdw_cdns_pdi *pdi; struct sdw_stream_config sconfig; struct sdw_port_config *pconfig; - int ret, i, ch, dir; + int ch, dir; + int ret; bool pcm = true; dma = snd_soc_dai_get_dma_data(dai, substream); @@ -553,38 +641,31 @@ static int intel_hw_params(struct snd_pcm_substream *substream, else dir = SDW_DATA_DIR_TX; - if (dma->stream_type == SDW_STREAM_PDM) { - /* TODO: Check whether PDM decimator is already in use */ - dma->nr_ports = sdw_cdns_get_stream(cdns, &cdns->pdm, ch, dir); + if (dma->stream_type == SDW_STREAM_PDM) pcm = false; - } else { - dma->nr_ports = sdw_cdns_get_stream(cdns, &cdns->pcm, ch, dir); - } - if (!dma->nr_ports) { - dev_err(dai->dev, "ports/resources not available\n"); - return -EINVAL; + if (pcm) + pdi = sdw_cdns_alloc_pdi(cdns, &cdns->pcm, ch, dir, dai->id); + else + pdi = sdw_cdns_alloc_pdi(cdns, &cdns->pdm, ch, dir, dai->id); + + if (!pdi) { + ret = -EINVAL; + goto error; } - dma->port = kcalloc(dma->nr_ports, sizeof(*dma->port), GFP_KERNEL); - if (!dma->port) - return -ENOMEM; + /* do run-time configurations for SHIM, ALH and PDI/PORT */ + intel_pdi_shim_configure(sdw, pdi); + intel_pdi_alh_configure(sdw, pdi); + sdw_cdns_config_stream(cdns, ch, dir, pdi); - for (i = 0; i < dma->nr_ports; i++) { - dma->port[i] = intel_alloc_port(sdw, ch, dir, pcm); - if (!dma->port[i]) { - ret = -EINVAL; - goto port_error; - } - } /* Inform DSP about PDI stream number */ - for (i = 0; i < dma->nr_ports; i++) { - ret = intel_config_stream(sdw, substream, dai, params, - dma->port[i]->pdi->intel_alh_id); - if (ret) - goto port_error; - } + ret = intel_params_stream(sdw, substream, dai, params, + sdw->instance, + pdi->intel_alh_id); + if (ret) + goto error; sconfig.direction = dir; sconfig.ch_count = ch; @@ -599,32 +680,22 @@ static int intel_hw_params(struct snd_pcm_substream *substream, } /* Port configuration */ - pconfig = kcalloc(dma->nr_ports, sizeof(*pconfig), GFP_KERNEL); + pconfig = kcalloc(1, sizeof(*pconfig), GFP_KERNEL); if (!pconfig) { ret = -ENOMEM; - goto port_error; + goto error; } - for (i = 0; i < dma->nr_ports; i++) { - pconfig[i].num = dma->port[i]->num; - pconfig[i].ch_mask = (1 << ch) - 1; - } + pconfig->num = pdi->num; + pconfig->ch_mask = (1 << ch) - 1; ret = sdw_stream_add_master(&cdns->bus, &sconfig, - pconfig, dma->nr_ports, dma->stream); - if (ret) { + pconfig, 1, dma->stream); + if (ret) dev_err(cdns->dev, "add master to stream failed:%d\n", ret); - goto stream_error; - } - - kfree(pconfig); - return ret; -stream_error: kfree(pconfig); -port_error: - intel_port_cleanup(dma); - kfree(dma->port); +error: return ret; } @@ -644,11 +715,22 @@ intel_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) dev_err(dai->dev, "remove master from stream %s failed: %d\n", dma->stream->name, ret); - intel_port_cleanup(dma); - kfree(dma->port); return ret; } +static void intel_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sdw_cdns_dma_data *dma; + + dma = snd_soc_dai_get_dma_data(dai, substream); + if (!dma) + return; + + snd_soc_dai_set_dma_data(dai, substream, NULL); + kfree(dma); +} + static int intel_pcm_set_sdw_stream(struct snd_soc_dai *dai, void *stream, int direction) { @@ -664,14 +746,14 @@ static int intel_pdm_set_sdw_stream(struct snd_soc_dai *dai, static const struct snd_soc_dai_ops intel_pcm_dai_ops = { .hw_params = intel_hw_params, .hw_free = intel_hw_free, - .shutdown = sdw_cdns_shutdown, + .shutdown = intel_shutdown, .set_sdw_stream = intel_pcm_set_sdw_stream, }; static const struct snd_soc_dai_ops intel_pdm_dai_ops = { .hw_params = intel_hw_params, .hw_free = intel_hw_free, - .shutdown = sdw_cdns_shutdown, + .shutdown = intel_shutdown, .set_sdw_stream = intel_pdm_set_sdw_stream, }; @@ -697,14 +779,6 @@ static int intel_create_dai(struct sdw_cdns *cdns, return -ENOMEM; if (type == INTEL_PDI_BD || type == INTEL_PDI_OUT) { - dais[i].playback.stream_name = - kasprintf(GFP_KERNEL, "SDW%d Tx%d", - cdns->instance, i); - if (!dais[i].playback.stream_name) { - kfree(dais[i].name); - return -ENOMEM; - } - dais[i].playback.channels_min = 1; dais[i].playback.channels_max = max_ch; dais[i].playback.rates = SNDRV_PCM_RATE_48000; @@ -712,23 +786,12 @@ static int intel_create_dai(struct sdw_cdns *cdns, } if (type == INTEL_PDI_BD || type == INTEL_PDI_IN) { - dais[i].capture.stream_name = - kasprintf(GFP_KERNEL, "SDW%d Rx%d", - cdns->instance, i); - if (!dais[i].capture.stream_name) { - kfree(dais[i].name); - kfree(dais[i].playback.stream_name); - return -ENOMEM; - } - dais[i].capture.channels_min = 1; dais[i].capture.channels_max = max_ch; dais[i].capture.rates = SNDRV_PCM_RATE_48000; dais[i].capture.formats = SNDRV_PCM_FMTBIT_S16_LE; } - dais[i].id = SDW_DAI_ID_RANGE_START + i; - if (pcm) dais[i].ops = &intel_pcm_dai_ops; else @@ -755,7 +818,7 @@ static int intel_register_dai(struct sdw_intel *sdw) /* Create PCM DAIs */ stream = &cdns->pcm; - ret = intel_create_dai(cdns, dais, INTEL_PDI_IN, stream->num_in, + ret = intel_create_dai(cdns, dais, INTEL_PDI_IN, cdns->pcm.num_in, off, stream->num_ch_in, true); if (ret) return ret; @@ -786,7 +849,7 @@ static int intel_register_dai(struct sdw_intel *sdw) if (ret) return ret; - off += cdns->pdm.num_bd; + off += cdns->pdm.num_out; ret = intel_create_dai(cdns, dais, INTEL_PDI_BD, cdns->pdm.num_bd, off, stream->num_ch_bd, false); if (ret) @@ -796,21 +859,47 @@ static int intel_register_dai(struct sdw_intel *sdw) dais, num_dai); } +static int sdw_master_read_intel_prop(struct sdw_bus *bus) +{ + struct sdw_master_prop *prop = &bus->prop; + struct fwnode_handle *link; + char name[32]; + u32 quirk_mask; + + /* Find master handle */ + snprintf(name, sizeof(name), + "mipi-sdw-link-%d-subproperties", bus->link_id); + + link = device_get_named_child_node(bus->dev, name); + if (!link) { + dev_err(bus->dev, "Master node %s not found\n", name); + return -EIO; + } + + fwnode_property_read_u32(link, + "intel-sdw-ip-clock", + &prop->mclk_freq); + + /* the values reported by BIOS are the 2x clock, not the bus clock */ + prop->mclk_freq /= 2; + + fwnode_property_read_u32(link, + "intel-quirk-mask", + &quirk_mask); + + if (quirk_mask & SDW_INTEL_QUIRK_MASK_BUS_DISABLE) + prop->hw_disabled = true; + + return 0; +} + static int intel_prop_read(struct sdw_bus *bus) { /* Initialize with default handler to read all DisCo properties */ sdw_master_read_prop(bus); - /* BIOS is not giving some values correctly. So, lets override them */ - bus->prop.num_clk_freq = 1; - bus->prop.clk_freq = devm_kcalloc(bus->dev, bus->prop.num_clk_freq, - sizeof(*bus->prop.clk_freq), - GFP_KERNEL); - if (!bus->prop.clk_freq) - return -ENOMEM; - - bus->prop.clk_freq[0] = bus->prop.max_clk_freq; - bus->prop.err_threshold = 5; + /* read Intel-specific properties */ + sdw_master_read_intel_prop(bus); return 0; } @@ -825,6 +914,15 @@ static struct sdw_master_ops sdw_intel_ops = { .post_bank_switch = intel_post_bank_switch, }; +static int intel_init(struct sdw_intel *sdw) +{ + /* Initialize shim and controller */ + intel_link_power_up(sdw); + intel_shim_init(sdw); + + return sdw_cdns_init(&sdw->cdns, false); +} + /* * probe and init */ @@ -858,19 +956,20 @@ static int intel_probe(struct platform_device *pdev) ret = sdw_add_bus_master(&sdw->cdns.bus); if (ret) { dev_err(&pdev->dev, "sdw_add_bus_master fail: %d\n", ret); - goto err_master_reg; + return ret; } - /* Initialize shim and controller */ - intel_link_power_up(sdw); - intel_shim_init(sdw); + if (sdw->cdns.bus.prop.hw_disabled) { + dev_info(&pdev->dev, "SoundWire master %d is disabled, ignoring\n", + sdw->cdns.bus.link_id); + return 0; + } - ret = sdw_cdns_init(&sdw->cdns); + /* Initialize shim, controller and Cadence IP */ + ret = intel_init(sdw); if (ret) goto err_init; - ret = sdw_cdns_enable_interrupt(&sdw->cdns); - /* Read the PDI config and initialize cadence PDI */ intel_pdi_init(sdw, &config); ret = sdw_cdns_pdi_init(&sdw->cdns, config); @@ -888,21 +987,35 @@ static int intel_probe(struct platform_device *pdev) goto err_init; } + ret = sdw_cdns_enable_interrupt(&sdw->cdns, true); + if (ret < 0) { + dev_err(sdw->cdns.dev, "cannot enable interrupts\n"); + goto err_init; + } + + ret = sdw_cdns_exit_reset(&sdw->cdns); + if (ret < 0) { + dev_err(sdw->cdns.dev, "unable to exit bus reset sequence\n"); + goto err_interrupt; + } + /* Register DAIs */ ret = intel_register_dai(sdw); if (ret) { dev_err(sdw->cdns.dev, "DAI registration failed: %d\n", ret); snd_soc_unregister_component(sdw->cdns.dev); - goto err_dai; + goto err_interrupt; } + intel_debugfs_init(sdw); + return 0; -err_dai: +err_interrupt: + sdw_cdns_enable_interrupt(&sdw->cdns, false); free_irq(sdw->res->irq, sdw); err_init: sdw_delete_bus_master(&sdw->cdns.bus); -err_master_reg: return ret; } @@ -912,8 +1025,12 @@ static int intel_remove(struct platform_device *pdev) sdw = platform_get_drvdata(pdev); - free_irq(sdw->res->irq, sdw); - snd_soc_unregister_component(sdw->cdns.dev); + if (!sdw->cdns.bus.prop.hw_disabled) { + intel_debugfs_exit(sdw); + sdw_cdns_enable_interrupt(&sdw->cdns, false); + free_irq(sdw->res->irq, sdw); + snd_soc_unregister_component(sdw->cdns.dev); + } sdw_delete_bus_master(&sdw->cdns.bus); return 0; diff --git a/drivers/soundwire/intel.h b/drivers/soundwire/intel.h index d923b6262330..38b7c125fb10 100644 --- a/drivers/soundwire/intel.h +++ b/drivers/soundwire/intel.h @@ -5,23 +5,26 @@ #define __SDW_INTEL_LOCAL_H /** - * struct sdw_intel_link_res - Soundwire link resources + * struct sdw_intel_link_res - Soundwire Intel link resource structure, + * typically populated by the controller driver. + * @pdev: platform_device + * @mmio_base: mmio base of SoundWire registers * @registers: Link IO registers base * @shim: Audio shim pointer * @alh: ALH (Audio Link Hub) pointer * @irq: Interrupt line * @ops: Shim callback ops - * @arg: Shim callback ops argument - * - * This is set as pdata for each link instance. + * @dev: device implementing hw_params and free callbacks */ struct sdw_intel_link_res { + struct platform_device *pdev; + void __iomem *mmio_base; /* not strictly needed, useful for debug */ void __iomem *registers; void __iomem *shim; void __iomem *alh; int irq; const struct sdw_intel_ops *ops; - void *arg; + struct device *dev; }; #endif /* __SDW_INTEL_LOCAL_H */ diff --git a/drivers/soundwire/intel_init.c b/drivers/soundwire/intel_init.c index 70637a0383d2..4b769409f6f8 100644 --- a/drivers/soundwire/intel_init.c +++ b/drivers/soundwire/intel_init.c @@ -9,6 +9,7 @@ #include <linux/acpi.h> #include <linux/export.h> +#include <linux/io.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/soundwire/sdw_intel.h> @@ -22,19 +23,13 @@ #define SDW_LINK_BASE 0x30000 #define SDW_LINK_SIZE 0x10000 -struct sdw_link_data { - struct sdw_intel_link_res res; - struct platform_device *pdev; -}; - -struct sdw_intel_ctx { - int count; - struct sdw_link_data *links; -}; +static int link_mask; +module_param_named(sdw_link_mask, link_mask, int, 0444); +MODULE_PARM_DESC(sdw_link_mask, "Intel link mask (one bit per link)"); static int sdw_intel_cleanup_pdev(struct sdw_intel_ctx *ctx) { - struct sdw_link_data *link = ctx->links; + struct sdw_intel_link_res *link = ctx->links; int i; if (!link) @@ -57,7 +52,7 @@ static struct sdw_intel_ctx { struct platform_device_info pdevinfo; struct platform_device *pdev; - struct sdw_link_data *link; + struct sdw_intel_link_res *link; struct sdw_intel_ctx *ctx; struct acpi_device *adev; int ret, i; @@ -111,14 +106,20 @@ static struct sdw_intel_ctx /* Create SDW Master devices */ for (i = 0; i < count; i++) { - link->res.irq = res->irq; - link->res.registers = res->mmio_base + SDW_LINK_BASE + if (link_mask && !(link_mask & BIT(i))) { + dev_dbg(&adev->dev, + "Link %d masked, will not be enabled\n", i); + link++; + continue; + } + + link->registers = res->mmio_base + SDW_LINK_BASE + (SDW_LINK_SIZE * i); - link->res.shim = res->mmio_base + SDW_SHIM_BASE; - link->res.alh = res->mmio_base + SDW_ALH_BASE; + link->shim = res->mmio_base + SDW_SHIM_BASE; + link->alh = res->mmio_base + SDW_ALH_BASE; - link->res.ops = res->ops; - link->res.arg = res->arg; + link->ops = res->ops; + link->dev = res->dev; memset(&pdevinfo, 0, sizeof(pdevinfo)); @@ -126,8 +127,6 @@ static struct sdw_intel_ctx pdevinfo.name = "int-sdw"; pdevinfo.id = i; pdevinfo.fwnode = acpi_fwnode_handle(adev); - pdevinfo.data = &link->res; - pdevinfo.size_data = sizeof(link->res); pdev = platform_device_register_full(&pdevinfo); if (IS_ERR(pdev)) { @@ -204,7 +203,6 @@ void *sdw_intel_init(acpi_handle *parent_handle, struct sdw_intel_res *res) return sdw_intel_add_controller(res); } -EXPORT_SYMBOL(sdw_intel_init); /** * sdw_intel_exit() - SoundWire Intel exit @@ -212,10 +210,8 @@ EXPORT_SYMBOL(sdw_intel_init); * * Delete the controller instances created and cleanup */ -void sdw_intel_exit(void *arg) +void sdw_intel_exit(struct sdw_intel_ctx *ctx) { - struct sdw_intel_ctx *ctx = arg; - sdw_intel_cleanup_pdev(ctx); kfree(ctx); } diff --git a/drivers/soundwire/mipi_disco.c b/drivers/soundwire/mipi_disco.c index 79fee1b21ab6..844e6b22974f 100644 --- a/drivers/soundwire/mipi_disco.c +++ b/drivers/soundwire/mipi_disco.c @@ -60,8 +60,7 @@ int sdw_master_read_prop(struct sdw_bus *bus) "mipi-sdw-max-clock-frequency", &prop->max_clk_freq); - nval = fwnode_property_read_u32_array(link, - "mipi-sdw-clock-frequencies-supported", NULL, 0); + nval = fwnode_property_count_u32(link, "mipi-sdw-clock-frequencies-supported"); if (nval > 0) { prop->num_clk_freq = nval; prop->clk_freq = devm_kcalloc(bus->dev, prop->num_clk_freq, @@ -87,8 +86,7 @@ int sdw_master_read_prop(struct sdw_bus *bus) } } - nval = fwnode_property_read_u32_array(link, - "mipi-sdw-supported-clock-gears", NULL, 0); + nval = fwnode_property_count_u32(link, "mipi-sdw-supported-clock-gears"); if (nval > 0) { prop->num_clk_gears = nval; prop->clk_gears = devm_kcalloc(bus->dev, prop->num_clk_gears, @@ -134,8 +132,7 @@ static int sdw_slave_read_dp0(struct sdw_slave *slave, fwnode_property_read_u32(port, "mipi-sdw-port-min-wordlength", &dp0->min_word); - nval = fwnode_property_read_u32_array(port, - "mipi-sdw-port-wordlength-configs", NULL, 0); + nval = fwnode_property_count_u32(port, "mipi-sdw-port-wordlength-configs"); if (nval > 0) { dp0->num_words = nval; @@ -193,8 +190,7 @@ static int sdw_slave_read_dpn(struct sdw_slave *slave, fwnode_property_read_u32(node, "mipi-sdw-port-min-wordlength", &dpn[i].min_word); - nval = fwnode_property_read_u32_array(node, - "mipi-sdw-port-wordlength-configs", NULL, 0); + nval = fwnode_property_count_u32(node, "mipi-sdw-port-wordlength-configs"); if (nval > 0) { dpn[i].num_words = nval; dpn[i].words = devm_kcalloc(&slave->dev, @@ -233,8 +229,7 @@ static int sdw_slave_read_dpn(struct sdw_slave *slave, fwnode_property_read_u32(node, "mipi-sdw-max-channel-number", &dpn[i].max_ch); - nval = fwnode_property_read_u32_array(node, - "mipi-sdw-channel-number-list", NULL, 0); + nval = fwnode_property_count_u32(node, "mipi-sdw-channel-number-list"); if (nval > 0) { dpn[i].num_ch = nval; dpn[i].ch = devm_kcalloc(&slave->dev, dpn[i].num_ch, @@ -248,8 +243,7 @@ static int sdw_slave_read_dpn(struct sdw_slave *slave, dpn[i].ch, dpn[i].num_ch); } - nval = fwnode_property_read_u32_array(node, - "mipi-sdw-channel-combination-list", NULL, 0); + nval = fwnode_property_count_u32(node, "mipi-sdw-channel-combination-list"); if (nval > 0) { dpn[i].num_ch_combinations = nval; dpn[i].ch_combinations = devm_kcalloc(&slave->dev, diff --git a/drivers/soundwire/qcom.c b/drivers/soundwire/qcom.c new file mode 100644 index 000000000000..1c6c6a2e0def --- /dev/null +++ b/drivers/soundwire/qcom.c @@ -0,0 +1,861 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2019, Linaro Limited + +#include <linux/clk.h> +#include <linux/completion.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_irq.h> +#include <linux/of_device.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/slimbus.h> +#include <linux/soundwire/sdw.h> +#include <linux/soundwire/sdw_registers.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include "bus.h" + +#define SWRM_COMP_HW_VERSION 0x00 +#define SWRM_COMP_CFG_ADDR 0x04 +#define SWRM_COMP_CFG_IRQ_LEVEL_OR_PULSE_MSK BIT(1) +#define SWRM_COMP_CFG_ENABLE_MSK BIT(0) +#define SWRM_COMP_PARAMS 0x100 +#define SWRM_COMP_PARAMS_DOUT_PORTS_MASK GENMASK(4, 0) +#define SWRM_COMP_PARAMS_DIN_PORTS_MASK GENMASK(9, 5) +#define SWRM_INTERRUPT_STATUS 0x200 +#define SWRM_INTERRUPT_STATUS_RMSK GENMASK(16, 0) +#define SWRM_INTERRUPT_STATUS_NEW_SLAVE_ATTACHED BIT(1) +#define SWRM_INTERRUPT_STATUS_CHANGE_ENUM_SLAVE_STATUS BIT(2) +#define SWRM_INTERRUPT_STATUS_CMD_ERROR BIT(7) +#define SWRM_INTERRUPT_STATUS_SPECIAL_CMD_ID_FINISHED BIT(10) +#define SWRM_INTERRUPT_MASK_ADDR 0x204 +#define SWRM_INTERRUPT_CLEAR 0x208 +#define SWRM_CMD_FIFO_WR_CMD 0x300 +#define SWRM_CMD_FIFO_RD_CMD 0x304 +#define SWRM_CMD_FIFO_CMD 0x308 +#define SWRM_CMD_FIFO_STATUS 0x30C +#define SWRM_CMD_FIFO_CFG_ADDR 0x314 +#define SWRM_RD_WR_CMD_RETRIES 0x7 +#define SWRM_CMD_FIFO_RD_FIFO_ADDR 0x318 +#define SWRM_ENUMERATOR_CFG_ADDR 0x500 +#define SWRM_MCP_FRAME_CTRL_BANK_ADDR(m) (0x101C + 0x40 * (m)) +#define SWRM_MCP_FRAME_CTRL_BANK_ROW_CTRL_SHFT 3 +#define SWRM_MCP_FRAME_CTRL_BANK_COL_CTRL_BMSK GENMASK(2, 0) +#define SWRM_MCP_FRAME_CTRL_BANK_ROW_CTRL_BMSK GENMASK(7, 3) +#define SWRM_MCP_FRAME_CTRL_BANK_COL_CTRL_SHFT 0 +#define SWRM_MCP_CFG_ADDR 0x1048 +#define SWRM_MCP_CFG_MAX_NUM_OF_CMD_NO_PINGS_BMSK GENMASK(21, 17) +#define SWRM_MCP_CFG_MAX_NUM_OF_CMD_NO_PINGS_SHFT 0x11 +#define SWRM_DEF_CMD_NO_PINGS 0x1f +#define SWRM_MCP_STATUS 0x104C +#define SWRM_MCP_STATUS_BANK_NUM_MASK BIT(0) +#define SWRM_MCP_SLV_STATUS 0x1090 +#define SWRM_MCP_SLV_STATUS_MASK GENMASK(1, 0) +#define SWRM_DP_PORT_CTRL_BANK(n, m) (0x1124 + 0x100 * (n - 1) + 0x40 * m) +#define SWRM_DP_PORT_CTRL_EN_CHAN_SHFT 0x18 +#define SWRM_DP_PORT_CTRL_OFFSET2_SHFT 0x10 +#define SWRM_DP_PORT_CTRL_OFFSET1_SHFT 0x08 +#define SWRM_AHB_BRIDGE_WR_DATA_0 0xc85 +#define SWRM_AHB_BRIDGE_WR_ADDR_0 0xc89 +#define SWRM_AHB_BRIDGE_RD_ADDR_0 0xc8d +#define SWRM_AHB_BRIDGE_RD_DATA_0 0xc91 + +#define SWRM_REG_VAL_PACK(data, dev, id, reg) \ + ((reg) | ((id) << 16) | ((dev) << 20) | ((data) << 24)) + +#define SWRM_MAX_ROW_VAL 0 /* Rows = 48 */ +#define SWRM_DEFAULT_ROWS 48 +#define SWRM_MIN_COL_VAL 0 /* Cols = 2 */ +#define SWRM_DEFAULT_COL 16 +#define SWRM_MAX_COL_VAL 7 +#define SWRM_SPECIAL_CMD_ID 0xF +#define MAX_FREQ_NUM 1 +#define TIMEOUT_MS (2 * HZ) +#define QCOM_SWRM_MAX_RD_LEN 0xf +#define QCOM_SDW_MAX_PORTS 14 +#define DEFAULT_CLK_FREQ 9600000 +#define SWRM_MAX_DAIS 0xF + +struct qcom_swrm_port_config { + u8 si; + u8 off1; + u8 off2; +}; + +struct qcom_swrm_ctrl { + struct sdw_bus bus; + struct device *dev; + struct regmap *regmap; + struct completion *comp; + struct work_struct slave_work; + /* read/write lock */ + spinlock_t comp_lock; + /* Port alloc/free lock */ + struct mutex port_lock; + struct clk *hclk; + u8 wr_cmd_id; + u8 rd_cmd_id; + int irq; + unsigned int version; + int num_din_ports; + int num_dout_ports; + unsigned long dout_port_mask; + unsigned long din_port_mask; + struct qcom_swrm_port_config pconfig[QCOM_SDW_MAX_PORTS]; + struct sdw_stream_runtime *sruntime[SWRM_MAX_DAIS]; + enum sdw_slave_status status[SDW_MAX_DEVICES]; + int (*reg_read)(struct qcom_swrm_ctrl *ctrl, int reg, u32 *val); + int (*reg_write)(struct qcom_swrm_ctrl *ctrl, int reg, int val); +}; + +#define to_qcom_sdw(b) container_of(b, struct qcom_swrm_ctrl, bus) + +static int qcom_swrm_abh_reg_read(struct qcom_swrm_ctrl *ctrl, int reg, + u32 *val) +{ + struct regmap *wcd_regmap = ctrl->regmap; + int ret; + + /* pg register + offset */ + ret = regmap_bulk_write(wcd_regmap, SWRM_AHB_BRIDGE_RD_ADDR_0, + (u8 *)®, 4); + if (ret < 0) + return SDW_CMD_FAIL; + + ret = regmap_bulk_read(wcd_regmap, SWRM_AHB_BRIDGE_RD_DATA_0, + val, 4); + if (ret < 0) + return SDW_CMD_FAIL; + + return SDW_CMD_OK; +} + +static int qcom_swrm_ahb_reg_write(struct qcom_swrm_ctrl *ctrl, + int reg, int val) +{ + struct regmap *wcd_regmap = ctrl->regmap; + int ret; + /* pg register + offset */ + ret = regmap_bulk_write(wcd_regmap, SWRM_AHB_BRIDGE_WR_DATA_0, + (u8 *)&val, 4); + if (ret) + return SDW_CMD_FAIL; + + /* write address register */ + ret = regmap_bulk_write(wcd_regmap, SWRM_AHB_BRIDGE_WR_ADDR_0, + (u8 *)®, 4); + if (ret) + return SDW_CMD_FAIL; + + return SDW_CMD_OK; +} + +static int qcom_swrm_cmd_fifo_wr_cmd(struct qcom_swrm_ctrl *ctrl, u8 cmd_data, + u8 dev_addr, u16 reg_addr) +{ + DECLARE_COMPLETION_ONSTACK(comp); + unsigned long flags; + u32 val; + int ret; + + spin_lock_irqsave(&ctrl->comp_lock, flags); + ctrl->comp = ∁ + spin_unlock_irqrestore(&ctrl->comp_lock, flags); + val = SWRM_REG_VAL_PACK(cmd_data, dev_addr, + SWRM_SPECIAL_CMD_ID, reg_addr); + ret = ctrl->reg_write(ctrl, SWRM_CMD_FIFO_WR_CMD, val); + if (ret) + goto err; + + ret = wait_for_completion_timeout(ctrl->comp, + msecs_to_jiffies(TIMEOUT_MS)); + + if (!ret) + ret = SDW_CMD_IGNORED; + else + ret = SDW_CMD_OK; +err: + spin_lock_irqsave(&ctrl->comp_lock, flags); + ctrl->comp = NULL; + spin_unlock_irqrestore(&ctrl->comp_lock, flags); + + return ret; +} + +static int qcom_swrm_cmd_fifo_rd_cmd(struct qcom_swrm_ctrl *ctrl, + u8 dev_addr, u16 reg_addr, + u32 len, u8 *rval) +{ + int i, ret; + u32 val; + DECLARE_COMPLETION_ONSTACK(comp); + unsigned long flags; + + spin_lock_irqsave(&ctrl->comp_lock, flags); + ctrl->comp = ∁ + spin_unlock_irqrestore(&ctrl->comp_lock, flags); + + val = SWRM_REG_VAL_PACK(len, dev_addr, SWRM_SPECIAL_CMD_ID, reg_addr); + ret = ctrl->reg_write(ctrl, SWRM_CMD_FIFO_RD_CMD, val); + if (ret) + goto err; + + ret = wait_for_completion_timeout(ctrl->comp, + msecs_to_jiffies(TIMEOUT_MS)); + + if (!ret) { + ret = SDW_CMD_IGNORED; + goto err; + } else { + ret = SDW_CMD_OK; + } + + for (i = 0; i < len; i++) { + ctrl->reg_read(ctrl, SWRM_CMD_FIFO_RD_FIFO_ADDR, &val); + rval[i] = val & 0xFF; + } + +err: + spin_lock_irqsave(&ctrl->comp_lock, flags); + ctrl->comp = NULL; + spin_unlock_irqrestore(&ctrl->comp_lock, flags); + + return ret; +} + +static void qcom_swrm_get_device_status(struct qcom_swrm_ctrl *ctrl) +{ + u32 val; + int i; + + ctrl->reg_read(ctrl, SWRM_MCP_SLV_STATUS, &val); + + for (i = 0; i < SDW_MAX_DEVICES; i++) { + u32 s; + + s = (val >> (i * 2)); + s &= SWRM_MCP_SLV_STATUS_MASK; + ctrl->status[i] = s; + } +} + +static irqreturn_t qcom_swrm_irq_handler(int irq, void *dev_id) +{ + struct qcom_swrm_ctrl *ctrl = dev_id; + u32 sts, value; + unsigned long flags; + + ctrl->reg_read(ctrl, SWRM_INTERRUPT_STATUS, &sts); + + if (sts & SWRM_INTERRUPT_STATUS_CMD_ERROR) { + ctrl->reg_read(ctrl, SWRM_CMD_FIFO_STATUS, &value); + dev_err_ratelimited(ctrl->dev, + "CMD error, fifo status 0x%x\n", + value); + ctrl->reg_write(ctrl, SWRM_CMD_FIFO_CMD, 0x1); + } + + if ((sts & SWRM_INTERRUPT_STATUS_NEW_SLAVE_ATTACHED) || + sts & SWRM_INTERRUPT_STATUS_CHANGE_ENUM_SLAVE_STATUS) + schedule_work(&ctrl->slave_work); + + /** + * clear the interrupt before complete() is called, as complete can + * schedule new read/writes which require interrupts, clearing the + * interrupt would avoid missing interrupts in such cases. + */ + ctrl->reg_write(ctrl, SWRM_INTERRUPT_CLEAR, sts); + + if (sts & SWRM_INTERRUPT_STATUS_SPECIAL_CMD_ID_FINISHED) { + spin_lock_irqsave(&ctrl->comp_lock, flags); + if (ctrl->comp) + complete(ctrl->comp); + spin_unlock_irqrestore(&ctrl->comp_lock, flags); + } + + return IRQ_HANDLED; +} +static int qcom_swrm_init(struct qcom_swrm_ctrl *ctrl) +{ + u32 val; + + /* Clear Rows and Cols */ + val = (SWRM_MAX_ROW_VAL << SWRM_MCP_FRAME_CTRL_BANK_ROW_CTRL_SHFT | + SWRM_MIN_COL_VAL << SWRM_MCP_FRAME_CTRL_BANK_COL_CTRL_SHFT); + + ctrl->reg_write(ctrl, SWRM_MCP_FRAME_CTRL_BANK_ADDR(0), val); + + /* Disable Auto enumeration */ + ctrl->reg_write(ctrl, SWRM_ENUMERATOR_CFG_ADDR, 0); + + /* Mask soundwire interrupts */ + ctrl->reg_write(ctrl, SWRM_INTERRUPT_MASK_ADDR, + SWRM_INTERRUPT_STATUS_RMSK); + + /* Configure No pings */ + ctrl->reg_read(ctrl, SWRM_MCP_CFG_ADDR, &val); + val &= ~SWRM_MCP_CFG_MAX_NUM_OF_CMD_NO_PINGS_BMSK; + val |= (SWRM_DEF_CMD_NO_PINGS << + SWRM_MCP_CFG_MAX_NUM_OF_CMD_NO_PINGS_SHFT); + ctrl->reg_write(ctrl, SWRM_MCP_CFG_ADDR, val); + + /* Configure number of retries of a read/write cmd */ + ctrl->reg_write(ctrl, SWRM_CMD_FIFO_CFG_ADDR, SWRM_RD_WR_CMD_RETRIES); + + /* Set IRQ to PULSE */ + ctrl->reg_write(ctrl, SWRM_COMP_CFG_ADDR, + SWRM_COMP_CFG_IRQ_LEVEL_OR_PULSE_MSK | + SWRM_COMP_CFG_ENABLE_MSK); + return 0; +} + +static enum sdw_command_response qcom_swrm_xfer_msg(struct sdw_bus *bus, + struct sdw_msg *msg) +{ + struct qcom_swrm_ctrl *ctrl = to_qcom_sdw(bus); + int ret, i, len; + + if (msg->flags == SDW_MSG_FLAG_READ) { + for (i = 0; i < msg->len;) { + if ((msg->len - i) < QCOM_SWRM_MAX_RD_LEN) + len = msg->len - i; + else + len = QCOM_SWRM_MAX_RD_LEN; + + ret = qcom_swrm_cmd_fifo_rd_cmd(ctrl, msg->dev_num, + msg->addr + i, len, + &msg->buf[i]); + if (ret) + return ret; + + i = i + len; + } + } else if (msg->flags == SDW_MSG_FLAG_WRITE) { + for (i = 0; i < msg->len; i++) { + ret = qcom_swrm_cmd_fifo_wr_cmd(ctrl, msg->buf[i], + msg->dev_num, + msg->addr + i); + if (ret) + return SDW_CMD_IGNORED; + } + } + + return SDW_CMD_OK; +} + +static int qcom_swrm_pre_bank_switch(struct sdw_bus *bus) +{ + u32 reg = SWRM_MCP_FRAME_CTRL_BANK_ADDR(bus->params.next_bank); + struct qcom_swrm_ctrl *ctrl = to_qcom_sdw(bus); + u32 val; + + ctrl->reg_read(ctrl, reg, &val); + + val &= ~SWRM_MCP_FRAME_CTRL_BANK_COL_CTRL_BMSK; + val &= ~SWRM_MCP_FRAME_CTRL_BANK_ROW_CTRL_BMSK; + + val |= (SWRM_MAX_ROW_VAL << SWRM_MCP_FRAME_CTRL_BANK_ROW_CTRL_SHFT | + SWRM_MAX_COL_VAL << SWRM_MCP_FRAME_CTRL_BANK_COL_CTRL_SHFT); + + return ctrl->reg_write(ctrl, reg, val); +} + +static int qcom_swrm_port_params(struct sdw_bus *bus, + struct sdw_port_params *p_params, + unsigned int bank) +{ + /* TBD */ + return 0; +} + +static int qcom_swrm_transport_params(struct sdw_bus *bus, + struct sdw_transport_params *params, + enum sdw_reg_bank bank) +{ + struct qcom_swrm_ctrl *ctrl = to_qcom_sdw(bus); + u32 value; + + value = params->offset1 << SWRM_DP_PORT_CTRL_OFFSET1_SHFT; + value |= params->offset2 << SWRM_DP_PORT_CTRL_OFFSET2_SHFT; + value |= params->sample_interval - 1; + + return ctrl->reg_write(ctrl, + SWRM_DP_PORT_CTRL_BANK((params->port_num), bank), + value); +} + +static int qcom_swrm_port_enable(struct sdw_bus *bus, + struct sdw_enable_ch *enable_ch, + unsigned int bank) +{ + u32 reg = SWRM_DP_PORT_CTRL_BANK(enable_ch->port_num, bank); + struct qcom_swrm_ctrl *ctrl = to_qcom_sdw(bus); + u32 val; + + ctrl->reg_read(ctrl, reg, &val); + + if (enable_ch->enable) + val |= (enable_ch->ch_mask << SWRM_DP_PORT_CTRL_EN_CHAN_SHFT); + else + val &= ~(0xff << SWRM_DP_PORT_CTRL_EN_CHAN_SHFT); + + return ctrl->reg_write(ctrl, reg, val); +} + +static struct sdw_master_port_ops qcom_swrm_port_ops = { + .dpn_set_port_params = qcom_swrm_port_params, + .dpn_set_port_transport_params = qcom_swrm_transport_params, + .dpn_port_enable_ch = qcom_swrm_port_enable, +}; + +static struct sdw_master_ops qcom_swrm_ops = { + .xfer_msg = qcom_swrm_xfer_msg, + .pre_bank_switch = qcom_swrm_pre_bank_switch, +}; + +static int qcom_swrm_compute_params(struct sdw_bus *bus) +{ + struct qcom_swrm_ctrl *ctrl = to_qcom_sdw(bus); + struct sdw_master_runtime *m_rt; + struct sdw_slave_runtime *s_rt; + struct sdw_port_runtime *p_rt; + struct qcom_swrm_port_config *pcfg; + int i = 0; + + list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) { + list_for_each_entry(p_rt, &m_rt->port_list, port_node) { + pcfg = &ctrl->pconfig[p_rt->num - 1]; + p_rt->transport_params.port_num = p_rt->num; + p_rt->transport_params.sample_interval = pcfg->si + 1; + p_rt->transport_params.offset1 = pcfg->off1; + p_rt->transport_params.offset2 = pcfg->off2; + } + + list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) { + list_for_each_entry(p_rt, &s_rt->port_list, port_node) { + pcfg = &ctrl->pconfig[i]; + p_rt->transport_params.port_num = p_rt->num; + p_rt->transport_params.sample_interval = + pcfg->si + 1; + p_rt->transport_params.offset1 = pcfg->off1; + p_rt->transport_params.offset2 = pcfg->off2; + i++; + } + } + } + + return 0; +} + +static u32 qcom_swrm_freq_tbl[MAX_FREQ_NUM] = { + DEFAULT_CLK_FREQ, +}; + +static void qcom_swrm_slave_wq(struct work_struct *work) +{ + struct qcom_swrm_ctrl *ctrl = + container_of(work, struct qcom_swrm_ctrl, slave_work); + + qcom_swrm_get_device_status(ctrl); + sdw_handle_slave_status(&ctrl->bus, ctrl->status); +} + + +static void qcom_swrm_stream_free_ports(struct qcom_swrm_ctrl *ctrl, + struct sdw_stream_runtime *stream) +{ + struct sdw_master_runtime *m_rt; + struct sdw_port_runtime *p_rt; + unsigned long *port_mask; + + mutex_lock(&ctrl->port_lock); + + list_for_each_entry(m_rt, &stream->master_list, stream_node) { + if (m_rt->direction == SDW_DATA_DIR_RX) + port_mask = &ctrl->dout_port_mask; + else + port_mask = &ctrl->din_port_mask; + + list_for_each_entry(p_rt, &m_rt->port_list, port_node) + clear_bit(p_rt->num - 1, port_mask); + } + + mutex_unlock(&ctrl->port_lock); +} + +static int qcom_swrm_stream_alloc_ports(struct qcom_swrm_ctrl *ctrl, + struct sdw_stream_runtime *stream, + struct snd_pcm_hw_params *params, + int direction) +{ + struct sdw_port_config pconfig[QCOM_SDW_MAX_PORTS]; + struct sdw_stream_config sconfig; + struct sdw_master_runtime *m_rt; + struct sdw_slave_runtime *s_rt; + struct sdw_port_runtime *p_rt; + unsigned long *port_mask; + int i, maxport, pn, nports = 0, ret = 0; + + mutex_lock(&ctrl->port_lock); + list_for_each_entry(m_rt, &stream->master_list, stream_node) { + if (m_rt->direction == SDW_DATA_DIR_RX) { + maxport = ctrl->num_dout_ports; + port_mask = &ctrl->dout_port_mask; + } else { + maxport = ctrl->num_din_ports; + port_mask = &ctrl->din_port_mask; + } + + list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) { + list_for_each_entry(p_rt, &s_rt->port_list, port_node) { + /* Port numbers start from 1 - 14*/ + pn = find_first_zero_bit(port_mask, maxport); + if (pn > (maxport - 1)) { + dev_err(ctrl->dev, "All ports busy\n"); + ret = -EBUSY; + goto err; + } + set_bit(pn, port_mask); + pconfig[nports].num = pn + 1; + pconfig[nports].ch_mask = p_rt->ch_mask; + nports++; + } + } + } + + if (direction == SNDRV_PCM_STREAM_CAPTURE) + sconfig.direction = SDW_DATA_DIR_TX; + else + sconfig.direction = SDW_DATA_DIR_RX; + + /* hw parameters wil be ignored as we only support PDM */ + sconfig.ch_count = 1; + sconfig.frame_rate = params_rate(params); + sconfig.type = stream->type; + sconfig.bps = 1; + sdw_stream_add_master(&ctrl->bus, &sconfig, pconfig, + nports, stream); +err: + if (ret) { + for (i = 0; i < nports; i++) + clear_bit(pconfig[i].num - 1, port_mask); + } + + mutex_unlock(&ctrl->port_lock); + + return ret; +} + +static int qcom_swrm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct qcom_swrm_ctrl *ctrl = dev_get_drvdata(dai->dev); + struct sdw_stream_runtime *sruntime = ctrl->sruntime[dai->id]; + int ret; + + ret = qcom_swrm_stream_alloc_ports(ctrl, sruntime, params, + substream->stream); + if (ret) + qcom_swrm_stream_free_ports(ctrl, sruntime); + + return ret; +} + +static int qcom_swrm_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct qcom_swrm_ctrl *ctrl = dev_get_drvdata(dai->dev); + struct sdw_stream_runtime *sruntime = ctrl->sruntime[dai->id]; + + qcom_swrm_stream_free_ports(ctrl, sruntime); + sdw_stream_remove_master(&ctrl->bus, sruntime); + + return 0; +} + +static int qcom_swrm_set_sdw_stream(struct snd_soc_dai *dai, + void *stream, int direction) +{ + struct qcom_swrm_ctrl *ctrl = dev_get_drvdata(dai->dev); + + ctrl->sruntime[dai->id] = stream; + + return 0; +} + +static int qcom_swrm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct qcom_swrm_ctrl *ctrl = dev_get_drvdata(dai->dev); + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct sdw_stream_runtime *sruntime; + int ret, i; + + sruntime = sdw_alloc_stream(dai->name); + if (!sruntime) + return -ENOMEM; + + ctrl->sruntime[dai->id] = sruntime; + + for (i = 0; i < rtd->num_codecs; i++) { + ret = snd_soc_dai_set_sdw_stream(rtd->codec_dais[i], sruntime, + substream->stream); + if (ret < 0 && ret != -ENOTSUPP) { + dev_err(dai->dev, "Failed to set sdw stream on %s", + rtd->codec_dais[i]->name); + sdw_release_stream(sruntime); + return ret; + } + } + + return 0; +} + +static void qcom_swrm_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct qcom_swrm_ctrl *ctrl = dev_get_drvdata(dai->dev); + + sdw_release_stream(ctrl->sruntime[dai->id]); + ctrl->sruntime[dai->id] = NULL; +} + +static const struct snd_soc_dai_ops qcom_swrm_pdm_dai_ops = { + .hw_params = qcom_swrm_hw_params, + .hw_free = qcom_swrm_hw_free, + .startup = qcom_swrm_startup, + .shutdown = qcom_swrm_shutdown, + .set_sdw_stream = qcom_swrm_set_sdw_stream, +}; + +static const struct snd_soc_component_driver qcom_swrm_dai_component = { + .name = "soundwire", +}; + +static int qcom_swrm_register_dais(struct qcom_swrm_ctrl *ctrl) +{ + int num_dais = ctrl->num_dout_ports + ctrl->num_din_ports; + struct snd_soc_dai_driver *dais; + struct snd_soc_pcm_stream *stream; + struct device *dev = ctrl->dev; + int i; + + /* PDM dais are only tested for now */ + dais = devm_kcalloc(dev, num_dais, sizeof(*dais), GFP_KERNEL); + if (!dais) + return -ENOMEM; + + for (i = 0; i < num_dais; i++) { + dais[i].name = devm_kasprintf(dev, GFP_KERNEL, "SDW Pin%d", i); + if (!dais[i].name) + return -ENOMEM; + + if (i < ctrl->num_dout_ports) + stream = &dais[i].playback; + else + stream = &dais[i].capture; + + stream->channels_min = 1; + stream->channels_max = 1; + stream->rates = SNDRV_PCM_RATE_48000; + stream->formats = SNDRV_PCM_FMTBIT_S16_LE; + + dais[i].ops = &qcom_swrm_pdm_dai_ops; + dais[i].id = i; + } + + return devm_snd_soc_register_component(ctrl->dev, + &qcom_swrm_dai_component, + dais, num_dais); +} + +static int qcom_swrm_get_port_config(struct qcom_swrm_ctrl *ctrl) +{ + struct device_node *np = ctrl->dev->of_node; + u8 off1[QCOM_SDW_MAX_PORTS]; + u8 off2[QCOM_SDW_MAX_PORTS]; + u8 si[QCOM_SDW_MAX_PORTS]; + int i, ret, nports, val; + + ctrl->reg_read(ctrl, SWRM_COMP_PARAMS, &val); + + ctrl->num_dout_ports = val & SWRM_COMP_PARAMS_DOUT_PORTS_MASK; + ctrl->num_din_ports = (val & SWRM_COMP_PARAMS_DIN_PORTS_MASK) >> 5; + + ret = of_property_read_u32(np, "qcom,din-ports", &val); + if (ret) + return ret; + + if (val > ctrl->num_din_ports) + return -EINVAL; + + ctrl->num_din_ports = val; + + ret = of_property_read_u32(np, "qcom,dout-ports", &val); + if (ret) + return ret; + + if (val > ctrl->num_dout_ports) + return -EINVAL; + + ctrl->num_dout_ports = val; + + nports = ctrl->num_dout_ports + ctrl->num_din_ports; + + ret = of_property_read_u8_array(np, "qcom,ports-offset1", + off1, nports); + if (ret) + return ret; + + ret = of_property_read_u8_array(np, "qcom,ports-offset2", + off2, nports); + if (ret) + return ret; + + ret = of_property_read_u8_array(np, "qcom,ports-sinterval-low", + si, nports); + if (ret) + return ret; + + for (i = 0; i < nports; i++) { + ctrl->pconfig[i].si = si[i]; + ctrl->pconfig[i].off1 = off1[i]; + ctrl->pconfig[i].off2 = off2[i]; + } + + return 0; +} + +static int qcom_swrm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct sdw_master_prop *prop; + struct sdw_bus_params *params; + struct qcom_swrm_ctrl *ctrl; + int ret; + u32 val; + + ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL); + if (!ctrl) + return -ENOMEM; + + if (dev->parent->bus == &slimbus_bus) { + ctrl->reg_read = qcom_swrm_abh_reg_read; + ctrl->reg_write = qcom_swrm_ahb_reg_write; + ctrl->regmap = dev_get_regmap(dev->parent, NULL); + if (!ctrl->regmap) + return -EINVAL; + } else { + /* Only WCD based SoundWire controller is supported */ + return -ENOTSUPP; + } + + ctrl->irq = of_irq_get(dev->of_node, 0); + if (ctrl->irq < 0) + return ctrl->irq; + + ctrl->hclk = devm_clk_get(dev, "iface"); + if (IS_ERR(ctrl->hclk)) + return PTR_ERR(ctrl->hclk); + + clk_prepare_enable(ctrl->hclk); + + ctrl->dev = dev; + dev_set_drvdata(&pdev->dev, ctrl); + spin_lock_init(&ctrl->comp_lock); + mutex_init(&ctrl->port_lock); + INIT_WORK(&ctrl->slave_work, qcom_swrm_slave_wq); + + ctrl->bus.dev = dev; + ctrl->bus.ops = &qcom_swrm_ops; + ctrl->bus.port_ops = &qcom_swrm_port_ops; + ctrl->bus.compute_params = &qcom_swrm_compute_params; + + ret = qcom_swrm_get_port_config(ctrl); + if (ret) + return ret; + + params = &ctrl->bus.params; + params->max_dr_freq = DEFAULT_CLK_FREQ; + params->curr_dr_freq = DEFAULT_CLK_FREQ; + params->col = SWRM_DEFAULT_COL; + params->row = SWRM_DEFAULT_ROWS; + ctrl->reg_read(ctrl, SWRM_MCP_STATUS, &val); + params->curr_bank = val & SWRM_MCP_STATUS_BANK_NUM_MASK; + params->next_bank = !params->curr_bank; + + prop = &ctrl->bus.prop; + prop->max_clk_freq = DEFAULT_CLK_FREQ; + prop->num_clk_gears = 0; + prop->num_clk_freq = MAX_FREQ_NUM; + prop->clk_freq = &qcom_swrm_freq_tbl[0]; + prop->default_col = SWRM_DEFAULT_COL; + prop->default_row = SWRM_DEFAULT_ROWS; + + ctrl->reg_read(ctrl, SWRM_COMP_HW_VERSION, &ctrl->version); + + ret = devm_request_threaded_irq(dev, ctrl->irq, NULL, + qcom_swrm_irq_handler, + IRQF_TRIGGER_RISING, + "soundwire", ctrl); + if (ret) { + dev_err(dev, "Failed to request soundwire irq\n"); + goto err; + } + + ret = sdw_add_bus_master(&ctrl->bus); + if (ret) { + dev_err(dev, "Failed to register Soundwire controller (%d)\n", + ret); + goto err; + } + + qcom_swrm_init(ctrl); + ret = qcom_swrm_register_dais(ctrl); + if (ret) + goto err; + + dev_info(dev, "Qualcomm Soundwire controller v%x.%x.%x Registered\n", + (ctrl->version >> 24) & 0xff, (ctrl->version >> 16) & 0xff, + ctrl->version & 0xffff); + + return 0; +err: + clk_disable_unprepare(ctrl->hclk); + return ret; +} + +static int qcom_swrm_remove(struct platform_device *pdev) +{ + struct qcom_swrm_ctrl *ctrl = dev_get_drvdata(&pdev->dev); + + sdw_delete_bus_master(&ctrl->bus); + clk_disable_unprepare(ctrl->hclk); + + return 0; +} + +static const struct of_device_id qcom_swrm_of_match[] = { + { .compatible = "qcom,soundwire-v1.3.0", }, + {/* sentinel */}, +}; + +MODULE_DEVICE_TABLE(of, qcom_swrm_of_match); + +static struct platform_driver qcom_swrm_driver = { + .probe = &qcom_swrm_probe, + .remove = &qcom_swrm_remove, + .driver = { + .name = "qcom-soundwire", + .of_match_table = qcom_swrm_of_match, + } +}; +module_platform_driver(qcom_swrm_driver); + +MODULE_DESCRIPTION("Qualcomm soundwire driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/soundwire/slave.c b/drivers/soundwire/slave.c index f39a5815e25d..19919975bb6d 100644 --- a/drivers/soundwire/slave.c +++ b/drivers/soundwire/slave.c @@ -2,6 +2,7 @@ // Copyright(c) 2015-17 Intel Corporation. #include <linux/acpi.h> +#include <linux/of.h> #include <linux/soundwire/sdw.h> #include <linux/soundwire/sdw_type.h> #include "bus.h" @@ -28,13 +29,21 @@ static int sdw_slave_add(struct sdw_bus *bus, slave->dev.parent = bus->dev; slave->dev.fwnode = fwnode; - /* name shall be sdw:link:mfg:part:class:unique */ - dev_set_name(&slave->dev, "sdw:%x:%x:%x:%x:%x", - bus->link_id, id->mfg_id, id->part_id, - id->class_id, id->unique_id); + if (id->unique_id == SDW_IGNORED_UNIQUE_ID) { + /* name shall be sdw:link:mfg:part:class */ + dev_set_name(&slave->dev, "sdw:%x:%x:%x:%x", + bus->link_id, id->mfg_id, id->part_id, + id->class_id); + } else { + /* name shall be sdw:link:mfg:part:class:unique */ + dev_set_name(&slave->dev, "sdw:%x:%x:%x:%x:%x", + bus->link_id, id->mfg_id, id->part_id, + id->class_id, id->unique_id); + } slave->dev.release = sdw_slave_release; slave->dev.bus = &sdw_bus_type; + slave->dev.of_node = of_node_get(to_of_node(fwnode)); slave->bus = bus; slave->status = SDW_SLAVE_UNATTACHED; slave->dev_num = 0; @@ -56,11 +65,42 @@ static int sdw_slave_add(struct sdw_bus *bus, mutex_unlock(&bus->bus_lock); put_device(&slave->dev); } + sdw_slave_debugfs_init(slave); return ret; } #if IS_ENABLED(CONFIG_ACPI) + +static bool find_slave(struct sdw_bus *bus, + struct acpi_device *adev, + struct sdw_slave_id *id) +{ + unsigned long long addr; + unsigned int link_id; + acpi_status status; + + status = acpi_evaluate_integer(adev->handle, + METHOD_NAME__ADR, NULL, &addr); + + if (ACPI_FAILURE(status)) { + dev_err(bus->dev, "_ADR resolution failed: %x\n", + status); + return false; + } + + /* Extract link id from ADR, Bit 51 to 48 (included) */ + link_id = (addr >> 48) & GENMASK(3, 0); + + /* Check for link_id match */ + if (link_id != bus->link_id) + return false; + + sdw_extract_slave_id(bus, addr, id); + + return true; +} + /* * sdw_acpi_find_slaves() - Find Slave devices in Master ACPI node * @bus: SDW bus instance @@ -70,6 +110,7 @@ static int sdw_slave_add(struct sdw_bus *bus, int sdw_acpi_find_slaves(struct sdw_bus *bus) { struct acpi_device *adev, *parent; + struct acpi_device *adev2, *parent2; parent = ACPI_COMPANION(bus->dev); if (!parent) { @@ -78,28 +119,46 @@ int sdw_acpi_find_slaves(struct sdw_bus *bus) } list_for_each_entry(adev, &parent->children, node) { - unsigned long long addr; struct sdw_slave_id id; - unsigned int link_id; - acpi_status status; + struct sdw_slave_id id2; + bool ignore_unique_id = true; - status = acpi_evaluate_integer(adev->handle, - METHOD_NAME__ADR, NULL, &addr); + if (!find_slave(bus, adev, &id)) + continue; - if (ACPI_FAILURE(status)) { - dev_err(bus->dev, "_ADR resolution failed: %x\n", - status); - return status; + /* brute-force O(N^2) search for duplicates */ + parent2 = parent; + list_for_each_entry(adev2, &parent2->children, node) { + + if (adev == adev2) + continue; + + if (!find_slave(bus, adev2, &id2)) + continue; + + if (id.sdw_version != id2.sdw_version || + id.mfg_id != id2.mfg_id || + id.part_id != id2.part_id || + id.class_id != id2.class_id) + continue; + + if (id.unique_id != id2.unique_id) { + dev_dbg(bus->dev, + "Valid unique IDs %x %x for Slave mfg %x part %d\n", + id.unique_id, id2.unique_id, + id.mfg_id, id.part_id); + ignore_unique_id = false; + } else { + dev_err(bus->dev, + "Invalid unique IDs %x %x for Slave mfg %x part %d\n", + id.unique_id, id2.unique_id, + id.mfg_id, id.part_id); + return -ENODEV; + } } - /* Extract link id from ADR, Bit 51 to 48 (included) */ - link_id = (addr >> 48) & GENMASK(3, 0); - - /* Check for link_id match */ - if (link_id != bus->link_id) - continue; - - sdw_extract_slave_id(bus, addr, &id); + if (ignore_unique_id) + id.unique_id = SDW_IGNORED_UNIQUE_ID; /* * don't error check for sdw_slave_add as we want to continue @@ -112,3 +171,54 @@ int sdw_acpi_find_slaves(struct sdw_bus *bus) } #endif + +/* + * sdw_of_find_slaves() - Find Slave devices in master device tree node + * @bus: SDW bus instance + * + * Scans Master DT node for SDW child Slave devices and registers it. + */ +int sdw_of_find_slaves(struct sdw_bus *bus) +{ + struct device *dev = bus->dev; + struct device_node *node; + + for_each_child_of_node(bus->dev->of_node, node) { + int link_id, ret, len; + unsigned int sdw_version; + const char *compat = NULL; + struct sdw_slave_id id; + const __be32 *addr; + + compat = of_get_property(node, "compatible", NULL); + if (!compat) + continue; + + ret = sscanf(compat, "sdw%01x%04hx%04hx%02hhx", &sdw_version, + &id.mfg_id, &id.part_id, &id.class_id); + + if (ret != 4) { + dev_err(dev, "Invalid compatible string found %s\n", + compat); + continue; + } + + addr = of_get_property(node, "reg", &len); + if (!addr || (len < 2 * sizeof(u32))) { + dev_err(dev, "Invalid Link and Instance ID\n"); + continue; + } + + link_id = be32_to_cpup(addr++); + id.unique_id = be32_to_cpup(addr); + id.sdw_version = sdw_version; + + /* Check for link_id match */ + if (link_id != bus->link_id) + continue; + + sdw_slave_add(bus, &id, of_fwnode_handle(node)); + } + + return 0; +} diff --git a/drivers/soundwire/stream.c b/drivers/soundwire/stream.c index a0476755a459..178ae92b8cc1 100644 --- a/drivers/soundwire/stream.c +++ b/drivers/soundwire/stream.c @@ -21,37 +21,39 @@ * The rows are arranged as per the array index value programmed * in register. The index 15 has dummy value 0 in order to fill hole. */ -int rows[SDW_FRAME_ROWS] = {48, 50, 60, 64, 75, 80, 125, 147, +int sdw_rows[SDW_FRAME_ROWS] = {48, 50, 60, 64, 75, 80, 125, 147, 96, 100, 120, 128, 150, 160, 250, 0, 192, 200, 240, 256, 72, 144, 90, 180}; -int cols[SDW_FRAME_COLS] = {2, 4, 6, 8, 10, 12, 14, 16}; +int sdw_cols[SDW_FRAME_COLS] = {2, 4, 6, 8, 10, 12, 14, 16}; -static int sdw_find_col_index(int col) +int sdw_find_col_index(int col) { int i; for (i = 0; i < SDW_FRAME_COLS; i++) { - if (cols[i] == col) + if (sdw_cols[i] == col) return i; } pr_warn("Requested column not found, selecting lowest column no: 2\n"); return 0; } +EXPORT_SYMBOL(sdw_find_col_index); -static int sdw_find_row_index(int row) +int sdw_find_row_index(int row) { int i; for (i = 0; i < SDW_FRAME_ROWS; i++) { - if (rows[i] == row) + if (sdw_rows[i] == row) return i; } pr_warn("Requested row not found, selecting lowest row no: 48\n"); return 0; } +EXPORT_SYMBOL(sdw_find_row_index); static int _sdw_program_slave_port_params(struct sdw_bus *bus, struct sdw_slave *slave, @@ -367,7 +369,7 @@ static int sdw_enable_disable_master_ports(struct sdw_master_runtime *m_rt, static int sdw_enable_disable_ports(struct sdw_master_runtime *m_rt, bool en) { struct sdw_port_runtime *s_port, *m_port; - struct sdw_slave_runtime *s_rt = NULL; + struct sdw_slave_runtime *s_rt; int ret = 0; /* Enable/Disable Slave port(s) */ @@ -415,7 +417,7 @@ static int sdw_prep_deprep_slave_ports(struct sdw_bus *bus, struct sdw_port_runtime *p_rt, bool prep) { - struct completion *port_ready = NULL; + struct completion *port_ready; struct sdw_dpn_prop *dpn_prop; struct sdw_prepare_ch prep_ch; unsigned int time_left; @@ -535,7 +537,7 @@ static int sdw_prep_deprep_master_ports(struct sdw_master_runtime *m_rt, */ static int sdw_prep_deprep_ports(struct sdw_master_runtime *m_rt, bool prep) { - struct sdw_slave_runtime *s_rt = NULL; + struct sdw_slave_runtime *s_rt; struct sdw_port_runtime *p_rt; int ret = 0; @@ -603,7 +605,7 @@ static int sdw_notify_config(struct sdw_master_runtime *m_rt) */ static int sdw_program_params(struct sdw_bus *bus) { - struct sdw_master_runtime *m_rt = NULL; + struct sdw_master_runtime *m_rt; int ret = 0; list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) { @@ -640,8 +642,8 @@ static int sdw_bank_switch(struct sdw_bus *bus, int m_rt_count) int col_index, row_index; bool multi_link; struct sdw_msg *wr_msg; - u8 *wbuf = NULL; - int ret = 0; + u8 *wbuf; + int ret; u16 addr; wr_msg = kzalloc(sizeof(*wr_msg), GFP_KERNEL); @@ -739,9 +741,9 @@ static int sdw_ml_sync_bank_switch(struct sdw_bus *bus) static int do_bank_switch(struct sdw_stream_runtime *stream) { - struct sdw_master_runtime *m_rt = NULL; + struct sdw_master_runtime *m_rt; const struct sdw_master_ops *ops; - struct sdw_bus *bus = NULL; + struct sdw_bus *bus; bool multi_link = false; int ret = 0; @@ -863,7 +865,7 @@ EXPORT_SYMBOL(sdw_release_stream); * sdw_alloc_stream should be called only once per stream. Typically * invoked from ALSA/ASoC machine/platform driver. */ -struct sdw_stream_runtime *sdw_alloc_stream(char *stream_name) +struct sdw_stream_runtime *sdw_alloc_stream(const char *stream_name) { struct sdw_stream_runtime *stream; @@ -884,7 +886,7 @@ static struct sdw_master_runtime *sdw_find_master_rt(struct sdw_bus *bus, struct sdw_stream_runtime *stream) { - struct sdw_master_runtime *m_rt = NULL; + struct sdw_master_runtime *m_rt; /* Retrieve Bus handle if already available */ list_for_each_entry(m_rt, &stream->master_list, stream_node) { @@ -953,7 +955,7 @@ static struct sdw_slave_runtime struct sdw_stream_config *stream_config, struct sdw_stream_runtime *stream) { - struct sdw_slave_runtime *s_rt = NULL; + struct sdw_slave_runtime *s_rt; s_rt = kzalloc(sizeof(*s_rt), GFP_KERNEL); if (!s_rt) @@ -1259,7 +1261,7 @@ int sdw_stream_add_master(struct sdw_bus *bus, unsigned int num_ports, struct sdw_stream_runtime *stream) { - struct sdw_master_runtime *m_rt = NULL; + struct sdw_master_runtime *m_rt; int ret; mutex_lock(&bus->bus_lock); @@ -1426,7 +1428,7 @@ struct sdw_dpn_prop *sdw_get_slave_dpn_prop(struct sdw_slave *slave, */ static void sdw_acquire_bus_lock(struct sdw_stream_runtime *stream) { - struct sdw_master_runtime *m_rt = NULL; + struct sdw_master_runtime *m_rt; struct sdw_bus *bus = NULL; /* Iterate for all Master(s) in Master list */ @@ -1460,9 +1462,9 @@ static void sdw_release_bus_lock(struct sdw_stream_runtime *stream) static int _sdw_prepare_stream(struct sdw_stream_runtime *stream) { - struct sdw_master_runtime *m_rt = NULL; + struct sdw_master_runtime *m_rt; struct sdw_bus *bus = NULL; - struct sdw_master_prop *prop = NULL; + struct sdw_master_prop *prop; struct sdw_bus_params params; int ret; @@ -1483,6 +1485,16 @@ static int _sdw_prepare_stream(struct sdw_stream_runtime *stream) bus->params.bandwidth += m_rt->stream->params.rate * m_rt->ch_count * m_rt->stream->params.bps; + /* Compute params */ + if (bus->compute_params) { + ret = bus->compute_params(bus); + if (ret < 0) { + dev_err(bus->dev, "Compute params failed: %d", + ret); + return ret; + } + } + /* Program params */ ret = sdw_program_params(bus); if (ret < 0) { @@ -1491,6 +1503,11 @@ static int _sdw_prepare_stream(struct sdw_stream_runtime *stream) } } + if (!bus) { + pr_err("Configuration error in %s\n", __func__); + return -EINVAL; + } + ret = do_bank_switch(stream); if (ret < 0) { dev_err(bus->dev, "Bank switch failed: %d\n", ret); @@ -1537,8 +1554,6 @@ int sdw_prepare_stream(struct sdw_stream_runtime *stream) sdw_acquire_bus_lock(stream); ret = _sdw_prepare_stream(stream); - if (ret < 0) - pr_err("Prepare for stream:%s failed: %d\n", stream->name, ret); sdw_release_bus_lock(stream); return ret; @@ -1547,7 +1562,7 @@ EXPORT_SYMBOL(sdw_prepare_stream); static int _sdw_enable_stream(struct sdw_stream_runtime *stream) { - struct sdw_master_runtime *m_rt = NULL; + struct sdw_master_runtime *m_rt; struct sdw_bus *bus = NULL; int ret; @@ -1571,6 +1586,11 @@ static int _sdw_enable_stream(struct sdw_stream_runtime *stream) } } + if (!bus) { + pr_err("Configuration error in %s\n", __func__); + return -EINVAL; + } + ret = do_bank_switch(stream); if (ret < 0) { dev_err(bus->dev, "Bank switch failed: %d\n", ret); @@ -1590,7 +1610,7 @@ static int _sdw_enable_stream(struct sdw_stream_runtime *stream) */ int sdw_enable_stream(struct sdw_stream_runtime *stream) { - int ret = 0; + int ret; if (!stream) { pr_err("SoundWire: Handle not found for stream\n"); @@ -1600,8 +1620,6 @@ int sdw_enable_stream(struct sdw_stream_runtime *stream) sdw_acquire_bus_lock(stream); ret = _sdw_enable_stream(stream); - if (ret < 0) - pr_err("Enable for stream:%s failed: %d\n", stream->name, ret); sdw_release_bus_lock(stream); return ret; @@ -1610,12 +1628,12 @@ EXPORT_SYMBOL(sdw_enable_stream); static int _sdw_disable_stream(struct sdw_stream_runtime *stream) { - struct sdw_master_runtime *m_rt = NULL; - struct sdw_bus *bus = NULL; + struct sdw_master_runtime *m_rt; int ret; list_for_each_entry(m_rt, &stream->master_list, stream_node) { - bus = m_rt->bus; + struct sdw_bus *bus = m_rt->bus; + /* Disable port(s) */ ret = sdw_enable_disable_ports(m_rt, false); if (ret < 0) { @@ -1626,7 +1644,8 @@ static int _sdw_disable_stream(struct sdw_stream_runtime *stream) stream->state = SDW_STREAM_DISABLED; list_for_each_entry(m_rt, &stream->master_list, stream_node) { - bus = m_rt->bus; + struct sdw_bus *bus = m_rt->bus; + /* Program params */ ret = sdw_program_params(bus); if (ret < 0) { @@ -1635,7 +1654,25 @@ static int _sdw_disable_stream(struct sdw_stream_runtime *stream) } } - return do_bank_switch(stream); + ret = do_bank_switch(stream); + if (ret < 0) { + pr_err("Bank switch failed: %d\n", ret); + return ret; + } + + /* make sure alternate bank (previous current) is also disabled */ + list_for_each_entry(m_rt, &stream->master_list, stream_node) { + struct sdw_bus *bus = m_rt->bus; + + /* Disable port(s) */ + ret = sdw_enable_disable_ports(m_rt, false); + if (ret < 0) { + dev_err(bus->dev, "Disable port(s) failed: %d\n", ret); + return ret; + } + } + + return 0; } /** @@ -1647,7 +1684,7 @@ static int _sdw_disable_stream(struct sdw_stream_runtime *stream) */ int sdw_disable_stream(struct sdw_stream_runtime *stream) { - int ret = 0; + int ret; if (!stream) { pr_err("SoundWire: Handle not found for stream\n"); @@ -1657,8 +1694,6 @@ int sdw_disable_stream(struct sdw_stream_runtime *stream) sdw_acquire_bus_lock(stream); ret = _sdw_disable_stream(stream); - if (ret < 0) - pr_err("Disable for stream:%s failed: %d\n", stream->name, ret); sdw_release_bus_lock(stream); return ret; @@ -1667,8 +1702,8 @@ EXPORT_SYMBOL(sdw_disable_stream); static int _sdw_deprepare_stream(struct sdw_stream_runtime *stream) { - struct sdw_master_runtime *m_rt = NULL; - struct sdw_bus *bus = NULL; + struct sdw_master_runtime *m_rt; + struct sdw_bus *bus; int ret = 0; list_for_each_entry(m_rt, &stream->master_list, stream_node) { @@ -1706,7 +1741,7 @@ static int _sdw_deprepare_stream(struct sdw_stream_runtime *stream) */ int sdw_deprepare_stream(struct sdw_stream_runtime *stream) { - int ret = 0; + int ret; if (!stream) { pr_err("SoundWire: Handle not found for stream\n"); @@ -1715,8 +1750,6 @@ int sdw_deprepare_stream(struct sdw_stream_runtime *stream) sdw_acquire_bus_lock(stream); ret = _sdw_deprepare_stream(stream); - if (ret < 0) - pr_err("De-prepare for stream:%d failed: %d\n", ret, ret); sdw_release_bus_lock(stream); return ret; |