Merge tag 'asoc-v5.19' of https://git.kernel.org/pub/scm/linux/kernel/git/broonie...
[linux-2.6-microblaze.git] / sound / soc / codecs / cs35l41-lib.c
index de022a5..6d3070e 100644 (file)
@@ -12,6 +12,7 @@
 #include <linux/regmap.h>
 #include <linux/regulator/consumer.h>
 #include <linux/slab.h>
+#include <linux/firmware/cirrus/wmfw.h>
 
 #include <sound/cs35l41.h>
 
@@ -667,6 +668,25 @@ static const struct reg_sequence cs35l41_revb2_errata_patch[] = {
        { CS35L41_AMP_GAIN_CTRL,         0x00000000 },
 };
 
+static const struct reg_sequence cs35l41_fs_errata_patch[] = {
+       { CS35L41_DSP1_RX1_RATE,        0x00000001 },
+       { CS35L41_DSP1_RX2_RATE,        0x00000001 },
+       { CS35L41_DSP1_RX3_RATE,        0x00000001 },
+       { CS35L41_DSP1_RX4_RATE,        0x00000001 },
+       { CS35L41_DSP1_RX5_RATE,        0x00000001 },
+       { CS35L41_DSP1_RX6_RATE,        0x00000001 },
+       { CS35L41_DSP1_RX7_RATE,        0x00000001 },
+       { CS35L41_DSP1_RX8_RATE,        0x00000001 },
+       { CS35L41_DSP1_TX1_RATE,        0x00000001 },
+       { CS35L41_DSP1_TX2_RATE,        0x00000001 },
+       { CS35L41_DSP1_TX3_RATE,        0x00000001 },
+       { CS35L41_DSP1_TX4_RATE,        0x00000001 },
+       { CS35L41_DSP1_TX5_RATE,        0x00000001 },
+       { CS35L41_DSP1_TX6_RATE,        0x00000001 },
+       { CS35L41_DSP1_TX7_RATE,        0x00000001 },
+       { CS35L41_DSP1_TX8_RATE,        0x00000001 },
+};
+
 static const struct cs35l41_otp_map_element_t cs35l41_otp_map_map[] = {
        {
                .id = 0x01,
@@ -956,9 +976,8 @@ static const unsigned char cs35l41_bst_slope_table[4] = {
        0x75, 0x6B, 0x3B, 0x28
 };
 
-
-int cs35l41_boost_config(struct device *dev, struct regmap *regmap, int boost_ind, int boost_cap,
-                        int boost_ipk)
+static int cs35l41_boost_config(struct device *dev, struct regmap *regmap, int boost_ind,
+                               int boost_cap, int boost_ipk)
 {
        unsigned char bst_lbst_val, bst_cbst_range, bst_ipk_scaled;
        int ret;
@@ -994,10 +1013,20 @@ int cs35l41_boost_config(struct device *dev, struct regmap *regmap, int boost_in
        case 101 ... 200:
                bst_cbst_range = 3;
                break;
-       default:        /* 201 uF and greater */
+       default:
+               if (boost_cap < 0) {
+                       dev_err(dev, "Invalid boost capacitor value: %d nH\n", boost_cap);
+                       return -EINVAL;
+               }
+               /* 201 uF and greater */
                bst_cbst_range = 4;
        }
 
+       if (boost_ipk < 1600 || boost_ipk > 4500) {
+               dev_err(dev, "Invalid boost inductor peak current: %d mA\n", boost_ipk);
+               return -EINVAL;
+       }
+
        ret = regmap_update_bits(regmap, CS35L41_BSTCVRT_COEFF,
                                 CS35L41_BST_K1_MASK | CS35L41_BST_K2_MASK,
                                 cs35l41_bst_k1_table[bst_lbst_val][bst_cbst_range]
@@ -1019,10 +1048,6 @@ int cs35l41_boost_config(struct device *dev, struct regmap *regmap, int boost_in
                return ret;
        }
 
-       if (boost_ipk < 1600 || boost_ipk > 4500) {
-               dev_err(dev, "Invalid boost inductor peak current: %d mA\n", boost_ipk);
-               return -EINVAL;
-       }
        bst_ipk_scaled = ((boost_ipk - 1600) / 50) + 0x10;
 
        ret = regmap_update_bits(regmap, CS35L41_BSTCVRT_PEAK_CUR, CS35L41_BST_IPK_MASK,
@@ -1032,9 +1057,269 @@ int cs35l41_boost_config(struct device *dev, struct regmap *regmap, int boost_in
                return ret;
        }
 
+       regmap_update_bits(regmap, CS35L41_PWR_CTRL2, CS35L41_BST_EN_MASK,
+                          CS35L41_BST_EN_DEFAULT << CS35L41_BST_EN_SHIFT);
+
        return 0;
 }
-EXPORT_SYMBOL_GPL(cs35l41_boost_config);
+
+static const struct reg_sequence cs35l41_safe_to_reset[] = {
+       { 0x00000040,                   0x00000055 },
+       { 0x00000040,                   0x000000AA },
+       { 0x0000393C,                   0x000000C0, 6000},
+       { 0x0000393C,                   0x00000000 },
+       { 0x00007414,                   0x00C82222 },
+       { 0x0000742C,                   0x00000000 },
+       { 0x00000040,                   0x000000CC },
+       { 0x00000040,                   0x00000033 },
+};
+
+static const struct reg_sequence cs35l41_active_to_safe[] = {
+       { 0x00000040,                   0x00000055 },
+       { 0x00000040,                   0x000000AA },
+       { 0x00007438,                   0x00585941 },
+       { CS35L41_PWR_CTRL1,            0x00000000 },
+       { 0x0000742C,                   0x00000009, 3000 },
+       { 0x00007438,                   0x00580941 },
+       { 0x00000040,                   0x000000CC },
+       { 0x00000040,                   0x00000033 },
+};
+
+static const struct reg_sequence cs35l41_safe_to_active[] = {
+       { 0x00000040,                   0x00000055 },
+       { 0x00000040,                   0x000000AA },
+       { 0x0000742C,                   0x0000000F },
+       { 0x0000742C,                   0x00000079 },
+       { 0x00007438,                   0x00585941 },
+       { CS35L41_PWR_CTRL1,            0x00000001, 3000 }, // GLOBAL_EN = 1
+       { 0x0000742C,                   0x000000F9 },
+       { 0x00007438,                   0x00580941 },
+       { 0x00000040,                   0x000000CC },
+       { 0x00000040,                   0x00000033 },
+};
+
+static const struct reg_sequence cs35l41_reset_to_safe[] = {
+       { 0x00000040,                   0x00000055 },
+       { 0x00000040,                   0x000000AA },
+       { 0x00007438,                   0x00585941 },
+       { 0x00007414,                   0x08C82222 },
+       { 0x0000742C,                   0x00000009 },
+       { 0x00000040,                   0x000000CC },
+       { 0x00000040,                   0x00000033 },
+};
+
+int cs35l41_init_boost(struct device *dev, struct regmap *regmap,
+                      struct cs35l41_hw_cfg *hw_cfg)
+{
+       int ret;
+
+       switch (hw_cfg->bst_type) {
+       case CS35L41_INT_BOOST:
+               ret = cs35l41_boost_config(dev, regmap, hw_cfg->bst_ind,
+                                          hw_cfg->bst_cap, hw_cfg->bst_ipk);
+               if (ret)
+                       dev_err(dev, "Error in Boost DT config: %d\n", ret);
+               break;
+       case CS35L41_EXT_BOOST:
+       case CS35L41_EXT_BOOST_NO_VSPK_SWITCH:
+               /* Only CLSA0100 doesn't use GPIO as VSPK switch, but even on that laptop we can
+                * toggle GPIO1 as is not connected to anything.
+                * There will be no other device without VSPK switch.
+                */
+               regmap_write(regmap, CS35L41_GPIO1_CTRL1, 0x00000001);
+               regmap_multi_reg_write(regmap, cs35l41_reset_to_safe,
+                                      ARRAY_SIZE(cs35l41_reset_to_safe));
+               ret = regmap_update_bits(regmap, CS35L41_PWR_CTRL2, CS35L41_BST_EN_MASK,
+                                        CS35L41_BST_DIS_FET_OFF << CS35L41_BST_EN_SHIFT);
+               break;
+       default:
+               dev_err(dev, "Boost type %d not supported\n", hw_cfg->bst_type);
+               ret = -EINVAL;
+               break;
+       }
+
+       return ret;
+}
+EXPORT_SYMBOL_GPL(cs35l41_init_boost);
+
+bool cs35l41_safe_reset(struct regmap *regmap, enum cs35l41_boost_type b_type)
+{
+       switch (b_type) {
+       /* There is only one laptop that doesn't have VSPK switch. */
+       case CS35L41_EXT_BOOST_NO_VSPK_SWITCH:
+               return false;
+       case CS35L41_EXT_BOOST:
+               regmap_write(regmap, CS35L41_GPIO1_CTRL1, 0x00000001);
+               regmap_multi_reg_write(regmap, cs35l41_safe_to_reset,
+                                      ARRAY_SIZE(cs35l41_safe_to_reset));
+               return true;
+       default:
+               return true;
+       }
+}
+EXPORT_SYMBOL_GPL(cs35l41_safe_reset);
+
+int cs35l41_global_enable(struct regmap *regmap, enum cs35l41_boost_type b_type, int enable)
+{
+       int ret;
+
+       switch (b_type) {
+       case CS35L41_INT_BOOST:
+               ret = regmap_update_bits(regmap, CS35L41_PWR_CTRL1, CS35L41_GLOBAL_EN_MASK,
+                                        enable << CS35L41_GLOBAL_EN_SHIFT);
+               usleep_range(3000, 3100);
+               break;
+       case CS35L41_EXT_BOOST:
+       case CS35L41_EXT_BOOST_NO_VSPK_SWITCH:
+               if (enable)
+                       ret = regmap_multi_reg_write(regmap, cs35l41_safe_to_active,
+                                                    ARRAY_SIZE(cs35l41_safe_to_active));
+               else
+                       ret = regmap_multi_reg_write(regmap, cs35l41_active_to_safe,
+                                                    ARRAY_SIZE(cs35l41_active_to_safe));
+               break;
+       default:
+               ret = -EINVAL;
+               break;
+       }
+
+       return ret;
+}
+EXPORT_SYMBOL_GPL(cs35l41_global_enable);
+
+int cs35l41_gpio_config(struct regmap *regmap, struct cs35l41_hw_cfg *hw_cfg)
+{
+       struct cs35l41_gpio_cfg *gpio1 = &hw_cfg->gpio1;
+       struct cs35l41_gpio_cfg *gpio2 = &hw_cfg->gpio2;
+       int irq_pol = IRQF_TRIGGER_NONE;
+
+       regmap_update_bits(regmap, CS35L41_GPIO1_CTRL1,
+                          CS35L41_GPIO_POL_MASK | CS35L41_GPIO_DIR_MASK,
+                          gpio1->pol_inv << CS35L41_GPIO_POL_SHIFT |
+                          !gpio1->out_en << CS35L41_GPIO_DIR_SHIFT);
+
+       regmap_update_bits(regmap, CS35L41_GPIO2_CTRL1,
+                          CS35L41_GPIO_POL_MASK | CS35L41_GPIO_DIR_MASK,
+                          gpio2->pol_inv << CS35L41_GPIO_POL_SHIFT |
+                          !gpio2->out_en << CS35L41_GPIO_DIR_SHIFT);
+
+       if (gpio1->valid)
+               regmap_update_bits(regmap, CS35L41_GPIO_PAD_CONTROL, CS35L41_GPIO1_CTRL_MASK,
+                                  gpio1->func << CS35L41_GPIO1_CTRL_SHIFT);
+
+       if (gpio2->valid) {
+               regmap_update_bits(regmap, CS35L41_GPIO_PAD_CONTROL, CS35L41_GPIO2_CTRL_MASK,
+                                  gpio2->func << CS35L41_GPIO2_CTRL_SHIFT);
+
+               switch (gpio2->func) {
+               case CS35L41_GPIO2_INT_PUSH_PULL_LOW:
+               case CS35L41_GPIO2_INT_OPEN_DRAIN:
+                       irq_pol = IRQF_TRIGGER_LOW;
+                       break;
+               case CS35L41_GPIO2_INT_PUSH_PULL_HIGH:
+                       irq_pol = IRQF_TRIGGER_HIGH;
+                       break;
+               default:
+                       break;
+               }
+       }
+
+       return irq_pol;
+}
+EXPORT_SYMBOL_GPL(cs35l41_gpio_config);
+
+static const struct cs_dsp_region cs35l41_dsp1_regions[] = {
+       { .type = WMFW_HALO_PM_PACKED,  .base = CS35L41_DSP1_PMEM_0 },
+       { .type = WMFW_HALO_XM_PACKED,  .base = CS35L41_DSP1_XMEM_PACK_0 },
+       { .type = WMFW_HALO_YM_PACKED,  .base = CS35L41_DSP1_YMEM_PACK_0 },
+       {. type = WMFW_ADSP2_XM,        .base = CS35L41_DSP1_XMEM_UNPACK24_0},
+       {. type = WMFW_ADSP2_YM,        .base = CS35L41_DSP1_YMEM_UNPACK24_0},
+};
+
+void cs35l41_configure_cs_dsp(struct device *dev, struct regmap *reg, struct cs_dsp *dsp)
+{
+       dsp->num = 1;
+       dsp->type = WMFW_HALO;
+       dsp->rev = 0;
+       dsp->dev = dev;
+       dsp->regmap = reg;
+       dsp->base = CS35L41_DSP1_CTRL_BASE;
+       dsp->base_sysinfo = CS35L41_DSP1_SYS_ID;
+       dsp->mem = cs35l41_dsp1_regions;
+       dsp->num_mems = ARRAY_SIZE(cs35l41_dsp1_regions);
+       dsp->lock_regions = 0xFFFFFFFF;
+}
+EXPORT_SYMBOL_GPL(cs35l41_configure_cs_dsp);
+
+static bool cs35l41_check_cspl_mbox_sts(enum cs35l41_cspl_mbox_cmd cmd,
+                                       enum cs35l41_cspl_mbox_status sts)
+{
+       switch (cmd) {
+       case CSPL_MBOX_CMD_NONE:
+       case CSPL_MBOX_CMD_UNKNOWN_CMD:
+               return true;
+       case CSPL_MBOX_CMD_PAUSE:
+       case CSPL_MBOX_CMD_OUT_OF_HIBERNATE:
+               return (sts == CSPL_MBOX_STS_PAUSED);
+       case CSPL_MBOX_CMD_RESUME:
+               return (sts == CSPL_MBOX_STS_RUNNING);
+       case CSPL_MBOX_CMD_REINIT:
+               return (sts == CSPL_MBOX_STS_RUNNING);
+       case CSPL_MBOX_CMD_STOP_PRE_REINIT:
+               return (sts == CSPL_MBOX_STS_RDY_FOR_REINIT);
+       default:
+               return false;
+       }
+}
+
+int cs35l41_set_cspl_mbox_cmd(struct device *dev, struct regmap *regmap,
+                             enum cs35l41_cspl_mbox_cmd cmd)
+{
+       unsigned int sts = 0, i;
+       int ret;
+
+       // Set mailbox cmd
+       ret = regmap_write(regmap, CS35L41_DSP_VIRT1_MBOX_1, cmd);
+       if (ret < 0) {
+               if (cmd != CSPL_MBOX_CMD_OUT_OF_HIBERNATE)
+                       dev_err(dev, "Failed to write MBOX: %d\n", ret);
+               return ret;
+       }
+
+       // Read mailbox status and verify it is appropriate for the given cmd
+       for (i = 0; i < 5; i++) {
+               usleep_range(1000, 1100);
+
+               ret = regmap_read(regmap, CS35L41_DSP_MBOX_2, &sts);
+               if (ret < 0) {
+                       dev_err(dev, "Failed to read MBOX STS: %d\n", ret);
+                       continue;
+               }
+
+               if (!cs35l41_check_cspl_mbox_sts(cmd, sts))
+                       dev_dbg(dev, "[%u] cmd %u returned invalid sts %u", i, cmd, sts);
+               else
+                       return 0;
+       }
+
+       dev_err(dev, "Failed to set mailbox cmd %u (status %u)\n", cmd, sts);
+
+       return -ENOMSG;
+}
+EXPORT_SYMBOL_GPL(cs35l41_set_cspl_mbox_cmd);
+
+int cs35l41_write_fs_errata(struct device *dev, struct regmap *regmap)
+{
+       int ret;
+
+       ret = regmap_multi_reg_write(regmap, cs35l41_fs_errata_patch,
+                                    ARRAY_SIZE(cs35l41_fs_errata_patch));
+       if (ret < 0)
+               dev_err(dev, "Failed to write fs errata: %d\n", ret);
+
+       return ret;
+}
+EXPORT_SYMBOL_GPL(cs35l41_write_fs_errata);
 
 MODULE_DESCRIPTION("CS35L41 library");
 MODULE_AUTHOR("David Rhodes, Cirrus Logic Inc, <david.rhodes@cirrus.com>");