diff --git a/include/sound/cs35l56.h b/include/sound/cs35l56.h index 5928af539c46..ae1e1489b671 100644 --- a/include/sound/cs35l56.h +++ b/include/sound/cs35l56.h @@ -9,6 +9,7 @@ #ifndef __CS35L56_H #define __CS35L56_H +#include #include #include #include @@ -26,6 +27,9 @@ struct snd_ctl_elem_value; #define CS35L56_GLOBAL_ENABLES 0x0002014 #define CS35L56_BLOCK_ENABLES 0x0002018 #define CS35L56_BLOCK_ENABLES2 0x000201C +#define CS35L56_SYNC_GPIO1_CFG 0x0002410 +#define CS35L56_ASP2_DIO_GPIO13_CFG 0x0002440 +#define CS35L56_UPDATE_REGS 0x0002A0C #define CS35L56_REFCLK_INPUT 0x0002C04 #define CS35L56_GLOBAL_SAMPLE_RATE 0x0002C0C #define CS35L56_OTP_MEM_53 0x00300D4 @@ -65,6 +69,9 @@ struct snd_ctl_elem_value; #define CS35L56_IRQ1_MASK_8 0x000E0AC #define CS35L56_IRQ1_MASK_18 0x000E0D4 #define CS35L56_IRQ1_MASK_20 0x000E0DC +#define CS35L56_GPIO_STATUS1 0x000F000 +#define CS35L56_GPIO1_CTRL1 0x000F008 +#define CS35L56_GPIO13_CTRL1 0x000F038 #define CS35L56_MIXER_NGATE_CH1_CFG 0x0010004 #define CS35L56_MIXER_NGATE_CH2_CFG 0x0010008 #define CS35L56_DSP_MBOX_1_RAW 0x0011000 @@ -130,6 +137,17 @@ struct snd_ctl_elem_value; #define CS35L56_MTLREVID_MASK 0x0000000F #define CS35L56_REVID_B0 0x000000B0 +/* PAD_INTF */ +#define CS35L56_PAD_GPIO_PULL_MASK GENMASK(3, 2) +#define CS35L56_PAD_GPIO_IE BIT(0) + +#define CS35L56_PAD_PULL_NONE 0 +#define CS35L56_PAD_PULL_UP 1 +#define CS35L56_PAD_PULL_DOWN 2 + +/* UPDATE_REGS */ +#define CS35L56_UPDT_GPIO_PRES BIT(6) + /* ASP_ENABLES1 */ #define CS35L56_ASP_RX2_EN_SHIFT 17 #define CS35L56_ASP_RX1_EN_SHIFT 16 @@ -185,6 +203,12 @@ struct snd_ctl_elem_value; /* MIXER_NGATE_CHn_CFG */ #define CS35L56_AUX_NGATE_CHn_EN 0x00000001 +/* GPIOn_CTRL1 */ +#define CS35L56_GPIO_DIR_MASK BIT(31) +#define CS35L56_GPIO_FN_MASK GENMASK(2, 0) + +#define CS35L56_GPIO_FN_GPIO 0x00000001 + /* Mixer input sources */ #define CS35L56_INPUT_SRC_NONE 0x00 #define CS35L56_INPUT_SRC_ASP1RX1 0x08 @@ -279,6 +303,7 @@ struct snd_ctl_elem_value; #define CS35L56_HALO_STATE_TIMEOUT_US 250000 #define CS35L56_RESET_PULSE_MIN_US 1100 #define CS35L56_WAKE_HOLD_TIME_US 1000 +#define CS35L56_PAD_PULL_SETTLE_US 10 #define CS35L56_CALIBRATION_POLL_US (100 * USEC_PER_MSEC) #define CS35L56_CALIBRATION_TIMEOUT_US (5 * USEC_PER_SEC) @@ -289,6 +314,9 @@ struct snd_ctl_elem_value; #define CS35L56_NUM_BULK_SUPPLIES 3 #define CS35L56_NUM_DSP_REGIONS 5 +#define CS35L56_MAX_GPIO 13 +#define CS35L63_MAX_GPIO 9 + /* Additional margin for SYSTEM_RESET to control port ready on SPI */ #define CS35L56_SPI_RESET_TO_PORT_READY_US (CS35L56_CONTROL_PORT_READY_US + 2500) @@ -338,6 +366,10 @@ struct cs35l56_base { const struct cirrus_amp_cal_controls *calibration_controls; struct dentry *debugfs; u64 silicon_uid; + u8 onchip_spkid_gpios[5]; + u8 num_onchip_spkid_gpios; + u8 onchip_spkid_pulls[5]; + u8 num_onchip_spkid_pulls; }; static inline bool cs35l56_is_otp_register(unsigned int reg) @@ -413,6 +445,11 @@ void cs35l56_warn_if_firmware_missing(struct cs35l56_base *cs35l56_base); void cs35l56_log_tuning(struct cs35l56_base *cs35l56_base, struct cs_dsp *cs_dsp); int cs35l56_hw_init(struct cs35l56_base *cs35l56_base); int cs35l56_get_speaker_id(struct cs35l56_base *cs35l56_base); +int cs35l56_check_and_save_onchip_spkid_gpios(struct cs35l56_base *cs35l56_base, + const u32 *gpios, int num_gpios, + const u32 *pulls, int num_pulls); +int cs35l56_configure_onchip_spkid_pads(struct cs35l56_base *cs35l56_base); +int cs35l56_read_onchip_spkid(struct cs35l56_base *cs35l56_base); int cs35l56_get_bclk_freq_id(unsigned int freq); void cs35l56_fill_supply_names(struct regulator_bulk_data *data); diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index e78ac302da15..adb3fb923be3 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -936,6 +936,20 @@ config SND_SOC_CS35L56_TEST help This builds KUnit tests for the Cirrus Logic cs35l56 codec driver. + + For more information on KUnit and unit tests in general, + please refer to the KUnit documentation in + Documentation/dev-tools/kunit/. + If in doubt, say "N". + +config SND_SOC_CS35L56_SHARED_TEST + tristate "KUnit test for Cirrus Logic cs35l56-shared" if !KUNIT_ALL_TESTS + depends on SND_SOC_CS35L56_SHARED && KUNIT + default KUNIT_ALL_TESTS + help + This builds KUnit tests for the Cirrus Logic cs35l56-shared + module. + For more information on KUnit and unit tests in general, please refer to the KUnit documentation in Documentation/dev-tools/kunit/. diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index c9e3b813653d..3ddee5298721 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -77,6 +77,7 @@ snd-soc-cs35l45-spi-y := cs35l45-spi.o snd-soc-cs35l45-i2c-y := cs35l45-i2c.o snd-soc-cs35l56-y := cs35l56.o snd-soc-cs35l56-shared-y := cs35l56-shared.o +snd-soc-cs35l56-shared-test-y := cs35l56-shared-test.o snd-soc-cs35l56-i2c-y := cs35l56-i2c.o snd-soc-cs35l56-spi-y := cs35l56-spi.o snd-soc-cs35l56-sdw-y := cs35l56-sdw.o @@ -512,6 +513,7 @@ obj-$(CONFIG_SND_SOC_CS35L45_SPI) += snd-soc-cs35l45-spi.o obj-$(CONFIG_SND_SOC_CS35L45_I2C) += snd-soc-cs35l45-i2c.o obj-$(CONFIG_SND_SOC_CS35L56) += snd-soc-cs35l56.o obj-$(CONFIG_SND_SOC_CS35L56_SHARED) += snd-soc-cs35l56-shared.o +obj-$(CONFIG_SND_SOC_CS35L56_SHARED_TEST) += snd-soc-cs35l56-shared-test.o obj-$(CONFIG_SND_SOC_CS35L56_I2C) += snd-soc-cs35l56-i2c.o obj-$(CONFIG_SND_SOC_CS35L56_SPI) += snd-soc-cs35l56-spi.o obj-$(CONFIG_SND_SOC_CS35L56_SDW) += snd-soc-cs35l56-sdw.o diff --git a/sound/soc/codecs/cs35l56-shared-test.c b/sound/soc/codecs/cs35l56-shared-test.c new file mode 100644 index 000000000000..94db02aef7dc --- /dev/null +++ b/sound/soc/codecs/cs35l56-shared-test.c @@ -0,0 +1,680 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// KUnit test for the Cirrus Logic cs35l56-shared module. +// +// Copyright (C) 2026 Cirrus Logic, Inc. and +// Cirrus Logic International Semiconductor Ltd. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct cs35l56_shared_test_priv { + struct kunit *test; + struct faux_device *amp_dev; + struct regmap *registers; + struct cs35l56_base *cs35l56_base; + u8 applied_pad_pull_state[CS35L56_MAX_GPIO]; +}; + +struct cs35l56_shared_test_param { + int spkid_gpios[4]; + int spkid_pulls[4]; + unsigned long gpio_status; + int spkid; +}; + +KUNIT_DEFINE_ACTION_WRAPPER(faux_device_destroy_wrapper, faux_device_destroy, + struct faux_device *) + +KUNIT_DEFINE_ACTION_WRAPPER(regmap_exit_wrapper, regmap_exit, struct regmap *) + +static const struct regmap_config cs35l56_shared_test_mock_registers_regmap = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = CS35L56_DSP1_PMEM_5114, + .cache_type = REGCACHE_MAPLE, +}; + +static const struct regmap_bus cs35l56_shared_test_mock_registers_regmap_bus = { + /* No handlers because it is always in cache-only */ +}; + +static unsigned int cs35l56_shared_test_read_gpio_status(struct cs35l56_shared_test_priv *priv) +{ + const struct cs35l56_shared_test_param *param = priv->test->param_value; + unsigned int reg_offs, pad_cfg, val; + unsigned int status = 0; + unsigned int mask = 1; + + for (reg_offs = 0; reg_offs < CS35L56_MAX_GPIO * sizeof(u32); reg_offs += sizeof(u32)) { + regmap_read(priv->registers, CS35L56_SYNC_GPIO1_CFG + reg_offs, &pad_cfg); + regmap_read(priv->registers, CS35L56_GPIO1_CTRL1 + reg_offs, &val); + + /* Only read a value if set as an input pin and as a GPIO */ + val &= (CS35L56_GPIO_DIR_MASK | CS35L56_GPIO_FN_MASK); + if ((pad_cfg & CS35L56_PAD_GPIO_IE) && + (val == (CS35L56_GPIO_DIR_MASK | CS35L56_GPIO_FN_GPIO))) + status |= (param->gpio_status & mask); + + mask <<= 1; + } + + return status; +} + +static int cs35l56_shared_test_updt_gpio_pres(struct cs35l56_shared_test_priv *priv, + unsigned int reg, unsigned int val) +{ + int i, ret; + + ret = regmap_write(priv->registers, reg, val); + if (ret) + return ret; + + if (val & CS35L56_UPDT_GPIO_PRES) { + /* Simulate transferring register state to internal latches */ + for (i = 0; i < ARRAY_SIZE(priv->applied_pad_pull_state); i++) { + reg = CS35L56_SYNC_GPIO1_CFG + (i * sizeof(u32)); + regmap_read(priv->registers, reg, &val); + val = FIELD_GET(CS35L56_PAD_GPIO_PULL_MASK, val); + priv->applied_pad_pull_state[i] = val; + } + } + + return 0; +} + +static int cs35l56_shared_test_reg_read(void *context, unsigned int reg, unsigned int *val) +{ + struct cs35l56_shared_test_priv *priv = context; + + switch (reg) { + case CS35L56_SYNC_GPIO1_CFG ... CS35L56_ASP2_DIO_GPIO13_CFG: + case CS35L56_GPIO1_CTRL1 ... CS35L56_GPIO13_CTRL1: + return regmap_read(priv->registers, reg, val); + case CS35L56_UPDATE_REGS: + *val = 0; + return 0; + case CS35L56_GPIO_STATUS1: + *val = cs35l56_shared_test_read_gpio_status(priv); + return 0; + default: + kunit_fail_current_test("Bad regmap read address %#x\n", reg); + return -EINVAL; + } +} + +static int cs35l56_shared_test_reg_write(void *context, unsigned int reg, unsigned int val) +{ + struct cs35l56_shared_test_priv *priv = context; + + switch (reg) { + case CS35L56_UPDATE_REGS: + return cs35l56_shared_test_updt_gpio_pres(priv, reg, val); + case CS35L56_SYNC_GPIO1_CFG ... CS35L56_ASP2_DIO_GPIO13_CFG: + case CS35L56_GPIO1_CTRL1 ... CS35L56_GPIO13_CTRL1: + return regmap_write(priv->registers, reg, val); + default: + kunit_fail_current_test("Bad regmap write address %#x\n", reg); + return -EINVAL; + } +} + +static const struct regmap_bus cs35l56_shared_test_regmap_bus = { + .reg_read = cs35l56_shared_test_reg_read, + .reg_write = cs35l56_shared_test_reg_write, + .reg_format_endian_default = REGMAP_ENDIAN_LITTLE, + .val_format_endian_default = REGMAP_ENDIAN_LITTLE, +}; + +/* + * Self-test that the mock GPIO registers obey the configuration bits. + * Other tests rely on the mocked registers only returning a GPIO state + * if the pin is correctly set as a GPIO input. + */ +static void cs35l56_shared_test_mock_gpio_status_selftest(struct kunit *test) +{ + const struct cs35l56_shared_test_param *param = test->param_value; + struct cs35l56_shared_test_priv *priv = test->priv; + struct cs35l56_base *cs35l56_base = priv->cs35l56_base; + unsigned int reg, val; + + KUNIT_ASSERT_NOT_NULL(test, param); + + /* Set all pins non-GPIO and output. Mock GPIO_STATUS should read 0 */ + for (reg = CS35L56_GPIO1_CTRL1; reg <= CS35L56_GPIO13_CTRL1; reg += sizeof(u32)) + KUNIT_ASSERT_EQ(test, 0, regmap_write(priv->registers, reg, 0)); + + /* Set all pads as inputs */ + for (reg = CS35L56_SYNC_GPIO1_CFG; reg <= CS35L56_ASP2_DIO_GPIO13_CFG; reg += sizeof(u32)) + KUNIT_ASSERT_EQ(test, 0, regmap_write(priv->registers, reg, CS35L56_PAD_GPIO_IE)); + + KUNIT_ASSERT_EQ(test, 0, regmap_read(cs35l56_base->regmap, CS35L56_GPIO_STATUS1, &val)); + KUNIT_EXPECT_EQ(test, val, 0); + + /* Set all pins as GPIO outputs. Mock GPIO_STATUS should read 0 */ + for (reg = CS35L56_GPIO1_CTRL1; reg <= CS35L56_GPIO13_CTRL1; reg += sizeof(u32)) + KUNIT_ASSERT_EQ(test, 0, regmap_write(priv->registers, reg, CS35L56_GPIO_FN_GPIO)); + + KUNIT_ASSERT_EQ(test, 0, regmap_read(cs35l56_base->regmap, CS35L56_GPIO_STATUS1, &val)); + KUNIT_EXPECT_EQ(test, val, 0); + + /* Set all pins as non-GPIO inputs. Mock GPIO_STATUS should read 0 */ + for (reg = CS35L56_GPIO1_CTRL1; reg <= CS35L56_GPIO13_CTRL1; reg += sizeof(u32)) + KUNIT_ASSERT_EQ(test, 0, regmap_write(priv->registers, reg, CS35L56_GPIO_DIR_MASK)); + + KUNIT_ASSERT_EQ(test, 0, regmap_read(cs35l56_base->regmap, CS35L56_GPIO_STATUS1, &val)); + KUNIT_EXPECT_EQ(test, val, 0); + + /* Set all pins as GPIO inputs. Mock GPIO_STATUS should match param->gpio_status */ + for (reg = CS35L56_GPIO1_CTRL1; reg <= CS35L56_GPIO13_CTRL1; reg += sizeof(u32)) + KUNIT_ASSERT_EQ(test, 0, + regmap_write(priv->registers, reg, + CS35L56_GPIO_DIR_MASK | CS35L56_GPIO_FN_GPIO)); + + KUNIT_ASSERT_EQ(test, 0, regmap_read(cs35l56_base->regmap, CS35L56_GPIO_STATUS1, &val)); + KUNIT_EXPECT_EQ(test, val, param->gpio_status); + + /* Set all pads as outputs. Mock GPIO_STATUS should read 0 */ + for (reg = CS35L56_SYNC_GPIO1_CFG; reg <= CS35L56_ASP2_DIO_GPIO13_CFG; reg += sizeof(u32)) + KUNIT_ASSERT_EQ(test, 0, regmap_write(priv->registers, reg, 0)); + + KUNIT_ASSERT_EQ(test, 0, regmap_read(cs35l56_base->regmap, CS35L56_GPIO_STATUS1, &val)); + KUNIT_EXPECT_EQ(test, val, 0); +} + +/* Test that the listed chip pins are assembled into a speaker ID integer. */ +static void cs35l56_shared_test_get_onchip_speaker_id(struct kunit *test) +{ + const struct cs35l56_shared_test_param *param = test->param_value; + struct cs35l56_shared_test_priv *priv = test->priv; + struct cs35l56_base *cs35l56_base = priv->cs35l56_base; + unsigned int i, reg; + + /* Set all pins non-GPIO and output */ + for (reg = CS35L56_GPIO1_CTRL1; reg <= CS35L56_GPIO13_CTRL1; reg += sizeof(u32)) + KUNIT_ASSERT_EQ(test, 0, regmap_write(priv->registers, reg, 0)); + + for (reg = CS35L56_SYNC_GPIO1_CFG; reg <= CS35L56_ASP2_DIO_GPIO13_CFG; reg += sizeof(u32)) + KUNIT_ASSERT_EQ(test, 0, regmap_write(priv->registers, reg, 0)); + + /* Init GPIO array */ + for (i = 0; i < ARRAY_SIZE(param->spkid_gpios); i++) { + if (param->spkid_gpios[i] < 0) + break; + + cs35l56_base->onchip_spkid_gpios[i] = param->spkid_gpios[i] - 1; + cs35l56_base->num_onchip_spkid_gpios++; + } + + cs35l56_base->num_onchip_spkid_pulls = 0; + + KUNIT_EXPECT_EQ(test, cs35l56_configure_onchip_spkid_pads(cs35l56_base), 0); + KUNIT_EXPECT_EQ(test, cs35l56_read_onchip_spkid(cs35l56_base), param->spkid); +} + +/* Test that the listed chip pins and the corresponding pads are configured correctly. */ +static void cs35l56_shared_test_onchip_speaker_id_pad_config(struct kunit *test) +{ + const struct cs35l56_shared_test_param *param = test->param_value; + struct cs35l56_shared_test_priv *priv = test->priv; + struct cs35l56_base *cs35l56_base = priv->cs35l56_base; + unsigned int i, reg, val; + + /* Init values in all pin registers */ + for (reg = CS35L56_GPIO1_CTRL1; reg <= CS35L56_GPIO13_CTRL1; reg += sizeof(u32)) + KUNIT_ASSERT_EQ(test, 0, regmap_write(priv->registers, reg, 0)); + + for (reg = CS35L56_SYNC_GPIO1_CFG; reg <= CS35L56_ASP2_DIO_GPIO13_CFG; reg += sizeof(u32)) + KUNIT_ASSERT_EQ(test, 0, regmap_write(priv->registers, reg, 0)); + + /* Init GPIO array */ + for (i = 0; i < ARRAY_SIZE(param->spkid_gpios); i++) { + if (param->spkid_gpios[i] < 0) + break; + + cs35l56_base->onchip_spkid_gpios[i] = param->spkid_gpios[i] - 1; + cs35l56_base->num_onchip_spkid_gpios++; + } + + /* Init pulls array */ + for (i = 0; i < ARRAY_SIZE(param->spkid_pulls); i++) { + if (param->spkid_pulls[i] < 0) + break; + + cs35l56_base->onchip_spkid_pulls[i] = param->spkid_pulls[i]; + cs35l56_base->num_onchip_spkid_pulls++; + } + + KUNIT_EXPECT_EQ(test, cs35l56_configure_onchip_spkid_pads(cs35l56_base), 0); + + for (i = 0; i < ARRAY_SIZE(param->spkid_gpios); i++) { + if (param->spkid_gpios[i] < 0) + break; + + /* Pad should be an input */ + reg = CS35L56_SYNC_GPIO1_CFG + ((param->spkid_gpios[i] - 1) * sizeof(u32)); + KUNIT_EXPECT_EQ(test, regmap_read(priv->registers, reg, &val), 0); + KUNIT_EXPECT_EQ(test, val & CS35L56_PAD_GPIO_IE, CS35L56_PAD_GPIO_IE); + + /* Specified pulls should be set, others should be none */ + if (i < cs35l56_base->num_onchip_spkid_pulls) { + KUNIT_EXPECT_EQ(test, val & CS35L56_PAD_GPIO_PULL_MASK, + FIELD_PREP(CS35L56_PAD_GPIO_PULL_MASK, + param->spkid_pulls[i])); + } else { + KUNIT_EXPECT_EQ(test, val & CS35L56_PAD_GPIO_PULL_MASK, + CS35L56_PAD_PULL_NONE); + } + + /* Pulls for all specfied GPIOs should have been transferred to AO latch */ + if (i < cs35l56_base->num_onchip_spkid_pulls) { + KUNIT_EXPECT_EQ(test, + priv->applied_pad_pull_state[param->spkid_gpios[i] - 1], + param->spkid_pulls[i]); + } else { + KUNIT_EXPECT_EQ(test, + priv->applied_pad_pull_state[param->spkid_gpios[i] - 1], + CS35L56_PAD_PULL_NONE); + } + } +} + +/* Test that the listed chip pins are stashed correctly. */ +static void cs35l56_shared_test_stash_onchip_spkid_pins(struct kunit *test) +{ + const struct cs35l56_shared_test_param *param = test->param_value; + struct cs35l56_shared_test_priv *priv = test->priv; + struct cs35l56_base *cs35l56_base = priv->cs35l56_base; + u32 gpios[5], pulls[5]; + int i, num_gpios, num_pulls; + + static_assert(ARRAY_SIZE(gpios) >= ARRAY_SIZE(param->spkid_gpios)); + static_assert(ARRAY_SIZE(pulls) >= ARRAY_SIZE(param->spkid_pulls)); + + num_gpios = 0; + for (i = 0; i < ARRAY_SIZE(param->spkid_gpios); i++) { + if (param->spkid_gpios[i] < 0) + break; + + gpios[i] = (u32)param->spkid_gpios[i]; + num_gpios++; + } + + num_pulls = 0; + for (i = 0; i < ARRAY_SIZE(param->spkid_pulls); i++) { + if (param->spkid_pulls[i] < 0) + break; + + pulls[i] = (u32)param->spkid_pulls[i]; + num_pulls++; + } + + cs35l56_base->num_onchip_spkid_gpios = 0; + cs35l56_base->num_onchip_spkid_pulls = 0; + + KUNIT_ASSERT_LE(test, num_gpios, ARRAY_SIZE(cs35l56_base->onchip_spkid_gpios)); + KUNIT_ASSERT_LE(test, num_pulls, ARRAY_SIZE(cs35l56_base->onchip_spkid_pulls)); + + KUNIT_EXPECT_EQ(test, + cs35l56_check_and_save_onchip_spkid_gpios(cs35l56_base, + gpios, num_gpios, + pulls, num_pulls), + 0); + + KUNIT_EXPECT_EQ(test, cs35l56_base->num_onchip_spkid_gpios, num_gpios); + KUNIT_EXPECT_EQ(test, cs35l56_base->num_onchip_spkid_pulls, num_pulls); + + /* GPIO numbers are adjusted from 1-based to 0-based */ + for (i = 0; i < num_gpios; i++) + KUNIT_EXPECT_EQ(test, cs35l56_base->onchip_spkid_gpios[i], gpios[i] - 1); + + for (i = 0; i < num_pulls; i++) + KUNIT_EXPECT_EQ(test, cs35l56_base->onchip_spkid_pulls[i], pulls[i]); +} + +/* Test that illegal GPIO numbers are rejected. */ +static void cs35l56_shared_test_stash_onchip_spkid_pins_reject_invalid(struct kunit *test) +{ + struct cs35l56_shared_test_priv *priv = test->priv; + struct cs35l56_base *cs35l56_base = priv->cs35l56_base; + u32 gpios[8] = { }, pulls[8] = { }; + + KUNIT_EXPECT_LE(test, + cs35l56_check_and_save_onchip_spkid_gpios(cs35l56_base, + gpios, 1, + pulls, 0), + 0); + + switch (cs35l56_base->type) { + case 0x54: + case 0x56: + case 0x57: + gpios[0] = CS35L56_MAX_GPIO + 1; + break; + case 0x63: + gpios[0] = CS35L63_MAX_GPIO + 1; + break; + default: + kunit_fail_current_test("Unsupported type:%#x\n", cs35l56_base->type); + return; + } + KUNIT_EXPECT_LE(test, + cs35l56_check_and_save_onchip_spkid_gpios(cs35l56_base, + gpios, 1, + pulls, 0), + 0); + + gpios[0] = 1; + pulls[0] = 3; + KUNIT_EXPECT_LE(test, + cs35l56_check_and_save_onchip_spkid_gpios(cs35l56_base, + gpios, 1, + pulls, 1), + 0); + + static_assert(ARRAY_SIZE(gpios) > ARRAY_SIZE(cs35l56_base->onchip_spkid_gpios)); + static_assert(ARRAY_SIZE(pulls) > ARRAY_SIZE(cs35l56_base->onchip_spkid_pulls)); + KUNIT_EXPECT_EQ(test, + cs35l56_check_and_save_onchip_spkid_gpios(cs35l56_base, + gpios, ARRAY_SIZE(gpios), + pulls, 0), + -EOVERFLOW); + KUNIT_EXPECT_EQ(test, + cs35l56_check_and_save_onchip_spkid_gpios(cs35l56_base, + gpios, 1, + pulls, ARRAY_SIZE(pulls)), + -EOVERFLOW); +} + +static void cs35l56_shared_test_onchip_speaker_id_not_defined(struct kunit *test) +{ + struct cs35l56_shared_test_priv *priv = test->priv; + struct cs35l56_base *cs35l56_base = priv->cs35l56_base; + + memset(cs35l56_base->onchip_spkid_gpios, 0, sizeof(cs35l56_base->onchip_spkid_gpios)); + memset(cs35l56_base->onchip_spkid_pulls, 0, sizeof(cs35l56_base->onchip_spkid_pulls)); + cs35l56_base->num_onchip_spkid_gpios = 0; + cs35l56_base->num_onchip_spkid_pulls = 0; + KUNIT_EXPECT_EQ(test, cs35l56_configure_onchip_spkid_pads(cs35l56_base), 0); + KUNIT_EXPECT_EQ(test, cs35l56_read_onchip_spkid(cs35l56_base), -ENOENT); +} + +static int cs35l56_shared_test_case_regmap_init(struct kunit *test, + const struct regmap_config *regmap_config) +{ + struct cs35l56_shared_test_priv *priv = test->priv; + struct cs35l56_base *cs35l56_base; + + /* + * Create a dummy regmap to simulate a register map by holding the + * values of all simulated registers in the regmap cache. + */ + priv->registers = regmap_init(&priv->amp_dev->dev, + &cs35l56_shared_test_mock_registers_regmap_bus, + priv, + &cs35l56_shared_test_mock_registers_regmap); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->registers); + KUNIT_ASSERT_EQ(test, 0, + kunit_add_action_or_reset(test, regmap_exit_wrapper, + priv->registers)); + regcache_cache_only(priv->registers, true); + + /* Create dummy regmap for cs35l56 driver */ + cs35l56_base = priv->cs35l56_base; + cs35l56_base->regmap = regmap_init(cs35l56_base->dev, + &cs35l56_shared_test_regmap_bus, + priv, + regmap_config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cs35l56_base->regmap); + KUNIT_ASSERT_EQ(test, 0, + kunit_add_action_or_reset(test, regmap_exit_wrapper, + cs35l56_base->regmap)); + + return 0; +} + +static int cs35l56_shared_test_case_base_init(struct kunit *test, u8 type, u8 rev, + const struct regmap_config *regmap_config) +{ + struct cs35l56_shared_test_priv *priv; + int ret; + + KUNIT_ASSERT_NOT_NULL(test, cs_amp_test_hooks); + + priv = kunit_kzalloc(test, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + test->priv = priv; + priv->test = test; + + /* Create dummy amp driver dev */ + priv->amp_dev = faux_device_create("cs35l56_shared_test_drv", NULL, NULL); + KUNIT_ASSERT_NOT_NULL(test, priv->amp_dev); + KUNIT_ASSERT_EQ(test, 0, + kunit_add_action_or_reset(test, + faux_device_destroy_wrapper, + priv->amp_dev)); + + priv->cs35l56_base = kunit_kzalloc(test, sizeof(*priv->cs35l56_base), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, priv->cs35l56_base); + priv->cs35l56_base->dev = &priv->amp_dev->dev; + priv->cs35l56_base->type = type; + priv->cs35l56_base->rev = rev; + + if (regmap_config) { + ret = cs35l56_shared_test_case_regmap_init(test, regmap_config); + if (ret) + return ret; + } + + return 0; +} + +static int cs35l56_shared_test_case_regmap_init_L56_B0_sdw(struct kunit *test) +{ + return cs35l56_shared_test_case_base_init(test, 0x56, 0xb0, &cs35l56_regmap_sdw); +} + +static int cs35l56_shared_test_case_regmap_init_L56_B0_spi(struct kunit *test) +{ + return cs35l56_shared_test_case_base_init(test, 0x56, 0xb0, &cs35l56_regmap_spi); +} + +static int cs35l56_shared_test_case_regmap_init_L56_B0_i2c(struct kunit *test) +{ + return cs35l56_shared_test_case_base_init(test, 0x56, 0xb0, &cs35l56_regmap_i2c); +} + +static int cs35l56_shared_test_case_regmap_init_L56_B2_sdw(struct kunit *test) +{ + return cs35l56_shared_test_case_base_init(test, 0x56, 0xb2, &cs35l56_regmap_sdw); +} + +static int cs35l56_shared_test_case_regmap_init_L56_B2_spi(struct kunit *test) +{ + return cs35l56_shared_test_case_base_init(test, 0x56, 0xb2, &cs35l56_regmap_spi); +} + +static int cs35l56_shared_test_case_regmap_init_L56_B2_i2c(struct kunit *test) +{ + return cs35l56_shared_test_case_base_init(test, 0x56, 0xb2, &cs35l56_regmap_i2c); +} + +static int cs35l56_shared_test_case_regmap_init_L63_A1_sdw(struct kunit *test) +{ + return cs35l56_shared_test_case_base_init(test, 0x63, 0xa1, &cs35l63_regmap_sdw); +} + +static void cs35l56_shared_test_gpio_param_desc(const struct cs35l56_shared_test_param *param, + char *desc) +{ + DECLARE_SEQ_BUF(gpios, 1 + (2 * ARRAY_SIZE(param->spkid_gpios))); + DECLARE_SEQ_BUF(pulls, 1 + (2 * ARRAY_SIZE(param->spkid_pulls))); + int i; + + for (i = 0; i < ARRAY_SIZE(param->spkid_gpios); i++) { + if (param->spkid_gpios[i] < 0) + break; + + seq_buf_printf(&gpios, "%s%d", (i == 0) ? "" : ",", param->spkid_gpios[i]); + } + + for (i = 0; i < ARRAY_SIZE(param->spkid_pulls); i++) { + if (param->spkid_pulls[i] < 0) + break; + + seq_buf_printf(&pulls, "%s%d", (i == 0) ? "" : ",", param->spkid_pulls[i]); + } + + snprintf(desc, KUNIT_PARAM_DESC_SIZE, "gpios:{%s} pulls:{%s} status:%#lx spkid:%d", + seq_buf_str(&gpios), seq_buf_str(&pulls), param->gpio_status, param->spkid); +} + +static const struct cs35l56_shared_test_param cs35l56_shared_test_gpios_selftest_cases[] = { + { .spkid_gpios = { -1 }, .gpio_status = GENMASK(12, 0) }, +}; +KUNIT_ARRAY_PARAM(cs35l56_shared_test_gpios_selftest, + cs35l56_shared_test_gpios_selftest_cases, + cs35l56_shared_test_gpio_param_desc); + +static const struct cs35l56_shared_test_param cs35l56_shared_test_onchip_spkid_cases[] = { + { .spkid_gpios = { 1, -1 }, .gpio_status = 0, .spkid = 0 }, + { .spkid_gpios = { 1, -1 }, .gpio_status = ~BIT(0), .spkid = 0 }, + { .spkid_gpios = { 1, -1 }, .gpio_status = BIT(0), .spkid = 1 }, + + { .spkid_gpios = { 7, -1 }, .gpio_status = 0, .spkid = 0 }, + { .spkid_gpios = { 7, -1 }, .gpio_status = ~BIT(6), .spkid = 0 }, + { .spkid_gpios = { 7, -1 }, .gpio_status = BIT(6), .spkid = 1 }, + + { .spkid_gpios = { 1, 7, -1 }, .gpio_status = 0, .spkid = 0 }, + { .spkid_gpios = { 1, 7, -1 }, .gpio_status = ~(BIT(0) | BIT(6)), .spkid = 0 }, + { .spkid_gpios = { 1, 7, -1 }, .gpio_status = BIT(6), .spkid = 1 }, + { .spkid_gpios = { 1, 7, -1 }, .gpio_status = BIT(0), .spkid = 2 }, + { .spkid_gpios = { 1, 7, -1 }, .gpio_status = BIT(6) | BIT(0), .spkid = 3 }, + + { .spkid_gpios = { 7, 1, -1 }, .gpio_status = 0, .spkid = 0 }, + { .spkid_gpios = { 7, 1, -1 }, .gpio_status = ~(BIT(6) | BIT(0)), .spkid = 0 }, + { .spkid_gpios = { 7, 1, -1 }, .gpio_status = BIT(0), .spkid = 1 }, + { .spkid_gpios = { 7, 1, -1 }, .gpio_status = BIT(6), .spkid = 2 }, + { .spkid_gpios = { 7, 1, -1 }, .gpio_status = BIT(6) | BIT(0), .spkid = 3 }, + + { .spkid_gpios = { 3, 7, 1, -1 }, .gpio_status = 0, .spkid = 0 }, + { .spkid_gpios = { 3, 7, 1, -1 }, .gpio_status = BIT(0), .spkid = 1 }, + { .spkid_gpios = { 3, 7, 1, -1 }, .gpio_status = BIT(6), .spkid = 2 }, + { .spkid_gpios = { 3, 7, 1, -1 }, .gpio_status = BIT(6) | BIT(0), .spkid = 3 }, + { .spkid_gpios = { 3, 7, 1, -1 }, .gpio_status = BIT(2), .spkid = 4 }, + { .spkid_gpios = { 3, 7, 1, -1 }, .gpio_status = BIT(2) | BIT(0), .spkid = 5 }, + { .spkid_gpios = { 3, 7, 1, -1 }, .gpio_status = BIT(2) | BIT(6), .spkid = 6 }, + { .spkid_gpios = { 3, 7, 1, -1 }, .gpio_status = BIT(2) | BIT(6) | BIT(0), .spkid = 7 }, +}; +KUNIT_ARRAY_PARAM(cs35l56_shared_test_onchip_spkid, cs35l56_shared_test_onchip_spkid_cases, + cs35l56_shared_test_gpio_param_desc); + +static const struct cs35l56_shared_test_param cs35l56_shared_test_onchip_spkid_pull_cases[] = { + { .spkid_gpios = { 1, -1 }, .spkid_pulls = { 1, -1 }, }, + { .spkid_gpios = { 1, -1 }, .spkid_pulls = { 2, -1 }, }, + + { .spkid_gpios = { 7, -1 }, .spkid_pulls = { 1, -1 }, }, + { .spkid_gpios = { 7, -1 }, .spkid_pulls = { 2, -1 }, }, + + { .spkid_gpios = { 1, 7, -1 }, .spkid_pulls = { 1, 1, -1 }, }, + { .spkid_gpios = { 1, 7, -1 }, .spkid_pulls = { 2, 2, -1 }, }, + + { .spkid_gpios = { 7, 1, -1 }, .spkid_pulls = { 1, 1, -1 }, }, + { .spkid_gpios = { 7, 1, -1 }, .spkid_pulls = { 2, 2, -1 }, }, + + { .spkid_gpios = { 3, 7, 1, -1 }, .spkid_pulls = { 1, 1, 1, -1 }, }, + { .spkid_gpios = { 3, 7, 1, -1 }, .spkid_pulls = { 2, 2, 2, -1 }, }, +}; +KUNIT_ARRAY_PARAM(cs35l56_shared_test_onchip_spkid_pull, + cs35l56_shared_test_onchip_spkid_pull_cases, + cs35l56_shared_test_gpio_param_desc); + +static struct kunit_case cs35l56_shared_test_cases[] = { + /* Tests for speaker id */ + KUNIT_CASE_PARAM(cs35l56_shared_test_mock_gpio_status_selftest, + cs35l56_shared_test_gpios_selftest_gen_params), + KUNIT_CASE_PARAM(cs35l56_shared_test_get_onchip_speaker_id, + cs35l56_shared_test_onchip_spkid_gen_params), + KUNIT_CASE_PARAM(cs35l56_shared_test_onchip_speaker_id_pad_config, + cs35l56_shared_test_onchip_spkid_gen_params), + KUNIT_CASE_PARAM(cs35l56_shared_test_onchip_speaker_id_pad_config, + cs35l56_shared_test_onchip_spkid_pull_gen_params), + KUNIT_CASE_PARAM(cs35l56_shared_test_stash_onchip_spkid_pins, + cs35l56_shared_test_onchip_spkid_pull_gen_params), + KUNIT_CASE(cs35l56_shared_test_stash_onchip_spkid_pins_reject_invalid), + KUNIT_CASE(cs35l56_shared_test_onchip_speaker_id_not_defined), + { } +}; + +static struct kunit_suite cs35l56_shared_test_suite_L56_B0_sdw = { + .name = "snd-soc-cs35l56-shared-test_L56_B0_sdw", + .init = cs35l56_shared_test_case_regmap_init_L56_B0_sdw, + .test_cases = cs35l56_shared_test_cases, +}; + +static struct kunit_suite cs35l56_shared_test_suite_L56_B2_sdw = { + .name = "snd-soc-cs35l56-shared-test_L56_B2_sdw", + .init = cs35l56_shared_test_case_regmap_init_L56_B2_sdw, + .test_cases = cs35l56_shared_test_cases, +}; + +static struct kunit_suite cs35l56_shared_test_suite_L63_A1_sdw = { + .name = "snd-soc-cs35l56-shared-test_L63_A1_sdw", + .init = cs35l56_shared_test_case_regmap_init_L63_A1_sdw, + .test_cases = cs35l56_shared_test_cases, +}; + +static struct kunit_suite cs35l56_shared_test_suite_L56_B0_spi = { + .name = "snd-soc-cs35l56-shared-test_L56_B0_spi", + .init = cs35l56_shared_test_case_regmap_init_L56_B0_spi, + .test_cases = cs35l56_shared_test_cases, +}; + +static struct kunit_suite cs35l56_shared_test_suite_L56_B2_spi = { + .name = "snd-soc-cs35l56-shared-test_L56_B2_spi", + .init = cs35l56_shared_test_case_regmap_init_L56_B2_spi, + .test_cases = cs35l56_shared_test_cases, +}; + +static struct kunit_suite cs35l56_shared_test_suite_L56_B0_i2c = { + .name = "snd-soc-cs35l56-shared-test_L56_B0_i2c", + .init = cs35l56_shared_test_case_regmap_init_L56_B0_i2c, + .test_cases = cs35l56_shared_test_cases, +}; + +static struct kunit_suite cs35l56_shared_test_suite_L56_B2_i2c = { + .name = "snd-soc-cs35l56-shared-test_L56_B2_i2c", + .init = cs35l56_shared_test_case_regmap_init_L56_B2_i2c, + .test_cases = cs35l56_shared_test_cases, +}; + +kunit_test_suites( + &cs35l56_shared_test_suite_L56_B0_sdw, + &cs35l56_shared_test_suite_L56_B2_sdw, + &cs35l56_shared_test_suite_L63_A1_sdw, + + &cs35l56_shared_test_suite_L56_B0_spi, + &cs35l56_shared_test_suite_L56_B2_spi, + + &cs35l56_shared_test_suite_L56_B0_i2c, + &cs35l56_shared_test_suite_L56_B2_i2c, +); + +MODULE_IMPORT_NS("SND_SOC_CS35L56_SHARED"); +MODULE_IMPORT_NS("SND_SOC_CS_AMP_LIB"); +MODULE_DESCRIPTION("KUnit test for cs35l56-shared module"); +MODULE_AUTHOR("Richard Fitzgerald "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs35l56-shared.c b/sound/soc/codecs/cs35l56-shared.c index 60100c8f8c95..4707f28bfca2 100644 --- a/sound/soc/codecs/cs35l56-shared.c +++ b/sound/soc/codecs/cs35l56-shared.c @@ -5,13 +5,16 @@ // Copyright (C) 2023 Cirrus Logic, Inc. and // Cirrus Logic International Semiconductor Ltd. +#include #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -182,6 +185,8 @@ static bool cs35l56_readable_reg(struct device *dev, unsigned int reg) case CS35L56_OTP_MEM_53: case CS35L56_OTP_MEM_54: case CS35L56_OTP_MEM_55: + case CS35L56_SYNC_GPIO1_CFG ... CS35L56_ASP2_DIO_GPIO13_CFG: + case CS35L56_UPDATE_REGS: case CS35L56_ASP1_ENABLES1: case CS35L56_ASP1_CONTROL1: case CS35L56_ASP1_CONTROL2: @@ -213,6 +218,7 @@ static bool cs35l56_readable_reg(struct device *dev, unsigned int reg) case CS35L56_IRQ1_MASK_8: case CS35L56_IRQ1_MASK_18: case CS35L56_IRQ1_MASK_20: + case CS35L56_GPIO_STATUS1 ... CS35L56_GPIO13_CTRL1: case CS35L56_MIXER_NGATE_CH1_CFG: case CS35L56_MIXER_NGATE_CH2_CFG: case CS35L56_DSP_VIRTUAL1_MBOX_1: @@ -262,6 +268,8 @@ static bool cs35l56_common_volatile_reg(unsigned int reg) case CS35L56_GLOBAL_ENABLES: /* owned by firmware */ case CS35L56_BLOCK_ENABLES: /* owned by firmware */ case CS35L56_BLOCK_ENABLES2: /* owned by firmware */ + case CS35L56_SYNC_GPIO1_CFG ... CS35L56_ASP2_DIO_GPIO13_CFG: + case CS35L56_UPDATE_REGS: case CS35L56_REFCLK_INPUT: /* owned by firmware */ case CS35L56_GLOBAL_SAMPLE_RATE: /* owned by firmware */ case CS35L56_DACPCM1_INPUT: /* owned by firmware */ @@ -272,6 +280,7 @@ static bool cs35l56_common_volatile_reg(unsigned int reg) case CS35L56_IRQ1_EINT_1 ... CS35L56_IRQ1_EINT_8: case CS35L56_IRQ1_EINT_18: case CS35L56_IRQ1_EINT_20: + case CS35L56_GPIO_STATUS1 ... CS35L56_GPIO13_CTRL1: case CS35L56_MIXER_NGATE_CH1_CFG: case CS35L56_MIXER_NGATE_CH2_CFG: case CS35L56_DSP_VIRTUAL1_MBOX_1: @@ -1552,6 +1561,169 @@ int cs35l56_get_speaker_id(struct cs35l56_base *cs35l56_base) } EXPORT_SYMBOL_NS_GPL(cs35l56_get_speaker_id, "SND_SOC_CS35L56_SHARED"); +int cs35l56_check_and_save_onchip_spkid_gpios(struct cs35l56_base *cs35l56_base, + const u32 *gpios, int num_gpios, + const u32 *pulls, int num_pulls) +{ + int max_gpio; + int ret = 0; + int i; + + if ((num_gpios > ARRAY_SIZE(cs35l56_base->onchip_spkid_gpios)) || + (num_pulls > ARRAY_SIZE(cs35l56_base->onchip_spkid_pulls))) + return -EOVERFLOW; + + switch (cs35l56_base->type) { + case 0x54: + case 0x56: + case 0x57: + max_gpio = CS35L56_MAX_GPIO; + break; + default: + max_gpio = CS35L63_MAX_GPIO; + break; + } + + for (i = 0; i < num_gpios; i++) { + if (gpios[i] < 1 || gpios[i] > max_gpio) { + dev_err(cs35l56_base->dev, "Invalid spkid GPIO %d\n", gpios[i]); + /* Keep going so we log all bad values */ + ret = -EINVAL; + } + + /* Change to zero-based */ + cs35l56_base->onchip_spkid_gpios[i] = gpios[i] - 1; + } + + for (i = 0; i < num_pulls; i++) { + switch (pulls[i]) { + case 0: + cs35l56_base->onchip_spkid_pulls[i] = CS35L56_PAD_PULL_NONE; + break; + case 1: + cs35l56_base->onchip_spkid_pulls[i] = CS35L56_PAD_PULL_UP; + break; + case 2: + cs35l56_base->onchip_spkid_pulls[i] = CS35L56_PAD_PULL_DOWN; + break; + default: + dev_err(cs35l56_base->dev, "Invalid spkid pull %d\n", pulls[i]); + /* Keep going so we log all bad values */ + ret = -EINVAL; + break; + } + } + if (ret) + return ret; + + cs35l56_base->num_onchip_spkid_gpios = num_gpios; + cs35l56_base->num_onchip_spkid_pulls = num_pulls; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(cs35l56_check_and_save_onchip_spkid_gpios, "SND_SOC_CS35L56_SHARED"); + +/* Caller must pm_runtime resume before calling this function */ +int cs35l56_configure_onchip_spkid_pads(struct cs35l56_base *cs35l56_base) +{ + struct regmap *regmap = cs35l56_base->regmap; + unsigned int addr_offset, val; + int num_gpios, num_pulls; + int i, ret; + + KUNIT_STATIC_STUB_REDIRECT(cs35l56_configure_onchip_spkid_pads, cs35l56_base); + + if (cs35l56_base->num_onchip_spkid_gpios == 0) + return 0; + + num_gpios = min(cs35l56_base->num_onchip_spkid_gpios, + ARRAY_SIZE(cs35l56_base->onchip_spkid_gpios)); + num_pulls = min(cs35l56_base->num_onchip_spkid_pulls, + ARRAY_SIZE(cs35l56_base->onchip_spkid_pulls)); + + for (i = 0; i < num_gpios; i++) { + addr_offset = cs35l56_base->onchip_spkid_gpios[i] * sizeof(u32); + + /* Set unspecified pulls to NONE */ + if (i < num_pulls) { + val = FIELD_PREP(CS35L56_PAD_GPIO_PULL_MASK, + cs35l56_base->onchip_spkid_pulls[i]); + } else { + val = FIELD_PREP(CS35L56_PAD_GPIO_PULL_MASK, CS35L56_PAD_PULL_NONE); + } + + ret = regmap_update_bits(regmap, CS35L56_SYNC_GPIO1_CFG + addr_offset, + CS35L56_PAD_GPIO_PULL_MASK | CS35L56_PAD_GPIO_IE, + val | CS35L56_PAD_GPIO_IE); + if (ret) { + dev_err(cs35l56_base->dev, "GPIO%d set pad fail: %d\n", + cs35l56_base->onchip_spkid_gpios[i] + 1, ret); + return ret; + } + } + + ret = regmap_write(regmap, CS35L56_UPDATE_REGS, CS35L56_UPDT_GPIO_PRES); + if (ret) { + dev_err(cs35l56_base->dev, "UPDT_GPIO_PRES failed:%d\n", ret); + return ret; + } + + usleep_range(CS35L56_PAD_PULL_SETTLE_US, CS35L56_PAD_PULL_SETTLE_US * 2); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(cs35l56_configure_onchip_spkid_pads, "SND_SOC_CS35L56_SHARED"); + +/* Caller must pm_runtime resume before calling this function */ +int cs35l56_read_onchip_spkid(struct cs35l56_base *cs35l56_base) +{ + struct regmap *regmap = cs35l56_base->regmap; + unsigned int addr_offset, val; + int num_gpios; + int speaker_id = 0; + int i, ret; + + KUNIT_STATIC_STUB_REDIRECT(cs35l56_read_onchip_spkid, cs35l56_base); + + if (cs35l56_base->num_onchip_spkid_gpios == 0) + return -ENOENT; + + num_gpios = min(cs35l56_base->num_onchip_spkid_gpios, + ARRAY_SIZE(cs35l56_base->onchip_spkid_gpios)); + + for (i = 0; i < num_gpios; i++) { + addr_offset = cs35l56_base->onchip_spkid_gpios[i] * sizeof(u32); + + ret = regmap_update_bits(regmap, CS35L56_GPIO1_CTRL1 + addr_offset, + CS35L56_GPIO_DIR_MASK | CS35L56_GPIO_FN_MASK, + CS35L56_GPIO_DIR_MASK | CS35L56_GPIO_FN_GPIO); + if (ret) { + dev_err(cs35l56_base->dev, "GPIO%u set func fail: %d\n", + cs35l56_base->onchip_spkid_gpios[i] + 1, ret); + return ret; + } + } + + ret = regmap_read(regmap, CS35L56_GPIO_STATUS1, &val); + if (ret) { + dev_err(cs35l56_base->dev, "GPIO%d status read failed: %d\n", + cs35l56_base->onchip_spkid_gpios[i] + 1, ret); + return ret; + } + + for (i = 0; i < num_gpios; i++) { + speaker_id <<= 1; + + if (val & BIT(cs35l56_base->onchip_spkid_gpios[i])) + speaker_id |= 1; + } + + dev_dbg(cs35l56_base->dev, "Onchip GPIO Speaker ID = %d\n", speaker_id); + + return speaker_id; +} +EXPORT_SYMBOL_NS_GPL(cs35l56_read_onchip_spkid, "SND_SOC_CS35L56_SHARED"); + static const u32 cs35l56_bclk_valid_for_pll_freq_table[] = { [0x0C] = 128000, [0x0F] = 256000, diff --git a/sound/soc/codecs/cs35l56-test.c b/sound/soc/codecs/cs35l56-test.c index a7b21660c402..b6c8c08e3ade 100644 --- a/sound/soc/codecs/cs35l56-test.c +++ b/sound/soc/codecs/cs35l56-test.c @@ -15,6 +15,8 @@ #include #include #include +#include +#include #include #include #include @@ -23,16 +25,46 @@ KUNIT_DEFINE_ACTION_WRAPPER(faux_device_destroy_wrapper, faux_device_destroy, struct faux_device *) +KUNIT_DEFINE_ACTION_WRAPPER(software_node_unregister_node_group_wrapper, + software_node_unregister_node_group, + const struct software_node * const *) + +KUNIT_DEFINE_ACTION_WRAPPER(software_node_unregister_wrapper, + software_node_unregister, + const struct software_node *) + +KUNIT_DEFINE_ACTION_WRAPPER(device_remove_software_node_wrapper, + device_remove_software_node, + struct device *) + struct cs35l56_test_priv { struct faux_device *amp_dev; struct cs35l56_private *cs35l56_priv; const char *ssidexv2; + + bool read_onchip_spkid_called; + bool configure_onchip_spkid_pads_called; }; struct cs35l56_test_param { u8 type; u8 rev; + + s32 spkid_gpios[4]; + s32 spkid_pulls[4]; +}; + +static const struct software_node cs35l56_test_dev_sw_node = + SOFTWARE_NODE("SWD1", NULL, NULL); + +static const struct software_node cs35l56_test_af01_sw_node = + SOFTWARE_NODE("AF01", NULL, &cs35l56_test_dev_sw_node); + +static const struct software_node *cs35l56_test_dev_and_af01_node_group[] = { + &cs35l56_test_dev_sw_node, + &cs35l56_test_af01_sw_node, + NULL }; static const char *cs35l56_test_devm_get_vendor_specific_variant_id_none(struct device *dev, @@ -232,6 +264,190 @@ static void cs35l56_test_l56_b0_ssidexv2_ignored_suffix_sdw(struct kunit *test) KUNIT_EXPECT_STREQ(test, cs35l56->fallback_fw_suffix, "l1u5"); } +/* + * Test that cs35l56_process_xu_properties() correctly parses the GPIO and + * pull values from properties into the arrays in struct cs35l56_base. + * + * This test creates the node tree: + * + * Node("SWD1") { // top-level device node + * Node("AF01") { + * Node("mipi-sdca-function-expansion-subproperties") { + * property: "01fa-spk-id-gpios-onchip" + * property: 01fa-spk-id-gpios-onchip-pull + * } + * } + * } + * + * Note that in ACPI "mipi-sdca-function-expansion-subproperties" is + * a special _DSD property that points to a Device(EXT0) node but behaves + * as an alias of the EXT0 node. The equivalent in software nodes is to + * create a Node named "mipi-sdca-function-expansion-subproperties" with + * the properties. + * + */ +static void cs35l56_test_parse_xu_onchip_spkid(struct kunit *test) +{ + const struct cs35l56_test_param *param = test->param_value; + struct cs35l56_test_priv *priv = test->priv; + struct cs35l56_private *cs35l56 = priv->cs35l56_priv; + struct software_node *ext0_node; + int num_gpios = 0; + int num_pulls = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(param->spkid_gpios); i++, num_gpios++) { + if (param->spkid_gpios[i] < 0) + break; + } + KUNIT_ASSERT_LE(test, num_gpios, ARRAY_SIZE(cs35l56->base.onchip_spkid_gpios)); + + for (i = 0; i < ARRAY_SIZE(param->spkid_pulls); i++, num_pulls++) { + if (param->spkid_pulls[i] < 0) + break; + } + KUNIT_ASSERT_LE(test, num_pulls, ARRAY_SIZE(cs35l56->base.onchip_spkid_pulls)); + + const struct property_entry ext0_props[] = { + PROPERTY_ENTRY_U32_ARRAY_LEN("01fa-spk-id-gpios-onchip", + param->spkid_gpios, num_gpios), + PROPERTY_ENTRY_U32_ARRAY_LEN("01fa-spk-id-gpios-onchip-pull", + param->spkid_pulls, num_pulls), + { } + }; + + KUNIT_ASSERT_EQ(test, + software_node_register_node_group(cs35l56_test_dev_and_af01_node_group), + 0); + KUNIT_ASSERT_EQ(test, + kunit_add_action_or_reset(test, + software_node_unregister_node_group_wrapper, + cs35l56_test_dev_and_af01_node_group), + 0); + + ext0_node = kunit_kzalloc(test, sizeof(*ext0_node), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ext0_node); + *ext0_node = SOFTWARE_NODE("mipi-sdca-function-expansion-subproperties", + ext0_props, &cs35l56_test_af01_sw_node); + + KUNIT_ASSERT_EQ(test, software_node_register(ext0_node), 0); + KUNIT_ASSERT_EQ(test, + kunit_add_action_or_reset(test, + software_node_unregister_wrapper, + ext0_node), + 0); + + KUNIT_ASSERT_EQ(test, + device_add_software_node(cs35l56->base.dev, &cs35l56_test_dev_sw_node), 0); + KUNIT_ASSERT_EQ(test, 0, + kunit_add_action_or_reset(test, + device_remove_software_node_wrapper, + cs35l56->base.dev)); + + KUNIT_EXPECT_EQ(test, cs35l56_process_xu_properties(cs35l56), 0); + + KUNIT_EXPECT_EQ(test, cs35l56->base.num_onchip_spkid_gpios, num_gpios); + KUNIT_EXPECT_EQ(test, cs35l56->base.num_onchip_spkid_pulls, num_pulls); + + for (i = 0; i < ARRAY_SIZE(param->spkid_gpios); i++) { + if (param->spkid_gpios[i] < 0) + break; + + /* + * cs35l56_process_xu_properties() stores the GPIO numbers + * zero-based, which is one less than the value in the property. + */ + KUNIT_EXPECT_EQ_MSG(test, cs35l56->base.onchip_spkid_gpios[i], + param->spkid_gpios[i] - 1, + "i=%d", i); + } + + for (i = 0; i < ARRAY_SIZE(param->spkid_pulls); i++) { + if (param->spkid_pulls[i] < 0) + break; + + KUNIT_EXPECT_EQ_MSG(test, cs35l56->base.onchip_spkid_pulls[i], + param->spkid_pulls[i], "i=%d", i); + } +} + +static int cs35l56_test_dummy_read_onchip_spkid(struct cs35l56_base *cs35l56_base) +{ + struct kunit *test = kunit_get_current_test(); + struct cs35l56_test_priv *priv = test->priv; + + priv->read_onchip_spkid_called = true; + + return 4; +} + +static int cs35l56_test_dummy_configure_onchip_spkid_pads(struct cs35l56_base *cs35l56_base) +{ + struct kunit *test = kunit_get_current_test(); + struct cs35l56_test_priv *priv = test->priv; + + priv->configure_onchip_spkid_pads_called = true; + + return 0; +} + +static void cs35l56_test_set_fw_name_reads_onchip_spkid(struct kunit *test) +{ + struct cs35l56_test_priv *priv = test->priv; + struct cs35l56_private *cs35l56 = priv->cs35l56_priv; + + /* Provide some on-chip GPIOs for spkid */ + cs35l56->base.onchip_spkid_gpios[0] = 1; + cs35l56->base.num_onchip_spkid_gpios = 1; + + cs35l56->speaker_id = -ENOENT; + + kunit_activate_static_stub(test, + cs35l56_configure_onchip_spkid_pads, + cs35l56_test_dummy_configure_onchip_spkid_pads); + kunit_activate_static_stub(test, + cs35l56_read_onchip_spkid, + cs35l56_test_dummy_read_onchip_spkid); + + priv->configure_onchip_spkid_pads_called = false; + priv->read_onchip_spkid_called = false; + KUNIT_EXPECT_EQ(test, cs35l56_set_fw_name(cs35l56->component), 0); + KUNIT_EXPECT_TRUE(test, priv->configure_onchip_spkid_pads_called); + KUNIT_EXPECT_TRUE(test, priv->read_onchip_spkid_called); + KUNIT_EXPECT_EQ(test, cs35l56->speaker_id, + cs35l56_test_dummy_read_onchip_spkid(&cs35l56->base)); +} + +static void cs35l56_test_set_fw_name_preserves_spkid_with_onchip_gpios(struct kunit *test) +{ + struct cs35l56_test_priv *priv = test->priv; + struct cs35l56_private *cs35l56 = priv->cs35l56_priv; + + /* Provide some on-chip GPIOs for spkid */ + cs35l56->base.onchip_spkid_gpios[0] = 1; + cs35l56->base.num_onchip_spkid_gpios = 1; + + /* Simulate that the driver already got a spkid from somewhere */ + cs35l56->speaker_id = 15; + + KUNIT_EXPECT_EQ(test, cs35l56_set_fw_name(cs35l56->component), 0); + KUNIT_EXPECT_EQ(test, cs35l56->speaker_id, 15); +} + +static void cs35l56_test_set_fw_name_preserves_spkid_without_onchip_gpios(struct kunit *test) +{ + struct cs35l56_test_priv *priv = test->priv; + struct cs35l56_private *cs35l56 = priv->cs35l56_priv; + + cs35l56->base.num_onchip_spkid_gpios = 0; + + /* Simulate that the driver already got a spkid from somewhere */ + cs35l56->speaker_id = 15; + + KUNIT_EXPECT_EQ(test, cs35l56_set_fw_name(cs35l56->component), 0); + KUNIT_EXPECT_EQ(test, cs35l56->speaker_id, 15); +} + static int cs35l56_test_case_init_common(struct kunit *test) { struct cs35l56_test_priv *priv; @@ -263,6 +479,7 @@ static int cs35l56_test_case_init_common(struct kunit *test) cs35l56->component = kunit_kzalloc(test, sizeof(*cs35l56->component), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, cs35l56->component); cs35l56->component->dev = cs35l56->base.dev; + snd_soc_component_set_drvdata(cs35l56->component, cs35l56); cs35l56->component->card = kunit_kzalloc(test, sizeof(*cs35l56->component->card), GFP_KERNEL); @@ -299,6 +516,50 @@ static int cs35l56_test_case_init_soundwire(struct kunit *test) return 0; } +static void cs35l56_test_gpio_param_desc(const struct cs35l56_test_param *param, char *desc) +{ + DECLARE_SEQ_BUF(gpios, 1 + (2 * ARRAY_SIZE(param->spkid_gpios))); + DECLARE_SEQ_BUF(pulls, 1 + (2 * ARRAY_SIZE(param->spkid_pulls))); + int i; + + for (i = 0; i < ARRAY_SIZE(param->spkid_gpios); i++) { + if (param->spkid_gpios[i] < 0) + break; + + seq_buf_printf(&gpios, "%s%d", (i == 0) ? "" : ",", param->spkid_gpios[i]); + } + + for (i = 0; i < ARRAY_SIZE(param->spkid_pulls); i++) { + if (param->spkid_pulls[i] < 0) + break; + + seq_buf_printf(&pulls, "%s%d", (i == 0) ? "" : ",", param->spkid_pulls[i]); + } + + snprintf(desc, KUNIT_PARAM_DESC_SIZE, "gpios:{%s} pulls:{%s}", + seq_buf_str(&gpios), seq_buf_str(&pulls)); +} + +static const struct cs35l56_test_param cs35l56_test_onchip_spkid_cases[] = { + { .spkid_gpios = { 1, -1 }, .spkid_pulls = { 1, -1 }, }, + { .spkid_gpios = { 1, -1 }, .spkid_pulls = { 2, -1 }, }, + + { .spkid_gpios = { 7, -1 }, .spkid_pulls = { 1, -1 }, }, + { .spkid_gpios = { 7, -1 }, .spkid_pulls = { 2, -1 }, }, + + { .spkid_gpios = { 1, 7, -1 }, .spkid_pulls = { 1, 1, -1 }, }, + { .spkid_gpios = { 1, 7, -1 }, .spkid_pulls = { 2, 2, -1 }, }, + + { .spkid_gpios = { 7, 1, -1 }, .spkid_pulls = { 1, 1, -1 }, }, + { .spkid_gpios = { 7, 1, -1 }, .spkid_pulls = { 2, 2, -1 }, }, + + { .spkid_gpios = { 3, 7, 1, -1 }, .spkid_pulls = { 1, 1, 1, -1 }, }, + { .spkid_gpios = { 3, 7, 1, -1 }, .spkid_pulls = { 2, 2, 2, -1 }, }, +}; +KUNIT_ARRAY_PARAM(cs35l56_test_onchip_spkid, + cs35l56_test_onchip_spkid_cases, + cs35l56_test_gpio_param_desc); + static void cs35l56_test_type_rev_param_desc(const struct cs35l56_test_param *param, char *desc) { @@ -331,6 +592,13 @@ static struct kunit_case cs35l56_test_cases_soundwire[] = { cs35l56_test_type_rev_ex_b0_gen_params), KUNIT_CASE(cs35l56_test_l56_b0_ssidexv2_ignored_suffix_sdw), + KUNIT_CASE_PARAM(cs35l56_test_parse_xu_onchip_spkid, + cs35l56_test_onchip_spkid_gen_params), + + KUNIT_CASE(cs35l56_test_set_fw_name_reads_onchip_spkid), + KUNIT_CASE(cs35l56_test_set_fw_name_preserves_spkid_with_onchip_gpios), + KUNIT_CASE(cs35l56_test_set_fw_name_preserves_spkid_without_onchip_gpios), + { } /* terminator */ }; @@ -339,6 +607,10 @@ static struct kunit_case cs35l56_test_cases_not_soundwire[] = { KUNIT_CASE_PARAM(cs35l56_test_ssidexv2_suffix_i2cspi, cs35l56_test_type_rev_all_gen_params), + KUNIT_CASE(cs35l56_test_set_fw_name_reads_onchip_spkid), + KUNIT_CASE(cs35l56_test_set_fw_name_preserves_spkid_with_onchip_gpios), + KUNIT_CASE(cs35l56_test_set_fw_name_preserves_spkid_without_onchip_gpios), + { } /* terminator */ }; @@ -360,6 +632,7 @@ kunit_test_suites( ); MODULE_IMPORT_NS("SND_SOC_CS_AMP_LIB"); +MODULE_IMPORT_NS("SND_SOC_CS35L56_SHARED"); MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); MODULE_DESCRIPTION("KUnit test for Cirrus Logic cs35l56 codec driver"); MODULE_AUTHOR("Richard Fitzgerald "); diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c index 31dd2f7b2858..2ff8b172b76e 100644 --- a/sound/soc/codecs/cs35l56.c +++ b/sound/soc/codecs/cs35l56.c @@ -1179,15 +1179,28 @@ VISIBLE_IF_KUNIT int cs35l56_set_fw_suffix(struct cs35l56_private *cs35l56) } EXPORT_SYMBOL_IF_KUNIT(cs35l56_set_fw_suffix); -static int cs35l56_component_probe(struct snd_soc_component *component) +VISIBLE_IF_KUNIT int cs35l56_set_fw_name(struct snd_soc_component *component) { - struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(component); - struct dentry *debugfs_root = component->debugfs_root; unsigned short vendor, device; int ret; - BUILD_BUG_ON(ARRAY_SIZE(cs35l56_tx_input_texts) != ARRAY_SIZE(cs35l56_tx_input_values)); + if ((cs35l56->speaker_id < 0) && cs35l56->base.num_onchip_spkid_gpios) { + PM_RUNTIME_ACQUIRE(cs35l56->base.dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + + ret = cs35l56_configure_onchip_spkid_pads(&cs35l56->base); + if (ret) + return ret; + + ret = cs35l56_read_onchip_spkid(&cs35l56->base); + if (ret < 0) + return ret; + + cs35l56->speaker_id = ret; + } if (!cs35l56->dsp.system_name && (snd_soc_card_get_pci_ssid(component->card, &vendor, &device) == 0)) { @@ -1208,6 +1221,19 @@ static int cs35l56_component_probe(struct snd_soc_component *component) return -ENOMEM; } + return 0; +} +EXPORT_SYMBOL_IF_KUNIT(cs35l56_set_fw_name); + +static int cs35l56_component_probe(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); + struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(component); + struct dentry *debugfs_root = component->debugfs_root; + int ret; + + BUILD_BUG_ON(ARRAY_SIZE(cs35l56_tx_input_texts) != ARRAY_SIZE(cs35l56_tx_input_values)); + if (!wait_for_completion_timeout(&cs35l56->init_completion, msecs_to_jiffies(5000))) { dev_err(cs35l56->base.dev, "%s: init_completion timed out\n", __func__); @@ -1219,6 +1245,10 @@ static int cs35l56_component_probe(struct snd_soc_component *component) return -ENOMEM; cs35l56->component = component; + ret = cs35l56_set_fw_name(component); + if (ret) + return ret; + ret = cs35l56_set_fw_suffix(cs35l56); if (ret) return ret; @@ -1532,6 +1562,105 @@ static int cs35l56_dsp_init(struct cs35l56_private *cs35l56) return 0; } +static int cs35l56_read_fwnode_u32_array(struct device *dev, + struct fwnode_handle *parent_node, + const char *prop_name, + int max_count, + u32 *dest) +{ + int count, ret; + + count = fwnode_property_count_u32(parent_node, prop_name); + if ((count == 0) || (count == -EINVAL) || (count == -ENODATA)) { + dev_dbg(dev, "%s not found in %s\n", prop_name, fwnode_get_name(parent_node)); + return 0; + } + + if (count < 0) { + dev_err(dev, "Get %s error:%d\n", prop_name, count); + return count; + } + + if (count > max_count) { + dev_err(dev, "%s too many entries (%d)\n", prop_name, count); + return -EOVERFLOW; + } + + ret = fwnode_property_read_u32_array(parent_node, prop_name, dest, count); + if (ret) { + dev_err(dev, "Error reading %s: %d\n", prop_name, ret); + return ret; + } + + return count; +} + +static int cs35l56_process_xu_onchip_speaker_id(struct cs35l56_private *cs35l56, + struct fwnode_handle *ext_node) +{ + static const char * const gpio_name = "01fa-spk-id-gpios-onchip"; + static const char * const pull_name = "01fa-spk-id-gpios-onchip-pull"; + u32 gpios[5], pulls[5]; + int num_gpios, num_pulls; + int ret; + + static_assert(ARRAY_SIZE(gpios) == ARRAY_SIZE(cs35l56->base.onchip_spkid_gpios)); + static_assert(ARRAY_SIZE(pulls) == ARRAY_SIZE(cs35l56->base.onchip_spkid_pulls)); + + num_gpios = cs35l56_read_fwnode_u32_array(cs35l56->base.dev, ext_node, gpio_name, + ARRAY_SIZE(gpios), gpios); + if (num_gpios < 1) + return num_gpios; + + num_pulls = cs35l56_read_fwnode_u32_array(cs35l56->base.dev, ext_node, pull_name, + ARRAY_SIZE(pulls), pulls); + if (num_pulls < 0) + return num_pulls; + + if (num_pulls != num_gpios) { + dev_warn(cs35l56->base.dev, "%s count(%d) != %s count(%d)\n", + pull_name, num_pulls, gpio_name, num_gpios); + } + + ret = cs35l56_check_and_save_onchip_spkid_gpios(&cs35l56->base, + gpios, num_gpios, + pulls, num_pulls); + if (ret) { + return dev_err_probe(cs35l56->base.dev, ret, "Error in %s/%s\n", + gpio_name, pull_name); + } + + return 0; +} + +VISIBLE_IF_KUNIT int cs35l56_process_xu_properties(struct cs35l56_private *cs35l56) +{ + struct fwnode_handle *ext_node = NULL; + struct fwnode_handle *link; + int ret; + + if (!cs35l56->sdw_peripheral) + return 0; + + fwnode_for_each_child_node(dev_fwnode(cs35l56->base.dev), link) { + ext_node = fwnode_get_named_child_node(link, + "mipi-sdca-function-expansion-subproperties"); + if (ext_node) { + fwnode_handle_put(link); + break; + } + } + + if (!ext_node) + return 0; + + ret = cs35l56_process_xu_onchip_speaker_id(cs35l56, ext_node); + fwnode_handle_put(ext_node); + + return ret; +} +EXPORT_SYMBOL_IF_KUNIT(cs35l56_process_xu_properties); + static int cs35l56_get_firmware_uid(struct cs35l56_private *cs35l56) { struct device *dev = cs35l56->base.dev; @@ -1712,6 +1841,10 @@ int cs35l56_common_probe(struct cs35l56_private *cs35l56) if (ret != 0) goto err; + ret = cs35l56_process_xu_properties(cs35l56); + if (ret) + goto err; + ret = cs35l56_dsp_init(cs35l56); if (ret < 0) { dev_err_probe(cs35l56->base.dev, ret, "DSP init failed\n"); diff --git a/sound/soc/codecs/cs35l56.h b/sound/soc/codecs/cs35l56.h index 7187885a13c1..691f857d0bd8 100644 --- a/sound/soc/codecs/cs35l56.h +++ b/sound/soc/codecs/cs35l56.h @@ -76,6 +76,8 @@ void cs35l56_remove(struct cs35l56_private *cs35l56); #if IS_ENABLED(CONFIG_KUNIT) int cs35l56_set_fw_suffix(struct cs35l56_private *cs35l56); +int cs35l56_set_fw_name(struct snd_soc_component *component); +int cs35l56_process_xu_properties(struct cs35l56_private *cs35l56); #endif #endif /* ifndef CS35L56_H */