#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>
#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
#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
#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
/* 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
#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)
#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)
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)
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);
// Cirrus Logic International Semiconductor Ltd.
#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>
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:
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:
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 */
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:
}
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;
+
+ 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;
+
+ 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,
}
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)) {
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__);
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;
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;
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");
#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 */