[alsa-devel] [RFC PATCH 6/6] soundwire: qcom: add support for SoundWire controller

Srinivas Kandagatla srinivas.kandagatla at linaro.org
Sun Jun 9 14:15:37 CEST 2019


Thanks for taking time to review,
I agre with most of the comments specially handling returns and making 
code more readable.
Will fix them in next version.

On 08/06/2019 22:53, Cezary Rojewski wrote:
> On 2019-06-07 10:56, Srinivas Kandagatla wrote:
>> Qualcomm SoundWire Master controller is present in most Qualcomm SoCs
>> either integrated as part of WCD audio codecs via slimbus or
>> as part of SOC I/O.
>>
>> This patchset adds support to a very basic controller which has been
>> tested with WCD934x SoundWire controller connected to WSA881x smart
>> speaker amplifiers.
>>
>> Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla at linaro.org>
>> ---
>>   drivers/soundwire/Kconfig  |   9 +
>>   drivers/soundwire/Makefile |   4 +
>>   drivers/soundwire/qcom.c   | 983 +++++++++++++++++++++++++++++++++++++
>>   3 files changed, 996 insertions(+)
>>   create mode 100644 drivers/soundwire/qcom.c
>>
>> diff --git a/drivers/soundwire/Kconfig b/drivers/soundwire/Kconfig
>> index 53b55b79c4af..f44d4f36dbbb 100644
>> --- a/drivers/soundwire/Kconfig
>> +++ b/drivers/soundwire/Kconfig
>> @@ -34,4 +34,13 @@ config SOUNDWIRE_INTEL
>>         enable this config option to get the SoundWire support for that
>>         device.
>> +config SOUNDWIRE_QCOM
>> +    tristate "Qualcomm SoundWire Master driver"
>> +    select SOUNDWIRE_BUS
>> +    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 5817beaca0e1..f4ebfde31372 100644
>> --- a/drivers/soundwire/Makefile
>> +++ b/drivers/soundwire/Makefile
>> @@ -16,3 +16,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/qcom.c b/drivers/soundwire/qcom.c
>> new file mode 100644
>> index 000000000000..d1722d44d217
>> --- /dev/null
>> +++ b/drivers/soundwire/qcom.c
>> @@ -0,0 +1,983 @@
>> +// 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_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"
>> +
> 
> Pierre already pointed this out - SWR looks odd. During my time with 
> Soundwire I've met SDW and SNDW, but it's the first time I see SWR.

These names are derived from register names from hw datasheet.
So I have not much choice here other than using them as it. May be 
adding QCOM_ prefix make it bit more non-confusing.
> 
> You seem to shortcut every reg here similarly to how it's done in SDW 
> spec. INTERRUPT is represented by INT there, and by doing so, this 
> define block would look more like a real family.
> 
Will do that in next version.!

>> +#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_CMD_FIFO_CFG_NUM_OF_CMD_RETRY_SHFT            0x0
>> +#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_SSP_PERIOD_SHFT        16
>> +#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_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_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)
> 
> Some of these you align, others leave with the equal amount of tabs 
> despite different widths.
>
I will take a closer look at such instance before sending next version.

>> +#define SWRM_DP_PORT_CTRL2_BANK(n, m)    (0x1126 + 0x100 * (n - 1) + 
>> 0x40 * m)
> 
> Consider reusing _CTRL_ and simply adding offset for 2_.
Okay.

> 
>> +#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                0xc885
>> +#define SWRM_AHB_BRIDGE_WR_ADDR_0                0xc889
>> +#define SWRM_AHB_BRIDGE_RD_ADDR_0                0xc88d
>> +#define SWRM_AHB_BRIDGE_RD_DATA_0                0xc891
>> +
>> +#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_SPECIAL_CMD_ID    0xF
>> +#define MAX_FREQ_NUM        1
>> +#define TIMEOUT_MS        1000
>> +#define QCOM_SWRM_MAX_RD_LEN    0xf
>> +#define DEFAULT_CLK_FREQ    9600000
>> +#define SWRM_MAX_DAIS        0xF
> 
> Given the scale of this block, it might be good to reiterate all defines 
> and see if indeed all are QCom specific. Maybe some could be replaced by 
> equivalents from common code.
> 
I did take a look at common defines, however these values are pretty 
much specific to this controller configuration.>> +
>> +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 sp_cmd_comp;
>> +    struct work_struct slave_work;
>> +    /* read/write lock */
>> +    struct mutex lock;
>> +    /* Port alloc/free lock */
>> +    struct mutex port_lock;
>> +    struct clk *hclk;
>> +    int fifo_status;
>> +    void __iomem *base;
>> +    u8 wr_cmd_id;
>> +    u8 rd_cmd_id;
>> +    int irq;
>> +    unsigned int version;
> 
> Given the fact you don't use version field directly and always shift it, 
> I'd consider making use of union here to listing version bits 
> explicitly. Overall size won't change.
Okay, I tried to keep most of the driver simple as I could as this is 
the first version, I will explore adding union as you suggested.

