ASoC: cs35l56: More support for new Dell laptops

Merge series from Richard Fitzgerald <rf@opensource.cirrus.com>:

Some new Dell models use spare pins on the amp as a binary integer
value to indicate the speaker type. The driver must use this to
select the correct firmware files for the hardware.

Patch #1 is the new support.
The other patches are for KUnit testing.
This commit is contained in:
Mark Brown 2026-02-05 22:41:22 +00:00
commit 5209af4db0
No known key found for this signature in database
GPG Key ID: 24D68B725D5487D0
8 changed files with 1317 additions and 4 deletions

View File

@ -9,6 +9,7 @@
#ifndef __CS35L56_H
#define __CS35L56_H
#include <linux/bits.h>
#include <linux/debugfs.h>
#include <linux/firmware/cirrus/cs_dsp.h>
#include <linux/regulator/consumer.h>
@ -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);

View File

@ -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/.

View File

@ -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

View File

@ -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 <kunit/resource.h>
#include <kunit/test.h>
#include <kunit/static_stub.h>
#include <linux/bitfield.h>
#include <linux/bitops.h>
#include <linux/device/faux.h>
#include <linux/module.h>
#include <linux/random.h>
#include <linux/regmap.h>
#include <linux/seq_buf.h>
#include <sound/cs35l56.h>
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 <rf@opensource.cirrus.com>");
MODULE_LICENSE("GPL");

View File

@ -5,13 +5,16 @@
// Copyright (C) 2023 Cirrus Logic, Inc. and
// Cirrus Logic International Semiconductor Ltd.
#include <kunit/static_stub.h>
#include <linux/array_size.h>
#include <linux/bitfield.h>
#include <linux/cleanup.h>
#include <linux/debugfs.h>
#include <linux/firmware/cirrus/wmfw.h>
#include <linux/fs.h>
#include <linux/gpio/consumer.h>
#include <linux/kstrtox.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/spi/spi.h>
@ -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,

View File

@ -15,6 +15,8 @@
#include <linux/module.h>
#include <linux/overflow.h>
#include <linux/pci_ids.h>
#include <linux/property.h>
#include <linux/seq_buf.h>
#include <linux/soundwire/sdw.h>
#include <sound/cs35l56.h>
#include <sound/cs-amp-lib.h>
@ -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 <rf@opensource.cirrus.com>");

View File

@ -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");

View File

@ -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 */