ASoC: cs35l56: Handle OTP read latency over SoundWire
authorRichard Fitzgerald <rf@opensource.cirrus.com>
Mon, 5 Aug 2024 14:08:39 +0000 (15:08 +0100)
committerMark Brown <broonie@kernel.org>
Mon, 5 Aug 2024 17:26:29 +0000 (18:26 +0100)
Use the late-read buffer in the CS35L56 SoundWire interface to
read OTP memory.

The OTP memory has a longer access latency than chip registers
and cannot guarantee to return the data value in the SoundWire
control response if the bus clock is >4.8 MHz. The Cirrus
SoundWire peripheral IP exposes the bridge-to-bus read buffer
and status bits. For a read from OTP the bridge status bits are
polled to wait for the OTP data to be loaded into the read buffer
and the data is then read from there.

Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com>
Fixes: e1830f66f6c6 ("ASoC: cs35l56: Add helper functions for amp calibration")
Link: https://patch.msgid.link/20240805140839.26042-1-rf@opensource.cirrus.com
Signed-off-by: Mark Brown <broonie@kernel.org>
include/sound/cs35l56.h
sound/soc/codecs/cs35l56-sdw.c

index a6aa112e57416ce04b4e9ada51b39b52a2e8de68..a51acefa785f72980f46c2deda5b76dbb4751fab 100644 (file)
@@ -277,6 +277,11 @@ static inline int cs35l56_force_sync_asp1_registers_from_cache(struct cs35l56_ba
        return 0;
 }
 
+static inline bool cs35l56_is_otp_register(unsigned int reg)
+{
+       return (reg >> 16) == 3;
+}
+
 extern struct regmap_config cs35l56_regmap_i2c;
 extern struct regmap_config cs35l56_regmap_spi;
 extern struct regmap_config cs35l56_regmap_sdw;
index fc03bb7ecae136b99fccc2efb332f05c4c1124fd..7c9a17fe2195ccc6011f4bc5ee09f594860b456e 100644 (file)
 /* Register addresses are offset when sent over SoundWire */
 #define CS35L56_SDW_ADDR_OFFSET                0x8000
 
+/* Cirrus bus bridge registers */
+#define CS35L56_SDW_MEM_ACCESS_STATUS  0xd0
+#define CS35L56_SDW_MEM_READ_DATA      0xd8
+
+#define CS35L56_SDW_LAST_LATE          BIT(3)
+#define CS35L56_SDW_CMD_IN_PROGRESS    BIT(2)
+#define CS35L56_SDW_RDATA_RDY          BIT(0)
+
+#define CS35L56_LATE_READ_POLL_US      10
+#define CS35L56_LATE_READ_TIMEOUT_US   1000
+
+static int cs35l56_sdw_poll_mem_status(struct sdw_slave *peripheral,
+                                      unsigned int mask,
+                                      unsigned int match)
+{
+       int ret, val;
+
+       ret = read_poll_timeout(sdw_read_no_pm, val,
+                               (val < 0) || ((val & mask) == match),
+                               CS35L56_LATE_READ_POLL_US, CS35L56_LATE_READ_TIMEOUT_US,
+                               false, peripheral, CS35L56_SDW_MEM_ACCESS_STATUS);
+       if (ret < 0)
+               return ret;
+
+       if (val < 0)
+               return val;
+
+       return 0;
+}
+
+static int cs35l56_sdw_slow_read(struct sdw_slave *peripheral, unsigned int reg,
+                                u8 *buf, size_t val_size)
+{
+       int ret, i;
+
+       reg += CS35L56_SDW_ADDR_OFFSET;
+
+       for (i = 0; i < val_size; i += sizeof(u32)) {
+               /* Poll for bus bridge idle */
+               ret = cs35l56_sdw_poll_mem_status(peripheral,
+                                                 CS35L56_SDW_CMD_IN_PROGRESS,
+                                                 0);
+               if (ret < 0) {
+                       dev_err(&peripheral->dev, "!CMD_IN_PROGRESS fail: %d\n", ret);
+                       return ret;
+               }
+
+               /* Reading LSByte triggers read of register to holding buffer */
+               sdw_read_no_pm(peripheral, reg + i);
+
+               /* Wait for data available */
+               ret = cs35l56_sdw_poll_mem_status(peripheral,
+                                                 CS35L56_SDW_RDATA_RDY,
+                                                 CS35L56_SDW_RDATA_RDY);
+               if (ret < 0) {
+                       dev_err(&peripheral->dev, "RDATA_RDY fail: %d\n", ret);
+                       return ret;
+               }
+
+               /* Read data from buffer */
+               ret = sdw_nread_no_pm(peripheral, CS35L56_SDW_MEM_READ_DATA,
+                                     sizeof(u32), &buf[i]);
+               if (ret) {
+                       dev_err(&peripheral->dev, "Late read @%#x failed: %d\n", reg + i, ret);
+                       return ret;
+               }
+
+               swab32s((u32 *)&buf[i]);
+       }
+
+       return 0;
+}
+
 static int cs35l56_sdw_read_one(struct sdw_slave *peripheral, unsigned int reg, void *buf)
 {
        int ret;
@@ -48,6 +121,10 @@ static int cs35l56_sdw_read(void *context, const void *reg_buf,
        int ret;
 
        reg = le32_to_cpu(*(const __le32 *)reg_buf);
+
+       if (cs35l56_is_otp_register(reg))
+               return cs35l56_sdw_slow_read(peripheral, reg, buf8, val_size);
+
        reg += CS35L56_SDW_ADDR_OFFSET;
 
        if (val_size == 4)