> 
>> +    int num_din_ports;
>> +    int num_dout_ports;
>> +    unsigned long dout_port_mask;
>> +    unsigned long din_port_mask;
>> +    struct qcom_swrm_port_config pconfig[SDW_MAX_PORTS];
>> +    struct sdw_stream_runtime *sruntime[SWRM_MAX_DAIS];
>> +    enum sdw_slave_status status[SDW_MAX_DEVICES];
>> +    u32 (*reg_read)(struct qcom_swrm_ctrl *ctrl, int reg);
>> +    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)
>> +
>> +struct usecase {
>> +    u8 num_port;
>> +    u8 num_ch;
>> +    u32 chrate;
>> +};
>> +
> 
> "usecase" looks ambiguous at best.

Its leftover

> 
>> +static u32 qcom_swrm_slim_reg_read(struct qcom_swrm_ctrl *ctrl, int reg)
>> +{
>> +    struct regmap *wcd_regmap = ctrl->regmap;
>> +    u32 val = 0, ret;
>> +
>> +    /* pg register + offset */
>> +    regmap_bulk_write(wcd_regmap, SWRM_AHB_BRIDGE_RD_ADDR_0,
>> +              (u8 *)&reg, 4);
>> +
>> +    ret = regmap_bulk_read(wcd_regmap, SWRM_AHB_BRIDGE_RD_DATA_0,
>> +                   (u8 *)&val, 4);
>> +    if (ret < 0)
>> +        dev_err(ctrl->dev, "Read Failure (%d)\n", ret);
>> +
>> +    return val;
>> +}
>> +
>> +static u32 qcom_swrm_mmio_reg_read(struct qcom_swrm_ctrl *ctrl, int reg)
>> +{
>> +    return readl_relaxed(ctrl->base + reg);
>> +}
>> +
>> +static int qcom_swrm_mmio_reg_write(struct qcom_swrm_ctrl *ctrl,
>> +                    int reg, int val)
>> +{
>> +    writel_relaxed(val, ctrl->base + reg);
>> +
>> +    return 0;
>> +}
>> +
>> +static int qcom_swrm_slim_reg_write(struct qcom_swrm_ctrl *ctrl,
>> +                    int reg, int val)
>> +{
>> +    struct regmap *wcd_regmap = ctrl->regmap;
>> +
>> +    /* pg register + offset */
>> +    regmap_bulk_write(wcd_regmap, SWRM_AHB_BRIDGE_WR_DATA_0,
>> +              (u8 *)&val, 4);
>> +    /* write address register */
>> +    regmap_bulk_write(wcd_regmap, SWRM_AHB_BRIDGE_WR_ADDR_0,
>> +              (u8 *)&reg, 4);
>> +
>> +    return 0;
>> +}
> 
> Ok, so you choose to declare write op as returning "int" yet either it 
> cannot do so (void writel_relaxed) or ret is completely ignored 
> (regmap_bulk_write does return an int value). Either switch to void or 
> check against returned value whenever possible.
> 

Its right to check the return values, I will change it this accordingly 
and fix all such occurances.

