From 557eb6e5eaa6cdbc9b4b6ec1fc4733549dcd9d02 Mon Sep 17 00:00:00 2001 From: Ricardo Rivera-Matos Date: Tue, 10 Sep 2024 15:19:31 +0000 Subject: [PATCH] drivers: audio: Adds support for CS35L45 smart amplifier Adds basic audio functionality for the CS35L45 smart amplifier. Signed-off-by: Ricardo Rivera-Matos --- drivers/audio/CMakeLists.txt | 1 + drivers/audio/Kconfig | 1 + drivers/audio/Kconfig.cs35l45 | 11 + drivers/audio/cs35l45.c | 569 ++++++++++++++++++++++++++++++++++ drivers/audio/cs35l45.h | 159 ++++++++++ 5 files changed, 741 insertions(+) create mode 100644 drivers/audio/Kconfig.cs35l45 create mode 100644 drivers/audio/cs35l45.c create mode 100644 drivers/audio/cs35l45.h diff --git a/drivers/audio/CMakeLists.txt b/drivers/audio/CMakeLists.txt index 0396ae23e483f4..5f1b1f0472f66a 100644 --- a/drivers/audio/CMakeLists.txt +++ b/drivers/audio/CMakeLists.txt @@ -10,3 +10,4 @@ zephyr_library_sources_ifdef(CONFIG_AUDIO_TAS6422DAC tas6422dac.c) zephyr_library_sources_ifdef(CONFIG_AUDIO_CODEC_SHELL codec_shell.c) zephyr_library_sources_ifdef(CONFIG_AUDIO_DMIC_MCUX dmic_mcux.c) zephyr_library_sources_ifdef(CONFIG_AUDIO_CODEC_WM8904 wm8904.c) +zephyr_library_sources_ifdef(CONFIG_AUDIO_CODEC_CS35L45 cs35l45.c) diff --git a/drivers/audio/Kconfig b/drivers/audio/Kconfig index 70d02b5320a219..b201c1dec85e83 100644 --- a/drivers/audio/Kconfig +++ b/drivers/audio/Kconfig @@ -38,6 +38,7 @@ source "subsys/logging/Kconfig.template.log_config" source "drivers/audio/Kconfig.tas6422dac" source "drivers/audio/Kconfig.tlv320dac" source "drivers/audio/Kconfig.wm8904" +source "drivers/audio/Kconfig.cs35l45" endif # AUDIO_CODEC diff --git a/drivers/audio/Kconfig.cs35l45 b/drivers/audio/Kconfig.cs35l45 new file mode 100644 index 00000000000000..f58eb24a34c049 --- /dev/null +++ b/drivers/audio/Kconfig.cs35l45 @@ -0,0 +1,11 @@ +# Copyright (c) 2024 Cirrus Logic, Inc. +# SPDX-License-Identifier: Apache-2.0 + +config AUDIO_CODEC_CS35L45 + bool "Cirrus Logic CS35L45 codec support" + depends on DT_HAS_CIRRUS_CS35L45_ENABLED + select I2C if $(dt_compat_on_bus,$(DT_COMPAT_CIRRUS_CS35L45),i2c) + select GPIO + select REGULATOR + help + Enable driver for CS35L45 smart amplifier diff --git a/drivers/audio/cs35l45.c b/drivers/audio/cs35l45.c new file mode 100644 index 00000000000000..da13fcc64082bb --- /dev/null +++ b/drivers/audio/cs35l45.c @@ -0,0 +1,569 @@ +/* + * Copyright 2024 Cirrus Logic, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT cirrus_cs35l45 + +#include +#include +#include +#include +#if DT_ANY_INST_ON_BUS_STATUS_OKAY(i2c) +#include +#include +#endif /* DT_ANY_INST_ON_BUS_STATUS_OKAY */ + +#include "cs35l45.h" + +#include +LOG_MODULE_REGISTER(cirrus_cs35l45, CONFIG_AUDIO_CODEC_LOG_LEVEL); + +union cs35l45_bus { +#if DT_ANY_INST_ON_BUS_STATUS_OKAY(i2c) + struct i2c_dt_spec i2c; +#endif /* DT_ANY_INST_ON_BUS_STATUS_OKAY */ +}; + +typedef bool (*cs35l45_bus_is_ready_fn)(const union cs35l45_bus *bus); + +struct cs35l45_config { + struct gpio_dt_spec reset_gpio; + const struct device *vdd_batt; + const struct device *vdd_a; + const union cs35l45_bus bus; + cs35l45_bus_is_ready_fn bus_is_ready; +}; + +#if DT_ANY_INST_ON_BUS_STATUS_OKAY(i2c) +static int cs35l45_reg_read(const struct device *dev, uint32_t reg_addr, uint32_t *val) +{ + uint8_t read_buf[sizeof(uint32_t)], write_buf[sizeof(uint32_t)]; + const struct cs35l45_config *config = dev->config; + int ret; + + sys_put_be32(reg_addr, write_buf); + + ret = i2c_write_read_dt(&config->bus.i2c, write_buf, sizeof(uint32_t), read_buf, + sizeof(uint32_t)); + if (ret < 0) { + return ret; + } + + *val = sys_get_be32(read_buf); + + return 0; +} + +static int cs35l45_reg_write(const struct device *dev, uint32_t reg_addr, uint32_t val) +{ + const struct cs35l45_config *config = dev->config; + uint64_t msg = ((uint64_t)reg_addr << 32) | val; + uint8_t buf[sizeof(uint64_t)]; + + sys_put_be64(msg, buf); + + return i2c_write_dt(&config->bus.i2c, buf, sizeof(uint64_t)); +} + +static bool cs35l45_bus_is_ready_i2c(const union cs35l45_bus *bus) +{ + return device_is_ready(bus->i2c.bus); +} +#endif /* DT_ANY_INST_ON_BUS_STATUS_OKAY */ + +static int cs35l45_reg_update(const struct device *dev, uint32_t reg_addr, uint32_t mask, + uint32_t val) +{ + uint32_t tmp, orig; + int ret; + + ret = cs35l45_reg_read(dev, reg_addr, &orig); + if (ret < 0) { + return ret; + } + + tmp = orig & ~mask; + tmp |= val & mask; + + return cs35l45_reg_write(dev, reg_addr, tmp); +} + +static int cs35l45_route_input(const struct device *dev, audio_channel_t channel, uint32_t input) +{ + uint32_t val; + int ret; + + switch (input) { + case CS35L45_DACPCM1_SRC_ASP_RX1: + val = CS35L45_ASP_RX1_EN; + break; + case CS35L45_DACPCM1_SRC_ASP_RX2: + val = CS35L45_ASP_RX2_EN; + break; + default: + return -EINVAL; + } + + ret = cs35l45_reg_update(dev, CS35L45_ASP_ENABLES1, val, val); + if (ret < 0) { + return ret; + } + + ret = cs35l45_reg_update(dev, CS35L45_BLOCK_ENABLES2, CS35L45_ASP_EN, CS35L45_ASP_EN); + if (ret < 0) { + return ret; + } + + return cs35l45_reg_update(dev, CS35L45_DACPCM1_INPUT, CS35L45_DACPCM1_SRC_MASK, input); +} + +static int cs35l45_apply_properties(const struct device *dev) +{ + return 0; +} + +static int cs35l45_set_pcm_volume(const struct device *dev, int vol) +{ + if (!IN_RANGE(vol, CS35L45_AMP_VOL_PCM_MIN, CS35L45_AMP_VOL_PCM_MAX)) { + return -EINVAL; + } + + return cs35l45_reg_update(dev, CS35L45_AMP_PCM_CONTROL, CS35L45_AMP_VOL_PCM_MASK, vol); +} + +static int cs35l45_set_mute(const struct device *dev, const bool mute) +{ + uint32_t val, hpf_tune; + uint8_t global_fs; + int ret; + + if (!mute) { + ret = cs35l45_reg_read(dev, CS35L45_GLOBAL_SAMPLE_RATE, &val); + if (ret < 0) { + return ret; + } + + global_fs = FIELD_GET(CS35L45_GLOBAL_FS_MASK, val); + + switch (global_fs) { + case CS35L45_GLOBAL_FS_44P1K: + hpf_tune = CS35L45_HPF_44P1; + break; + default: + hpf_tune = CS35L45_HPF_DEFAULT; + break; + } + + ret = cs35l45_reg_read(dev, CS35L45_AMP_PCM_HPF_TST, &val); + if (ret < 0) { + return ret; + } + + if (val != hpf_tune) { + struct reg_sequence hpf_override_seq[] = { + {0x00000040, 0x00000055}, + {0x00000040, 0x000000AA}, + {0x00000044, 0x00000055}, + {0x00000044, 0x000000AA}, + {CS35L45_AMP_PCM_HPF_TST, hpf_tune}, + {0x00000040, 0x00000000}, + {0x00000044, 0x00000000}, + }; + + for (int i = 0; i < ARRAY_SIZE(hpf_override_seq); i++) { + ret = cs35l45_reg_write(dev, hpf_override_seq[i].reg, + hpf_override_seq[i].def); + if (ret < 0) { + return ret; + } + } + } + } + + return cs35l45_reg_update(dev, CS35L45_AMP_OUTPUT_MUTE, CS35L45_AMP_MUTE, + FIELD_PREP(CS35L45_AMP_MUTE, mute)); +} + +static int cs35l45_set_property(const struct device *dev, audio_property_t property, + audio_channel_t channel, audio_property_value_t val) +{ + switch (property) { + case AUDIO_PROPERTY_INPUT_MUTE: + return cs35l45_set_mute(dev, val.mute); + case AUDIO_PROPERTY_INPUT_VOLUME: + return cs35l45_set_pcm_volume(dev, val.vol); + default: + return -EINVAL; + } +} + +static int cs35l45_global_en_event(const struct device *dev, const bool enable) +{ + int ret; + + if (enable) { + ret = cs35l45_reg_write(dev, CS35L45_GLOBAL_ENABLES, CS35L45_GLOBAL_EN_MASK); + if (ret < 0) { + return ret; + } + + k_usleep(CS35L45_POST_GLOBAL_EN_US); + } else { + k_usleep(CS35L45_PRE_GLOBAL_DIS_US); + ret = cs35l45_reg_write(dev, CS35L45_GLOBAL_ENABLES, 0); + if (ret < 0) { + return ret; + } + } + + return 0; +} + +static void cs35l45_stop_output(const struct device *dev) +{ + cs35l45_global_en_event(dev, false); +} + +static void cs35l45_start_output(const struct device *dev) +{ + cs35l45_global_en_event(dev, true); +} + +static int cs35l45_get_clk_freq_id(const uint32_t freq) +{ + int i; + + if (freq == 0) { + return -EINVAL; + } + + for (i = 0; i < ARRAY_SIZE(cs35l45_pll_refclk_freq); ++i) { + if (cs35l45_pll_refclk_freq[i].freq == freq) { + return cs35l45_pll_refclk_freq[i].cfg_id; + } + } + + return -EINVAL; +} + +static int cs35l45_set_pll(const struct device *dev, const uint32_t freq) +{ + uint8_t freq_id; + uint32_t val; + int ret; + + freq_id = cs35l45_get_clk_freq_id(freq); + if (freq_id < 0) { + LOG_DBG("Invalid freq: %u", freq); + return -EINVAL; + } + + ret = cs35l45_reg_read(dev, CS35L45_REFCLK_INPUT, &val); + if (ret < 0) { + return ret; + } + + val = FIELD_GET(CS35L45_PLL_REFCLK_FREQ_MASK, val); + if (val == freq_id) { + return 0; + } + + ret = cs35l45_reg_update(dev, CS35L45_REFCLK_INPUT, CS35L45_PLL_OPEN_LOOP_MASK, + CS35L45_PLL_OPEN_LOOP_MASK); + if (ret < 0) { + return ret; + } + + ret = cs35l45_reg_update(dev, CS35L45_REFCLK_INPUT, CS35L45_PLL_REFCLK_FREQ_MASK, + FIELD_PREP(CS35L45_PLL_REFCLK_FREQ_MASK, freq_id)); + if (ret < 0) { + return ret; + } + + ret = cs35l45_reg_update(dev, CS35L45_REFCLK_INPUT, CS35L45_PLL_REFCLK_EN_MASK, 0); + if (ret < 0) { + return ret; + } + + ret = cs35l45_reg_update(dev, CS35L45_REFCLK_INPUT, CS35L45_PLL_OPEN_LOOP_MASK, 0); + if (ret < 0) { + return ret; + } + + return cs35l45_reg_update(dev, CS35L45_REFCLK_INPUT, CS35L45_PLL_REFCLK_EN_MASK, + CS35L45_PLL_REFCLK_EN_MASK); +} + +static int cs35l45_set_frame_clock(const struct device *dev, const uint32_t freq) +{ + uint8_t global_fs; + + switch (freq) { + case AUDIO_PCM_RATE_44P1K: + global_fs = CS35L45_GLOBAL_FS_44P1K; + break; + case AUDIO_PCM_RATE_48K: + global_fs = CS35L45_GLOBAL_FS_48K; + break; + case AUDIO_PCM_RATE_96K: + global_fs = CS35L45_GLOBAL_FS_96K; + break; + default: + LOG_DBG("Unsupported frame clock frequency: %d Hz", freq); + return -EINVAL; + } + + return cs35l45_reg_update(dev, CS35L45_GLOBAL_SAMPLE_RATE, CS35L45_GLOBAL_FS_MASK, + global_fs); +} + +static int cs35l45_configure_asp_fmt(const struct device *dev, const i2s_fmt_t i2s_fmt, + const i2s_opt_t i2s_opt) +{ + uint8_t asp_fmt; + uint32_t val; + + if ((i2s_opt & (I2S_OPT_BIT_CLK_SLAVE | I2S_OPT_FRAME_CLK_SLAVE)) == 0U) { + LOG_DBG("Invalid DAI clocking"); + return -EINVAL; + } + + switch (FIELD_GET(I2S_FMT_DATA_FORMAT_MASK, i2s_fmt)) { + case I2S_FMT_DATA_FORMAT_I2S: + asp_fmt = CS35L45_ASP_FMT_I2S; + break; + case I2S_FMT_DATA_FORMAT_PCM_SHORT: + asp_fmt = CS35L45_ASP_FMT_TDM_1_5; + break; + case I2S_FMT_DATA_FORMAT_PCM_LONG: + asp_fmt = CS35L45_ASP_FMT_DSP_A; + break; + default: + LOG_DBG("Invalid DAI format"); + return -EINVAL; + } + + val = FIELD_PREP(CS35L45_ASP_FMT_MASK, asp_fmt); + + switch (FIELD_GET(I2S_FMT_CLK_FORMAT_SHIFT, i2s_fmt)) { + case I2S_FMT_CLK_NF_NB: + break; + case I2S_FMT_CLK_NF_IB: + val |= CS35L45_ASP_BCLK_INV; + break; + case I2S_FMT_CLK_IF_NB: + val |= CS35L45_ASP_FSYNC_INV; + break; + case I2S_FMT_CLK_IF_IB: + val |= (CS35L45_ASP_FSYNC_INV | CS35L45_ASP_BCLK_INV); + break; + default: + LOG_DBG("Invalid DAI clock polarity"); + } + + return cs35l45_reg_update( + dev, CS35L45_ASP_CONTROL2, + (CS35L45_ASP_FMT_MASK | CS35L45_ASP_BCLK_INV | CS35L45_ASP_FSYNC_INV), val); +} + +static int cs35l45_configure_asp_word(const struct device *dev, const uint8_t word_size, + const uint8_t channels, const audio_route_t dai_route) +{ + uint8_t asp_width; + int ret; + + if (!IN_RANGE(word_size, CS35L45_ASP_WL_MIN, CS35L45_ASP_WL_MAX)) { + return -EINVAL; + } + + asp_width = word_size * channels; + + if (!IN_RANGE(asp_width, CS35L45_ASP_WIDTH_MIN, CS35L45_ASP_WIDTH_MAX)) { + return -EINVAL; + } + + if (dai_route == AUDIO_ROUTE_PLAYBACK) { + ret = cs35l45_reg_update(dev, CS35L45_ASP_CONTROL2, CS35L45_ASP_WIDTH_RX_MASK, + FIELD_PREP(CS35L45_ASP_WIDTH_RX_MASK, asp_width)); + if (ret < 0) { + return ret; + } + + return cs35l45_reg_update(dev, CS35L45_ASP_DATA_CONTROL5, CS35L45_ASP_WL_MASK, + word_size); + + } else if (dai_route == AUDIO_ROUTE_CAPTURE) { + ret = cs35l45_reg_update(dev, CS35L45_ASP_CONTROL2, CS35L45_ASP_WIDTH_TX_MASK, + FIELD_PREP(CS35L45_ASP_WIDTH_TX_MASK, asp_width)); + if (ret < 0) { + return ret; + } + + return cs35l45_reg_update(dev, CS35L45_ASP_DATA_CONTROL1, CS35L45_ASP_WL_MASK, + asp_width); + + } else { + return -EINVAL; + } +} + +static int cs35l45_configure(const struct device *dev, struct audio_codec_cfg *cfg) +{ + uint32_t bclk_freq; + int ret; + + ret = cs35l45_set_frame_clock(dev, cfg->dai_cfg.i2s.frame_clk_freq); + if (ret < 0) { + return ret; + } + + ret = cs35l45_configure_asp_word(dev, cfg->dai_cfg.i2s.word_size, cfg->dai_cfg.i2s.channels, + cfg->dai_route); + if (ret < 0) { + return ret; + } + + ret = cs35l45_configure_asp_fmt(dev, cfg->dai_cfg.i2s.format, cfg->dai_cfg.i2s.options); + if (ret < 0) { + return ret; + } + + if (cfg->dai_cfg.i2s.format == I2S_FMT_DATA_FORMAT_I2S) { + bclk_freq = cfg->dai_cfg.i2s.frame_clk_freq * cfg->dai_cfg.i2s.word_size * + I2S_FMT_I2S_CHANNELS * 2; + } else { + bclk_freq = cfg->dai_cfg.i2s.frame_clk_freq * cfg->dai_cfg.i2s.word_size * + cfg->dai_cfg.i2s.channels; + } + + return cs35l45_set_pll(dev, bclk_freq); +} + +static int cs35l45_apply_patch(const struct device *dev) +{ + int ret; + + for (int i = 0; i < ARRAY_SIZE(cs35l45_patch); i++) { + ret = cs35l45_reg_write(dev, cs35l45_patch[i].reg, cs35l45_patch[i].def); + if (ret < 0) { + return ret; + } + } + + return 0; +} + +static int cs35l45_hw_init(const struct device *dev) +{ + uint32_t val = 0; + int ret, i = 5; + + while (!(val & CS35L45_OTP_BOOT_DONE_STS_MASK)) { + k_usleep(1000); + cs35l45_reg_read(dev, CS35L45_IRQ1_EINT_4, &val); + i--; + if (i < 0) { + return -ETIMEDOUT; + } + } + + ret = cs35l45_reg_read(dev, CS35L45_DEVID, &val); + if (ret < 0) { + return ret; + } + + if (val == 0x35A450) { + LOG_INF("Found DEVID:0x%x", val); + } else { + LOG_DBG("Bad DEVID 0x%x", val); + return -ENODEV; + } + + ret = cs35l45_reg_write(dev, CS35L45_IRQ1_EINT_4, + (CS35L45_OTP_BOOT_DONE_STS_MASK | CS35L45_OTP_BUSY_MASK)); + if (ret < 0) { + return ret; + } + + ret = cs35l45_apply_patch(dev); + if (ret < 0) { + return ret; + } + + return cs35l45_reg_update(dev, CS35L45_BLOCK_ENABLES, CS35L45_BST_EN_MASK, + FIELD_PREP(CS35L45_BST_DISABLE_FET_ON, CS35L45_BST_EN_MASK)); +} + +static int cs35l45_init(const struct device *dev) +{ + const struct cs35l45_config *config = dev->config; + int ret; + + if (!config->bus_is_ready) { + return -ENODEV; + } + + if (config->vdd_batt != NULL) { + ret = regulator_enable(config->vdd_batt); + if (ret < 0) { + return ret; + } + } + + if (config->vdd_a != NULL) { + ret = regulator_enable(config->vdd_a); + if (ret < 0) { + return ret; + } + } + + if (!gpio_is_ready_dt(&config->reset_gpio)) { + return -ENODEV; + } + + ret = gpio_pin_configure_dt(&config->reset_gpio, GPIO_OUTPUT_ACTIVE); + if (ret == -EBUSY) { + LOG_DBG("Reset line is busy, assuming shared reset"); + } else if (ret < 0) { + return ret; + } + + k_usleep(CS35L45_T_RLPW_US); + gpio_pin_set_dt(&config->reset_gpio, 0); + k_usleep(CS35L45_T_IRS_US); + + return cs35l45_hw_init(dev); +} + +static const struct audio_codec_api api = { + .configure = cs35l45_configure, + .start_output = cs35l45_start_output, + .stop_output = cs35l45_stop_output, + .set_property = cs35l45_set_property, + .apply_properties = cs35l45_apply_properties, + .route_input = cs35l45_route_input, +}; + +#define CS35L45_DEVICE_INIT(inst) \ + DEVICE_DT_INST_DEFINE(inst, cs35l45_init, NULL, NULL, &cs35l45_config_##inst, POST_KERNEL, \ + CONFIG_AUDIO_CODEC_INIT_PRIORITY, &api); + +#define CS35L45_CONFIG(inst) \ + .vdd_batt = DEVICE_DT_GET_OR_NULL(DT_NODELABEL(vdd_batt)), \ + .vdd_a = DEVICE_DT_GET_OR_NULL(DT_NODELABEL(vdd_a)), \ + .reset_gpio = GPIO_DT_SPEC_INST_GET(inst, reset_gpios), + +#define CS35L45_CONFIG_I2C(inst) \ + {.bus = {.i2c = I2C_DT_SPEC_INST_GET(inst)}, \ + .bus_is_ready = cs35l45_bus_is_ready_i2c, \ + CS35L45_CONFIG(inst)} + +#define CS35L45_DEFINE_I2C(inst) \ + static struct cs35l45_config cs35l45_config_##inst = CS35L45_CONFIG_I2C(inst); \ + CS35L45_DEVICE_INIT(inst) + +#define AUDIO_CODEC_CS35L45_DEFINE(inst) CS35L45_DEFINE_I2C(inst) + +DT_INST_FOREACH_STATUS_OKAY(AUDIO_CODEC_CS35L45_DEFINE) diff --git a/drivers/audio/cs35l45.h b/drivers/audio/cs35l45.h new file mode 100644 index 00000000000000..a8dbb84ea56e82 --- /dev/null +++ b/drivers/audio/cs35l45.h @@ -0,0 +1,159 @@ +/* + * Copyright 2024 Cirrus Logic, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_AUDIO_CS35L45_H_ +#define ZEPHYR_DRIVERS_AUDIO_CS35L45_H_ + +#include +#include +#include +#include +#include + +#define CS35L45_DEVID 0x00000000 +#define CS35L45_REVID 0x00000004 +#define CS35L45_RELID 0x0000000C +#define CS35L45_OTPID 0x00000010 +#define CS35L45_SFT_RESET 0x00000020 + +#define CS35L45_GLOBAL_ENABLES 0x00002014 +#define CS35L45_GLOBAL_EN_MASK BIT(0) +#define CS35L45_POST_GLOBAL_EN_US 5000 +#define CS35L45_PRE_GLOBAL_DIS_US 3000 + +#define CS35L45_BLOCK_ENABLES 0x00002018 +#define CS35L45_BST_EN_MASK GENMASK(5, 4) +#define CS35L45_BST_DISABLE_FET_ON 0x1 + +#define CS35L45_BLOCK_ENABLES2 0x0000201C +#define CS35L45_ASP_EN BIT(27) + +#define CS35L45_ERROR_RELEASE 0x00002034 +#define CS35L45_GLOBAL_ERR_RLS BIT(11) + +#define CS35L45_REFCLK_INPUT 0x00002C04 +#define CS35L45_PLL_OPEN_LOOP_MASK BIT(11) +#define CS35L45_PLL_REFCLK_FREQ_MASK GENMASK(10, 5) +#define CS35L45_PLL_REFCLK_EN_MASK BIT(4) + +#define CS35L45_GLOBAL_SAMPLE_RATE 0x00002C0C +#define CS35L45_GLOBAL_FS_MASK GENMASK(4, 0) +#define CS35L45_GLOBAL_FS_44P1K 0xB +#define CS35L45_GLOBAL_FS_48K 0x3 +#define CS35L45_GLOBAL_FS_96K 0x4 + +#define CS35L45_BOOST_CCM_CFG 0x00003808 +#define CS35L45_BOOST_DCM_CFG 0x0000380C +#define CS35L45_BOOST_OV_CFG 0x0000382C + +#define CS35L45_ASP_ENABLES1 0x00004800 +#define CS35L45_ASP_RX2_EN BIT(17) +#define CS35L45_ASP_RX1_EN BIT(16) + +#define CS35L45_ASP_CONTROL1 0x00004804 + +#define CS35L45_ASP_CONTROL2 0x00004808 +#define CS35L45_ASP_WIDTH_RX_MASK GENMASK(31, 24) +#define CS35L45_ASP_WIDTH_TX_MASK GENMASK(23, 16) +#define CS35L45_ASP_WIDTH_MIN 12 +#define CS35L45_ASP_WIDTH_MAX 128 +#define CS35L45_ASP_FMT_MASK GENMASK(10, 8) +#define CS35L45_ASP_FMT_DSP_A 0 +#define CS35L45_ASP_FMT_I2S 2 +#define CS35L45_ASP_FMT_TDM_1_5 4 +#define CS35L45_ASP_BCLK_INV BIT(6) +#define CS35L45_ASP_FSYNC_INV BIT(2) + +#define CS35L45_ASP_CONTROL3 0x0000480C +#define CS35L45_ASP_FRAME_CONTROL1 0x00004810 +#define CS35L45_ASP_FRAME_CONTROL2 0x00004814 +#define CS35L45_ASP_FRAME_CONTROL5 0x00004820 +#define CS35L45_ASP_DATA_CONTROL1 0x00004830 + +#define CS35L45_ASP_DATA_CONTROL5 0x00004840 +#define CS35L45_ASP_WL_MASK GENMASK(5, 0) +#define CS35L45_ASP_WL_MIN 12 +#define CS35L45_ASP_WL_MAX 24 + +#define CS35L45_DACPCM1_INPUT 0x00004C00 +#define CS35L45_DACPCM1_SRC_MASK GENMASK(6, 0) +#define CS35L45_DACPCM1_SRC_ZERO_FILL 0x0 +#define CS35L45_DACPCM1_SRC_ASP_RX1 0x8 +#define CS35L45_DACPCM1_SRC_ASP_RX2 0x9 +#define CS35L45_DACPCM1_SRC_DSP_TX1 0x32 +#define CS35L45_DACPCM1_SRC_DSP_TX2 0x33 + +#define CS35L45_ASPTX1_INPUT 0x00004C20 +#define CS35L45_ASPTX2_INPUT 0x00004C24 +#define CS35L45_ASPTX3_INPUT 0x00004C28 +#define CS35L45_ASPTX4_INPUT 0x00004C2C +#define CS35L45_ASPTX5_INPUT 0x00004C30 +#define CS35L45_LDPM_CONFIG 0x00006404 + +#define CS35L45_AMP_PCM_CONTROL 0x00007000 +#define CS35L45_AMP_VOL_PCM_MIN -816 +#define CS35L45_AMP_VOL_PCM_MAX 96 +#define CS35L45_AMP_VOL_PCM_MASK GENMASK(10, 0) + +#define CS35L45_AMP_PCM_HPF_TST 0x00007004 + +#define CS35L45_AMP_OUTPUT_MUTE 0x00007C04 +#define CS35L45_AMP_MUTE BIT(0) + +#define CS35L45_HPF_44P1 0x000108BD +#define CS35L45_HPF_88P2 0x0001045F +#define CS35L45_HPF_DEFAULT 0x0 + +#define CS35L45_IRQ1_EINT_4 0x0000E01C +#define CS35L45_OTP_BOOT_DONE_STS_MASK BIT(1) +#define CS35L45_OTP_BUSY_MASK BIT(0) + +#define CS35L45_T_RLPW_US 1000 +#define CS35L45_T_IRS_US 1100 + +#define I2S_FMT_I2S_CHANNELS 2 + +static const struct { + uint8_t cfg_id; + uint32_t freq; +} cs35l45_pll_refclk_freq[] = { + {0x0C, 128000}, {0x0F, 256000}, {0x11, 384000}, {0x12, 512000}, {0x15, 768000}, + {0x17, 1024000}, {0x19, 1411200}, {0x1B, 1536000}, {0x1C, 2116800}, {0x1D, 2048000}, + {0x1E, 2304000}, {0x1F, 2822400}, {0x21, 3072000}, {0x23, 4233600}, {0x24, 4096000}, + {0x25, 4608000}, {0x26, 5644800}, {0x27, 6000000}, {0x28, 6144000}, {0x29, 6350400}, + {0x2A, 6912000}, {0x2D, 7526400}, {0x2E, 8467200}, {0x2F, 8192000}, {0x30, 9216000}, + {0x31, 11289600}, {0x33, 12288000}, {0x37, 16934400}, {0x38, 18432000}, {0x39, 22579200}, + {0x3B, 24576000}, +}; + +struct reg_sequence { + uint32_t reg; + uint32_t def; +}; + +static const struct reg_sequence cs35l45_patch[] = { + {0x00000040, 0x00000055}, + {0x00000040, 0x000000AA}, + {0x00000044, 0x00000055}, + {0x00000044, 0x000000AA}, + {0x00006480, 0x0830500A}, + {0x00007C60, 0x1000850B}, + {CS35L45_BOOST_OV_CFG, 0x007000D0}, + {CS35L45_LDPM_CONFIG, 0x0001B636}, + {0x00002C08, 0x00000009}, + {0x00006850, 0x0A30FFC4}, + {0x00003820, 0x00040100}, + {0x00003824, 0x00000000}, + {0x00007CFC, 0x62870004}, + {0x00007C60, 0x1001850B}, + {0x00000040, 0x00000000}, + {0x00000044, 0x00000000}, + {CS35L45_BOOST_CCM_CFG, 0xF0000003}, + {CS35L45_BOOST_DCM_CFG, 0x08710220}, + {CS35L45_ERROR_RELEASE, 0x00200000}, +}; + +#endif