>> +
>> +static int qcom_swrm_cmd_fifo_wr_cmd(struct qcom_swrm_ctrl *ctrl, u8 
>> cmd_data,
>> +                     u8 dev_addr, u16 reg_addr)
>> +{
>> +    int ret = 0;
>> +    u8 cmd_id;
>> +    u32 val;
>> +
>> +    mutex_lock(&ctrl->lock);
>> +    if (dev_addr == SDW_BROADCAST_DEV_NUM) {
>> +        cmd_id = SWRM_SPECIAL_CMD_ID;
>> +    } else {
>> +        if (++ctrl->wr_cmd_id == SWRM_SPECIAL_CMD_ID)
>> +            ctrl->wr_cmd_id = 0;
>> +
>> +        cmd_id = ctrl->wr_cmd_id;
>> +    }
>> +
>> +    val = SWRM_REG_VAL_PACK(cmd_data, dev_addr, cmd_id, reg_addr);
>> +    ret = ctrl->reg_write(ctrl, SWRM_CMD_FIFO_WR_CMD, val);
>> +    if (ret < 0) {
>> +        dev_err(ctrl->dev, "%s: reg 0x%x write failed, err:%d\n",
>> +            __func__, val, ret);
>> +        goto err;
>> +    }
>> +
>> +    if (dev_addr == SDW_BROADCAST_DEV_NUM) {
>> +        ctrl->fifo_status = 0;
>> +        ret = wait_for_completion_timeout(&ctrl->sp_cmd_comp,
>> +                          msecs_to_jiffies(TIMEOUT_MS));
>> +
>> +        if (!ret || ctrl->fifo_status) {
>> +            dev_err(ctrl->dev, "reg 0x%x write failed\n", val);
> 
> Both, this and err msg above are generic enough to be put into goto to 
> save some space.

I agree!

> 
>> +            ret = -ENODATA;
>> +            goto err;
>> +        }
>> +    }
>> +err:
>> +    mutex_unlock(&ctrl->lock);
>> +    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 = 0;
>> +    u8 cmd_id = 0;
>> +    u32 val;
>> +
>> +    mutex_lock(&ctrl->lock);
>> +    if (dev_addr == SDW_ENUM_DEV_NUM) {
>> +        cmd_id = SWRM_SPECIAL_CMD_ID;
>> +    } else {
>> +        if (++ctrl->rd_cmd_id == SWRM_SPECIAL_CMD_ID)
>> +            ctrl->rd_cmd_id = 0;
>> +
>> +        cmd_id = ctrl->rd_cmd_id;
>> +    }
>> +
>> +    val = SWRM_REG_VAL_PACK(len, dev_addr, cmd_id, reg_addr);
>> +    ret = ctrl->reg_write(ctrl, SWRM_CMD_FIFO_RD_CMD, val);
>> +    if (ret < 0) {
>> +        dev_err(ctrl->dev, "reg 0x%x write failed err:%d\n", val, ret);
> 
> Same for _rt_.
> 
>> +        goto err;
>> +    }
>> +
>> +    if (dev_addr == SDW_ENUM_DEV_NUM) {
>> +        ctrl->fifo_status = 0;
>> +        ret = wait_for_completion_timeout(&ctrl->sp_cmd_comp,
>> +                          msecs_to_jiffies(TIMEOUT_MS));
>> +
>> +        if (!ret || ctrl->fifo_status) {
>> +            dev_err(ctrl->dev, "reg 0x%x read failed\n", val);
> 
> Just to be sure. It is really "read" that is failing here?
>
The final result is that read has not been sucessfull with a complete 
interrupt. So yes read is failing here.


>> +            ret = -ENODATA;
>> +            goto err;
>> +        }
>> +    }
>> +
>> +    for (i = 0; i < len; i++) {
>> +        rval[i] = ctrl->reg_read(ctrl, SWRM_CMD_FIFO_RD_FIFO_ADDR);
>> +        rval[i] &= 0xFF;
>> +    }
>> +
>> +err:
>> +    mutex_unlock(&ctrl->lock);
>> +    return ret;
>> +}
>> +
>> +static void qcom_swrm_get_device_status(struct qcom_swrm_ctrl *ctrl)
>> +{
>> +    u32 val = ctrl->reg_read(ctrl, SWRM_MCP_SLV_STATUS);
>> +    int i;
>> +
>> +    for (i = 1; 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;
>> +
>> +    sts = ctrl->reg_read(ctrl, SWRM_INTERRUPT_STATUS);
>> +
>> +    if (sts & SWRM_INTERRUPT_STATUS_SPECIAL_CMD_ID_FINISHED)
>> +        complete(&ctrl->sp_cmd_comp);
>> +
>> +    if (sts & SWRM_INTERRUPT_STATUS_CMD_ERROR) {
>> +        value = ctrl->reg_read(ctrl, SWRM_CMD_FIFO_STATUS);
>> +        dev_err_ratelimited(ctrl->dev,
>> +                    "CMD error, fifo status 0x%x\n",
>> +                     value);
>> +        ctrl->reg_write(ctrl, SWRM_CMD_FIFO_CMD, 0x1);
>> +        if ((value & 0xF) == 0xF) {
>> +            ctrl->fifo_status = -ENODATA;
>> +            complete(&ctrl->sp_cmd_comp);
>> +        }
>> +    }
>> +
>> +    if ((sts & SWRM_INTERRUPT_STATUS_NEW_SLAVE_ATTACHED) ||
>> +        sts & SWRM_INTERRUPT_STATUS_CHANGE_ENUM_SLAVE_STATUS) {
>> +        if (sts & SWRM_INTERRUPT_STATUS_NEW_SLAVE_ATTACHED)
>> +            ctrl->status[0] = SDW_SLAVE_ATTACHED;
>> +
>> +        schedule_work(&ctrl->slave_work);
>> +    }
>> +
>> +    if (sts & SWRM_INTERRUPT_STATUS_SLAVE_PEND_IRQ)
>> +        dev_dbg(ctrl->dev, "Slave pend irq\n");
>> +
>> +    if (sts & SWRM_INTERRUPT_STATUS_NEW_SLAVE_ATTACHED)
>> +        dev_dbg(ctrl->dev, "New slave attached\n");
>> +
>> +    if (sts & SWRM_INTERRUPT_STATUS_MASTER_CLASH_DET)
>> +        dev_err_ratelimited(ctrl->dev, "Bus clash detected\n");
>> +
>> +    if (sts & SWRM_INTERRUPT_STATUS_RD_FIFO_OVERFLOW)
>> +        dev_err(ctrl->dev, "Read FIFO overflow\n");
>> +
>> +    if (sts & SWRM_INTERRUPT_STATUS_RD_FIFO_UNDERFLOW)
>> +        dev_err(ctrl->dev, "Read FIFO underflow\n");
>> +
>> +    if (sts & SWRM_INTERRUPT_STATUS_WR_CMD_FIFO_OVERFLOW)
>> +        dev_err(ctrl->dev, "Write FIFO overflow\n");
>> +
>> +    if (sts & SWRM_INTERRUPT_STATUS_DOUT_PORT_COLLISION)
>> +        dev_err(ctrl->dev, "Port collision detected\n");
>> +
>> +    if (sts & SWRM_INTERRUPT_STATUS_READ_EN_RD_VALID_MISMATCH)
>> +        dev_err(ctrl->dev, "Read enable valid mismatch\n");
>> +
>> +    if (sts & SWRM_INTERRUPT_STATUS_SPECIAL_CMD_ID_FINISHED)
>> +        dev_err(ctrl->dev, "Cmd id finished\n");
>> +
>> +    if (sts & SWRM_INTERRUPT_STATUS_BUS_RESET_FINISHED)
>> +        dev_err(ctrl->dev, "Bus reset finished\n");
> 
> If you do not handle these errors at all, consider declaring 
> ERROR-message table. I believe leaving erroneus status as is may lead to 
> fatal consequences. If there is no intention for handling even the most 
> critical cases, please add TODO/ comment here.

Yep, I agree will clean this up!

> 
>> +
>> +    ctrl->reg_write(ctrl, SWRM_INTERRUPT_CLEAR, sts);
>> +
>> +    return IRQ_HANDLED;
>> +}
>> +
>> +static int qcom_swrm_init(struct qcom_swrm_ctrl *ctrl)
>> +{
>> +    u32 val;
>> +    u8 row_ctrl = SWRM_MAX_ROW_VAL;
>> +    u8 col_ctrl = SWRM_MIN_COL_VAL;
>> +    u8 ssp_period = 1;
>> +    u8 retry_cmd_num = 3;
>> +
>> +    /* Clear Rows and Cols */
>> +    val = ((row_ctrl << SWRM_MCP_FRAME_CTRL_BANK_ROW_CTRL_SHFT) |
>> +        (col_ctrl << SWRM_MCP_FRAME_CTRL_BANK_COL_CTRL_SHFT) |
>> +        (ssp_period << SWRM_MCP_FRAME_CTRL_BANK_SSP_PERIOD_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 */
>> +    val = ctrl->reg_read(ctrl, SWRM_MCP_CFG_ADDR);
>> +
>> +    val &= ~SWRM_MCP_CFG_MAX_NUM_OF_CMD_NO_PINGS_BMSK;
>> +    val |= (0x1f << 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 */
>> +    val = (retry_cmd_num << SWRM_CMD_FIFO_CFG_NUM_OF_CMD_RETRY_SHFT);
>> +    ctrl->reg_write(ctrl, SWRM_CMD_FIFO_CFG_ADDR, val);
>> +
>> +    /* 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;
> 
> As in my previous comment, you should check against ret from reg_write 
> if void approach is not chosen.

I agree!

> 
>> +}
>> +
>> +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 < 0) {
>> +                if (ret == -ENODATA)
>> +                    return SDW_CMD_IGNORED;
>> +
>> +                return ret;
>> +            }
>> +
>> +            i = i + len;
> 
> Any reason for inlining this incrementation? If _rd_ fails, we leave the 
> loop anyway.
> 
>> +        }
>> +    } 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 < 0) {
>> +                if (ret == -ENODATA)
>> +                    return SDW_CMD_IGNORED;
>> +
>> +                return ret;
>> +            }
>> +        }
>> +    }
>> +
>> +    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;
>> +
>> +    val = ctrl->reg_read(ctrl, reg);
>> +    val |= ((0 << SWRM_MCP_FRAME_CTRL_BANK_ROW_CTRL_SHFT) |
>> +        (7l << SWRM_MCP_FRAME_CTRL_BANK_COL_CTRL_SHFT));
>> +    ctrl->reg_write(ctrl, reg, val);
>> +
>> +    return 0;
> 
> s/return 0/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;
>> +
>> +    ctrl->reg_write(ctrl, SWRM_DP_PORT_CTRL_BANK((params->port_num), 
>> bank),
>> +            value);
>> +
>> +    return 0;
> 
> Another "return issue" here.
> 
>> +}
>> +
>> +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;
>> +
>> +    val = ctrl->reg_read(ctrl, reg);
>> +    if (enable_ch->enable)
>> +        val |= (enable_ch->ch_mask << SWRM_DP_PORT_CTRL_EN_CHAN_SHFT);
>> +    else
>> +        val &= ~(enable_ch->ch_mask << SWRM_DP_PORT_CTRL_EN_CHAN_SHFT);
>> +
>> +    ctrl->reg_write(ctrl, reg, val);
>> +
>> +    return 0;
>> +}
>> +
>> +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;
>> +    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) {
>> +            p_rt->transport_params.port_num = p_rt->num;
>> +            p_rt->transport_params.sample_interval =
>> +                    ctrl->pconfig[p_rt->num - 1].si + 1;
>> +            p_rt->transport_params.offset1 =
>> +                    ctrl->pconfig[p_rt->num - 1].off1;
>> +            p_rt->transport_params.offset2 =
>> +                    ctrl->pconfig[p_rt->num - 1].off2;
> 
> ctrl->pconfig[ <idx> ] colleagues bellow make use of local index 
> variable which clearly makes it more readable.
> 
>> +        }
>> +
>> +        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) {
>> +                p_rt->transport_params.port_num = p_rt->num;
>> +                p_rt->transport_params.sample_interval =
>> +                        ctrl->pconfig[i].si + 1;
>> +                p_rt->transport_params.offset1 =
>> +                        ctrl->pconfig[i].off1;
>> +                p_rt->transport_params.offset2 =
>> +                        ctrl->pconfig[i].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 int qcom_swrm_prepare(struct snd_pcm_substream *substream,
>> +                 struct snd_soc_dai *dai)
>> +{
>> +    struct qcom_swrm_ctrl *ctrl = dev_get_drvdata(dai->dev);
>> +
>> +    if (!ctrl->sruntime[dai->id])
>> +        return -EINVAL;
>> +
>> +    return sdw_enable_stream(ctrl->sruntime[dai->id]);
>> +}
>> +
>> +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);
>> +        }
> 
> Unnecessary brackets.
> 
>> +    }
>> +
>> +    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[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;
>> +
>> +    sconfig.ch_count = 1;
>> +    sconfig.frame_rate = params_rate(params);
>> +    sconfig.type = stream->type;
>> +    sconfig.bps = 1;
> 
> Hmm. frame_rate and type gets assigned based on "input" data yet the 
> rest is hardcoded. Is this intended?

For now I only managed to test PDM on this controller, and I agree these 
need to be more generic..
>> +    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);
>> +    int ret;
>> +
>> +    ret = qcom_swrm_stream_alloc_ports(ctrl, ctrl->sruntime[dai->id],
>> +                       params, substream->stream);
>> +    if (ret)
>> +        return ret;
>> +
>> +    return sdw_prepare_stream(ctrl->sruntime[dai->id]);
>> +}
>> +
>> +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);
>> +
>> +    qcom_swrm_stream_free_ports(ctrl, ctrl->sruntime[dai->id]);
>> +    sdw_stream_remove_master(&ctrl->bus, ctrl->sruntime[dai->id]);
>> +    sdw_deprepare_stream(ctrl->sruntime[dai->id]);
>> +    sdw_disable_stream(ctrl->sruntime[dai->id]);
> 
> Declaring local variable initialized with ctrl->sruntime[dai->id] should 
> prove more readable.
> 
I agree!

>> +
>> +    return 0;
>> +}
>> +
>> +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;
>> +    int i;
>> +
>> +    /* PDM dais are only tested for now */
>> +    dais = devm_kcalloc(ctrl->dev, num_dais, sizeof(*dais), GFP_KERNEL);
>> +    if (!dais)
>> +        return -ENOMEM;
>> +
>> +    for (i = 0; i < num_dais; i++) {
>> +        dais[i].name = kasprintf(GFP_KERNEL, "SDW Pin%d", i);
>> +        if (i < ctrl->num_dout_ports) {
>> +            dais[i].playback.stream_name = kasprintf(GFP_KERNEL,
>> +                                 "SDW Tx%d", i);
>> +            if (!dais[i].playback.stream_name) {
>> +                kfree(dais[i].name);
>> +                return -ENOMEM;
> 
> Now this got me worried. What about memory allocated in iterations 
> before the failure? It must be freed in error handling path. goto should 
> be of help here.
> 
I agree.

>> +            }
>> +            dais[i].playback.channels_min = 1;
>> +            dais[i].playback.channels_max = 1;
>> +            dais[i].playback.rates = SNDRV_PCM_RATE_48000;
>> +            dais[i].playback.formats = SNDRV_PCM_FMTBIT_S16_LE;
> 
> All of these formats are hardcoded. Consider declaring a "template" 
> format above and simply initialize each dai with it.

Okay!

> 
>> +        } else {
>> +            dais[i].capture.stream_name = kasprintf(GFP_KERNEL,
>> +                                "SDW Rx%d", i);
>> +            if (!dais[i].capture.stream_name) {
>> +                kfree(dais[i].name);
>> +                kfree(dais[i].playback.stream_name);
>> +                return -ENOMEM;
> 
> Same memory deallocation issue here.
I see that, I will take care of this in next version.
> 
>> +            }
>> +
>> +            dais[i].capture.channels_min = 1;
>> +            dais[i].capture.channels_max = 1;
>> +            dais[i].capture.rates = SNDRV_PCM_RATE_48000;
>> +            dais[i].capture.formats = SNDRV_PCM_FMTBIT_S16_LE;
> 
> Comment regarding playback dai initialization applies here too.
> 
>> +        }
>> +        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[SDW_MAX_PORTS];
>> +    u8 off2[SDW_MAX_PORTS];
>> +    u8 si[SDW_MAX_PORTS];
> 
> Array of struct qcom_swrm_port_config instead of this trio?
> 
Makes sense! Will do that in next version.
>> +    int i, ret, nports, val;
>> +
>> +    val = ctrl->reg_read(ctrl, SWRM_COMP_PARAMS);
>> +    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];
>> +    }
> 
> Using array I spoke of earlier leads to brackets being redundant here.
> 
>> +
>> +    return 0;
>> +}
>> +


More information about the Alsa-devel mailing list