From d912892ccb1f7ea911db54c06c99c104d1e9278b Mon Sep 17 00:00:00 2001 From: aszlig Date: Thu, 15 Dec 2016 23:32:41 +0100 Subject: hardware/t100ha: Add patch for SST sound This is essentially a backport of @plbossart's "experimental/codecs" branch against Linux 4.9. The original repository can be found at: https://github.com/plbossart/sound/commits/experimental/codecs Thanks to him for doing a lot of work on getting sound working for these CherryTrail devices. Signed-off-by: aszlig --- modules/hardware/t100ha/default.nix | 11 +- modules/hardware/t100ha/sound.patch | 5632 +++++++++++++++++++++++++++++++++++ 2 files changed, 5640 insertions(+), 3 deletions(-) create mode 100644 modules/hardware/t100ha/sound.patch (limited to 'modules/hardware') diff --git a/modules/hardware/t100ha/default.nix b/modules/hardware/t100ha/default.nix index 00a5855d..69fd10d1 100644 --- a/modules/hardware/t100ha/default.nix +++ b/modules/hardware/t100ha/default.nix @@ -8,12 +8,14 @@ in { options.vuizvui.hardware.t100ha.enable = lib.mkEnableOption desc; config = lib.mkIf cfg.enable { - hardware.firmware = lib.singleton (pkgs.runCommand "brcm43340-firmware" { + hardware.firmware = lib.singleton (pkgs.runCommand "t100ha-firmware" { params = ./brcmfmac43340-sdio.txt; - fwbase = "lib/firmware/brcm/brcmfmac43340-sdio"; + fwpkg = pkgs.firmwareLinuxNonfree; install = "install -vD -m 0644"; } '' - $install "${pkgs.firmwareLinuxNonfree}/$fwbase.bin" "$out/$fwbase.bin" + for fw in brcm/brcmfmac43340-sdio intel/fw_sst_22a8; do + $install "$fwpkg/lib/firmware/$fw.bin" "$out/lib/firmware/$fw.bin" + done $install "$params" "$out/$fwbase.txt" ''); @@ -27,6 +29,9 @@ in { { name = "sdio"; patch = ./sdio.patch; } + { name = "sound"; + patch = ./sound.patch; + } ]; boot.kernelPackages = let diff --git a/modules/hardware/t100ha/sound.patch b/modules/hardware/t100ha/sound.patch new file mode 100644 index 00000000..2fb67f17 --- /dev/null +++ b/modules/hardware/t100ha/sound.patch @@ -0,0 +1,5632 @@ +diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig +index bada636d1065..233898ec1fbd 100644 +--- a/arch/x86/Kconfig ++++ b/arch/x86/Kconfig +@@ -2756,6 +2756,7 @@ config X86_DMA_REMAP + config PMC_ATOM + def_bool y + depends on PCI ++ select COMMON_CLK + + source "net/Kconfig" + +diff --git a/arch/x86/include/asm/pmc_atom.h b/arch/x86/include/asm/pmc_atom.h +index aa8744c77c6d..59f95168fa8d 100644 +--- a/arch/x86/include/asm/pmc_atom.h ++++ b/arch/x86/include/asm/pmc_atom.h +@@ -152,6 +152,12 @@ + #define SLEEP_TYPE_S5 0x1C00 + #define SLEEP_ENABLE 0x2000 + ++struct pmc_clk { ++ const char *name; ++ unsigned long freq; ++ const char *parent_name; ++}; ++ + extern int pmc_atom_read(int offset, u32 *value); + extern int pmc_atom_write(int offset, u32 value); + +diff --git a/arch/x86/platform/atom/pmc_atom.c b/arch/x86/platform/atom/pmc_atom.c +index 964ff4fc61f9..e2de3e234959 100644 +--- a/arch/x86/platform/atom/pmc_atom.c ++++ b/arch/x86/platform/atom/pmc_atom.c +@@ -21,6 +21,7 @@ + #include + #include + #include ++#include + + #include + +@@ -37,6 +38,11 @@ struct pmc_reg_map { + const struct pmc_bit_map *pss; + }; + ++struct pmc_data { ++ const struct pmc_reg_map *map; ++ const struct pmc_clk *clks; ++}; ++ + struct pmc_dev { + u32 base_addr; + void __iomem *regmap; +@@ -50,6 +56,29 @@ struct pmc_dev { + static struct pmc_dev pmc_device; + static u32 acpi_base_addr; + ++static const struct pmc_clk byt_clks[] = { ++ { ++ .name = "xtal", ++ .freq = 25000000, ++ .parent_name = NULL, ++ }, ++ { ++ .name = "pll", ++ .freq = 19200000, ++ .parent_name = "xtal", ++ }, ++ {}, ++}; ++ ++static const struct pmc_clk cht_clks[] = { ++ { ++ .name = "xtal", ++ .freq = 19200000, ++ .parent_name = NULL, ++ }, ++ {}, ++}; ++ + static const struct pmc_bit_map d3_sts_0_map[] = { + {"LPSS1_F0_DMA", BIT_LPSS1_F0_DMA}, + {"LPSS1_F1_PWM1", BIT_LPSS1_F1_PWM1}, +@@ -169,6 +198,16 @@ static const struct pmc_reg_map cht_reg_map = { + .pss = cht_pss_map, + }; + ++static const struct pmc_data byt_data = { ++ .map = &byt_reg_map, ++ .clks = byt_clks, ++}; ++ ++static const struct pmc_data cht_data = { ++ .map = &cht_reg_map, ++ .clks = cht_clks, ++}; ++ + static inline u32 pmc_reg_read(struct pmc_dev *pmc, int reg_offset) + { + return readl(pmc->regmap + reg_offset); +@@ -384,8 +423,11 @@ static int pmc_dbgfs_register(struct pmc_dev *pmc) + + static int pmc_setup_dev(struct pci_dev *pdev, const struct pci_device_id *ent) + { ++ struct platform_device *clkdev; + struct pmc_dev *pmc = &pmc_device; +- const struct pmc_reg_map *map = (struct pmc_reg_map *)ent->driver_data; ++ const struct pmc_data *data = (struct pmc_data *)ent->driver_data; ++ const struct pmc_reg_map *map = data->map; ++ const struct pmc_clk *clks = data->clks; + int ret; + + /* Obtain ACPI base address */ +@@ -414,6 +456,13 @@ static int pmc_setup_dev(struct pci_dev *pdev, const struct pci_device_id *ent) + if (ret) + dev_warn(&pdev->dev, "debugfs register failed\n"); + ++ /* Register platform clocks - PMC_PLT_CLK [5:0] */ ++ clkdev = platform_device_register_data(NULL, "clk-byt-plt", -1, ++ &clks, sizeof(clks)); ++ if (IS_ERR(clkdev)) ++ dev_warn(&pdev->dev, "platform clocks register failed: %ld\n", ++ PTR_ERR(clkdev)); ++ + pmc->init = true; + return ret; + } +@@ -424,8 +473,8 @@ static int pmc_setup_dev(struct pci_dev *pdev, const struct pci_device_id *ent) + * used by pci_match_id() call below. + */ + static const struct pci_device_id pmc_pci_ids[] = { +- { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_VLV_PMC), (kernel_ulong_t)&byt_reg_map }, +- { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_CHT_PMC), (kernel_ulong_t)&cht_reg_map }, ++ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_VLV_PMC), (kernel_ulong_t)&byt_data }, ++ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_CHT_PMC), (kernel_ulong_t)&cht_data }, + { 0, }, + }; + +diff --git a/drivers/clk/x86/Makefile b/drivers/clk/x86/Makefile +index 04781389d0fb..cbdc8cc00060 100644 +--- a/drivers/clk/x86/Makefile ++++ b/drivers/clk/x86/Makefile +@@ -1,2 +1,3 @@ + clk-x86-lpss-objs := clk-lpt.o + obj-$(CONFIG_X86_INTEL_LPSS) += clk-x86-lpss.o ++obj-$(CONFIG_PMC_ATOM) += clk-byt-plt.o +diff --git a/drivers/clk/x86/clk-byt-plt.c b/drivers/clk/x86/clk-byt-plt.c +new file mode 100644 +index 000000000000..330cd35d1d27 +--- /dev/null ++++ b/drivers/clk/x86/clk-byt-plt.c +@@ -0,0 +1,413 @@ ++/* ++ * Intel Atom platform clocks driver for Baytrail and CherryTrail SoC. ++ * ++ * Copyright (C) 2016, Intel Corporation ++ * Author: Irina Tirdea ++ * ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms and conditions of the GNU General Public License, ++ * version 2, as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope it will be useful, but WITHOUT ++ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ++ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for ++ * more details. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#define PLT_CLK_NAME_BASE "pmc_plt_clk_" ++#define PLT_CLK_DRIVER_NAME "clk-byt-plt" ++ ++#define PMC_CLK_CTL_0 0x60 ++#define PMC_CLK_CTL_SIZE 4 ++#define PMC_CLK_NUM 6 ++#define PMC_MASK_CLK_CTL GENMASK(1, 0) ++#define PMC_MASK_CLK_FREQ BIT(2) ++#define PMC_CLK_CTL_GATED_ON_D3 0x0 ++#define PMC_CLK_CTL_FORCE_ON 0x1 ++#define PMC_CLK_CTL_FORCE_OFF 0x2 ++#define PMC_CLK_CTL_RESERVED 0x3 ++#define PMC_CLK_FREQ_XTAL 0x0 /* 25 MHz */ ++#define PMC_CLK_FREQ_PLL 0x4 /* 19.2 MHz */ ++ ++struct clk_plt_fixed { ++ struct clk *clk; ++ struct clk_lookup *lookup; ++}; ++ ++struct clk_plt { ++ struct clk_hw hw; ++ u8 id; ++ u32 offset; ++ struct clk_lookup *lookup; ++ spinlock_t lock; ++}; ++ ++#define to_clk_plt(_hw) container_of(_hw, struct clk_plt, hw) ++ ++struct clk_plt_data { ++ struct clk_plt_fixed **parents; ++ u8 nparents; ++ struct clk *clks[PMC_CLK_NUM]; ++}; ++ ++static inline int plt_reg_to_parent(int reg) ++{ ++ switch (reg & PMC_MASK_CLK_FREQ) { ++ case PMC_CLK_FREQ_XTAL: ++ return 0; /* index 0 in parents[] */ ++ case PMC_CLK_FREQ_PLL: ++ return 1; /* index 1 in parents[] */ ++ } ++ ++ return 0; ++} ++ ++static inline int plt_parent_to_reg(int index) ++{ ++ switch (index) { ++ case 0: /* index 0 in parents[] */ ++ return PMC_CLK_FREQ_XTAL; ++ case 1: /* index 0 in parents[] */ ++ return PMC_CLK_FREQ_PLL; ++ } ++ ++ return PMC_CLK_FREQ_XTAL; ++} ++ ++static inline int plt_reg_to_enabled(int reg) ++{ ++ switch (reg & PMC_MASK_CLK_CTL) { ++ case PMC_CLK_CTL_GATED_ON_D3: ++ case PMC_CLK_CTL_FORCE_ON: ++ return 1; /* enabled */ ++ case PMC_CLK_CTL_FORCE_OFF: ++ case PMC_CLK_CTL_RESERVED: ++ default: ++ return 0; /* disabled */ ++ } ++} ++ ++static int plt_pmc_atom_update(struct clk_plt *clk, u32 mask, u32 val) ++{ ++ int ret; ++ u32 orig, tmp; ++ unsigned long flags = 0; ++ ++ spin_lock_irqsave(&clk->lock, flags); ++ ++ ret = pmc_atom_read(clk->offset, &orig); ++ if (ret) ++ goto out; ++ ++ tmp = orig & ~mask; ++ tmp |= val & mask; ++ ++ if (tmp == orig) ++ goto out; ++ ++ ret = pmc_atom_write(clk->offset, tmp); ++ if (ret) ++ goto out; ++ ++out: ++ spin_unlock_irqrestore(&clk->lock, flags); ++ ++ return ret; ++} ++ ++static int plt_clk_set_parent(struct clk_hw *hw, u8 index) ++{ ++ struct clk_plt *clk = to_clk_plt(hw); ++ ++ return plt_pmc_atom_update(clk, PMC_MASK_CLK_FREQ, ++ plt_parent_to_reg(index)); ++} ++ ++static u8 plt_clk_get_parent(struct clk_hw *hw) ++{ ++ struct clk_plt *clk = to_clk_plt(hw); ++ u32 value; ++ int ret; ++ ++ ret = pmc_atom_read(clk->offset, &value); ++ if (ret) ++ return ret; ++ ++ return plt_reg_to_parent(value); ++} ++ ++static int plt_clk_enable(struct clk_hw *hw) ++{ ++ struct clk_plt *clk = to_clk_plt(hw); ++ ++ return plt_pmc_atom_update(clk, PMC_MASK_CLK_CTL, PMC_CLK_CTL_FORCE_ON); ++} ++ ++static void plt_clk_disable(struct clk_hw *hw) ++{ ++ struct clk_plt *clk = to_clk_plt(hw); ++ ++ plt_pmc_atom_update(clk, PMC_MASK_CLK_CTL, PMC_CLK_CTL_FORCE_OFF); ++} ++ ++static int plt_clk_is_enabled(struct clk_hw *hw) ++{ ++ struct clk_plt *clk = to_clk_plt(hw); ++ u32 value; ++ int ret; ++ ++ ret = pmc_atom_read(clk->offset, &value); ++ if (ret) ++ return ret; ++ ++ return plt_reg_to_enabled(value); ++} ++ ++static const struct clk_ops plt_clk_ops = { ++ .enable = plt_clk_enable, ++ .disable = plt_clk_disable, ++ .is_enabled = plt_clk_is_enabled, ++ .get_parent = plt_clk_get_parent, ++ .set_parent = plt_clk_set_parent, ++ .determine_rate = __clk_mux_determine_rate, ++}; ++ ++static struct clk *plt_clk_register(struct platform_device *pdev, int id, ++ const char **parent_names, int num_parents) ++{ ++ struct clk_plt *pclk; ++ struct clk *clk; ++ struct clk_init_data init; ++ int ret = 0; ++ ++ pclk = devm_kzalloc(&pdev->dev, sizeof(*pclk), GFP_KERNEL); ++ if (!pclk) ++ return ERR_PTR(-ENOMEM); ++ ++ init.name = kasprintf(GFP_KERNEL, "%s%d", PLT_CLK_NAME_BASE, id); ++ init.ops = &plt_clk_ops; ++ init.flags = 0; ++ init.parent_names = parent_names; ++ init.num_parents = num_parents; ++ ++ pclk->hw.init = &init; ++ pclk->id = id; ++ pclk->offset = PMC_CLK_CTL_0 + id * PMC_CLK_CTL_SIZE; ++ spin_lock_init(&pclk->lock); ++ ++ clk = clk_register(&pdev->dev, &pclk->hw); ++ if (IS_ERR(clk)) { ++ ret = PTR_ERR(clk); ++ goto err_free_pclk; ++ } ++ ++ pclk->lookup = clkdev_create(clk, init.name, NULL); ++ if (!pclk->lookup) { ++ ret = -ENOMEM; ++ goto err_clk_unregister; ++ } ++ ++ kfree(init.name); ++ ++ return clk; ++ ++err_clk_unregister: ++ clk_unregister(clk); ++err_free_pclk: ++ kfree(init.name); ++ return ERR_PTR(ret); ++} ++ ++static void plt_clk_unregister(struct clk *clk) ++{ ++ struct clk_plt *pclk; ++ struct clk_hw *hw; ++ ++ hw = __clk_get_hw(clk); ++ if (!hw) ++ return; ++ ++ pclk = to_clk_plt(hw); ++ ++ clkdev_drop(pclk->lookup); ++ clk_unregister(clk); ++} ++ ++static struct clk_plt_fixed *plt_clk_register_fixed_rate(struct platform_device *pdev, ++ const char *name, ++ const char *parent_name, ++ unsigned long fixed_rate) ++{ ++ struct clk_plt_fixed *pclk; ++ int ret = 0; ++ ++ pclk = devm_kzalloc(&pdev->dev, sizeof(*pclk), GFP_KERNEL); ++ if (!pclk) ++ return ERR_PTR(-ENOMEM); ++ ++ pclk->clk = clk_register_fixed_rate(&pdev->dev, name, parent_name, ++ 0, fixed_rate); ++ if (IS_ERR(pclk->clk)) { ++ ret = PTR_ERR(pclk->clk); ++ return ERR_PTR(ret); ++ } ++ ++ pclk->lookup = clkdev_create(pclk->clk, name, NULL); ++ if (!pclk->lookup) { ++ ret = -ENOMEM; ++ goto err_clk_unregister; ++ } ++ ++ return pclk; ++ ++err_clk_unregister: ++ clk_unregister_fixed_rate(pclk->clk); ++ return ERR_PTR(ret); ++} ++ ++static void plt_clk_unregister_fixed_rate(struct clk_plt_fixed *pclk) ++{ ++ clkdev_drop(pclk->lookup); ++ clk_unregister_fixed_rate(pclk->clk); ++} ++ ++static const char **plt_clk_register_parents(struct platform_device *pdev, ++ struct clk_plt_data *data) ++{ ++ struct pmc_clk **pclks, *clks; ++ const char **parent_names; ++ int i, err; ++ ++ data->nparents = 0; ++ pclks = dev_get_platdata(&pdev->dev); ++ if (!pclks) ++ return NULL; ++ ++ clks = *pclks; ++ while (clks[data->nparents].name) ++ data->nparents++; ++ ++ data->parents = devm_kzalloc(&pdev->dev, ++ sizeof(*data->parents) * data->nparents, ++ GFP_KERNEL); ++ if (!data->parents) { ++ err = -ENOMEM; ++ goto err_out; ++ } ++ ++ parent_names = kcalloc(data->nparents, sizeof(*parent_names), ++ GFP_KERNEL); ++ if (!parent_names) { ++ err = -ENOMEM; ++ goto err_out; ++ } ++ ++ for (i = 0; i < data->nparents; i++) { ++ data->parents[i] = ++ plt_clk_register_fixed_rate(pdev, clks[i].name, ++ clks[i].parent_name, ++ clks[i].freq); ++ if (IS_ERR(data->parents[i])) { ++ err = PTR_ERR(data->parents[i]); ++ goto err_unreg; ++ } ++ parent_names[i] = kstrdup_const(clks[i].name, GFP_KERNEL); ++ } ++ ++ return parent_names; ++ ++err_unreg: ++ for (i--; i >= 0; i--) { ++ plt_clk_unregister_fixed_rate(data->parents[i]); ++ kfree_const(parent_names[i]); ++ } ++ kfree(parent_names); ++err_out: ++ data->nparents = 0; ++ return ERR_PTR(err); ++} ++ ++static void plt_clk_unregister_parents(struct clk_plt_data *data) ++{ ++ int i; ++ ++ for (i = 0; i < data->nparents; i++) ++ plt_clk_unregister_fixed_rate(data->parents[i]); ++} ++ ++static int plt_clk_probe(struct platform_device *pdev) ++{ ++ struct clk_plt_data *data; ++ int i, err; ++ const char **parent_names; ++ ++ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); ++ if (!data) ++ return -ENOMEM; ++ ++ parent_names = plt_clk_register_parents(pdev, data); ++ if (IS_ERR(parent_names)) ++ return PTR_ERR(parent_names); ++ ++ for (i = 0; i < PMC_CLK_NUM; i++) { ++ data->clks[i] = plt_clk_register(pdev, i, parent_names, ++ data->nparents); ++ if (IS_ERR(data->clks[i])) { ++ err = PTR_ERR(data->clks[i]); ++ goto err_unreg_clk_plt; ++ } ++ } ++ ++ for (i = 0; i < data->nparents; i++) ++ kfree_const(parent_names[i]); ++ kfree(parent_names); ++ ++ dev_set_drvdata(&pdev->dev, data); ++ return 0; ++ ++err_unreg_clk_plt: ++ for (i--; i >= 0; i--) ++ plt_clk_unregister(data->clks[i]); ++ plt_clk_unregister_parents(data); ++ for (i = 0; i < data->nparents; i++) ++ kfree_const(parent_names[i]); ++ kfree(parent_names); ++ return err; ++} ++ ++static int plt_clk_remove(struct platform_device *pdev) ++{ ++ struct clk_plt_data *data; ++ int i; ++ ++ data = dev_get_drvdata(&pdev->dev); ++ if (!data) ++ return 0; ++ ++ for (i = 0; i < PMC_CLK_NUM; i++) ++ plt_clk_unregister(data->clks[i]); ++ plt_clk_unregister_parents(data); ++ return 0; ++} ++ ++static struct platform_driver plt_clk_driver = { ++ .driver = { ++ .name = PLT_CLK_DRIVER_NAME, ++ }, ++ .probe = plt_clk_probe, ++ .remove = plt_clk_remove, ++}; ++module_platform_driver(plt_clk_driver); ++ ++MODULE_DESCRIPTION("Intel Atom platform clocks driver"); ++MODULE_AUTHOR("Irina Tirdea "); ++MODULE_LICENSE("GPL v2"); +diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig +index c67667bb970f..655e2f4d1dd0 100644 +--- a/sound/soc/codecs/Kconfig ++++ b/sound/soc/codecs/Kconfig +@@ -61,6 +61,7 @@ config SND_SOC_ALL_CODECS + select SND_SOC_CS47L24 if MFD_CS47L24 + select SND_SOC_CS53L30 if I2C + select SND_SOC_CX20442 if TTY ++ select SND_SOC_CX2072X if I2C + select SND_SOC_DA7210 if SND_SOC_I2C_AND_SPI + select SND_SOC_DA7213 if I2C + select SND_SOC_DA7218 if I2C +@@ -478,6 +479,10 @@ config SND_SOC_CX20442 + tristate + depends on TTY + ++config SND_SOC_CX2072X ++ tristate "Conexant CX2072X CODEC" ++ depends on I2C ++ + config SND_SOC_JZ4740_CODEC + select REGMAP_MMIO + tristate +diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile +index 958cd4912fbc..1dded20a4d84 100644 +--- a/sound/soc/codecs/Makefile ++++ b/sound/soc/codecs/Makefile +@@ -54,6 +54,7 @@ snd-soc-cs4349-objs := cs4349.o + snd-soc-cs47l24-objs := cs47l24.o + snd-soc-cs53l30-objs := cs53l30.o + snd-soc-cx20442-objs := cx20442.o ++snd-soc-cx2072x-objs := cx2072x.o + snd-soc-da7210-objs := da7210.o + snd-soc-da7213-objs := da7213.o + snd-soc-da7218-objs := da7218.o +@@ -279,6 +280,7 @@ obj-$(CONFIG_SND_SOC_CS4349) += snd-soc-cs4349.o + obj-$(CONFIG_SND_SOC_CS47L24) += snd-soc-cs47l24.o + obj-$(CONFIG_SND_SOC_CS53L30) += snd-soc-cs53l30.o + obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o ++obj-$(CONFIG_SND_SOC_CX2072X) += snd-soc-cx2072x.o + obj-$(CONFIG_SND_SOC_DA7210) += snd-soc-da7210.o + obj-$(CONFIG_SND_SOC_DA7213) += snd-soc-da7213.o + obj-$(CONFIG_SND_SOC_DA7218) += snd-soc-da7218.o +diff --git a/sound/soc/codecs/cx2072x.c b/sound/soc/codecs/cx2072x.c +new file mode 100644 +index 000000000000..aaec3722854c +--- /dev/null ++++ b/sound/soc/codecs/cx2072x.c +@@ -0,0 +1,2124 @@ ++/* ++ * ALSA SoC CX20721/cx20723 Solana codec driver ++ * Copyright: (C) 2016 Conexant Systems, Inc. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 as ++ * published by the Free Software Foundation. ++ * ++ ************************************************************************/ ++#define DEBUG ++/*#define INTEL_MCLK_CONTROL*/ ++ ++/*#define ENABLE_MIC_POP_WA*/ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#ifdef INTEL_MCLK_CONTROL ++#include ++#endif ++#include "cx2072x.h" ++ ++#define SUPPORT_RKI2S_FORMAT ++#define CX2072X_REV_A2 0x00100002 ++#define CXDBG_REG_DUMP ++ ++/*FIXME: need to move the EQ/DRC setting to device tree*/ ++static unsigned char cx2072x_eq_coeff_array[MAX_EQ_BAND][MAC_EQ_COEFF] = { ++ {0x77, 0x26, 0x13, 0xb3, 0x76, 0x26, 0x0a, 0x3d, 0xd4, 0xe2, 0x04}, ++ {0x97, 0x3e, 0xb3, 0x86, 0xc2, 0x3b, 0x4d, 0x79, 0xa7, 0xc5, 0x03}, ++ {0x0f, 0x39, 0x76, 0xa3, 0x1b, 0x2b, 0x89, 0x5c, 0xd7, 0xdb, 0x03}, ++ {0x21, 0x46, 0xfe, 0xa6, 0xec, 0x24, 0x01, 0x59, 0xf4, 0xd4, 0x03}, ++ {0xe9, 0x78, 0x9c, 0xb0, 0x8a, 0x56, 0x64, 0x4f, 0x8d, 0xb0, 0x02}, ++ {0x60, 0x6e, 0x57, 0xee, 0xec, 0x18, 0xa8, 0x11, 0xb5, 0xf8, 0x02}, ++ {0x5a, 0x14, 0x68, 0xe9, 0x1d, 0x06, 0xb9, 0x5f, 0x68, 0xdc, 0x03}, ++}; ++ ++static unsigned char cx2072x_drc_array[MAX_DRC_REGS] = { ++ 0x65, 0x55, 0x3C, 0x01, 0x05, 0x39, 0x76, 0x1A, 0x00 ++}; ++ ++/*#define CXDBG_REG_DUMP*/ ++#ifdef CXDBG_REG_DUMP ++ ++#define CX2072X_FORMATS (SNDRV_PCM_FMTBIT_S24_LE \ ++ | SNDRV_PCM_FMTBIT_S16_LE) ++#define BITS_PER_SLOT 8 ++ ++#define _REG(_name_, _size_, _access_, _volatile_) { \ ++ #_name_, _name_, _size_ | _access_ | _volatile_} ++ ++struct CX2072X_REG_DEF { ++ const char *name; ++ unsigned int addr; ++ unsigned int attr; ++}; ++ ++#define WO 0x0100 ++#define RO 0x0200 ++#define RW 0x0300 ++#define VO 0x8000 ++#define NV 0x0000 ++#define REGISTER_SIZE_MASK 0x000F ++#define REGISTER_ASSCESS_MASK 0x0F00 ++#define REGISTER_VOLATILE_MASK 0x8000 ++#define UNAVAILABLE 0 ++ ++ ++static const struct CX2072X_REG_DEF cx2072x_regs[] = { ++ _REG(CX2072X_VENDOR_ID, 4, RO, VO), ++ _REG(CX2072X_REVISION_ID, 4, RO, VO), ++ _REG(CX2072X_CURRENT_BCLK_FREQUENCY, 4, RO, VO), ++ _REG(CX2072X_AFG_POWER_STATE, 1, RW, NV), ++ _REG(CX2072X_UM_RESPONSE, 1, RW, NV), ++ _REG(CX2072X_GPIO_DATA, 1, RW, NV), ++ _REG(CX2072X_GPIO_ENABLE, 1, RW, NV), ++ _REG(CX2072X_GPIO_DIRECTION, 1, RW, NV), ++ _REG(CX2072X_GPIO_WAKE, 1, RW, NV), ++ _REG(CX2072X_GPIO_UM_ENABLE, 1, RW, NV), ++ _REG(CX2072X_GPIO_STICKY_MASK, 1, RW, NV), ++ _REG(CX2072X_AFG_FUNCTION_RESET, 1, WO, NV), ++ _REG(CX2072X_DAC1_CONVERTER_FORMAT, 2, RW, NV), ++ _REG(CX2072X_DAC1_AMP_GAIN_RIGHT, 1, RW, NV), ++ _REG(CX2072X_DAC1_AMP_GAIN_LEFT, 1, RW, NV), ++ _REG(CX2072X_DAC1_POWER_STATE, 1, RW, NV), ++ _REG(CX2072X_DAC1_CONVERTER_STREAM_CHANNEL, 1, RW, NV), ++ _REG(CX2072X_DAC1_EAPD_ENABLE, 1, RW, NV), ++ _REG(CX2072X_DAC2_CONVERTER_FORMAT, 2, RW, NV), ++ _REG(CX2072X_DAC2_AMP_GAIN_RIGHT, 1, RW, NV), ++ _REG(CX2072X_DAC2_AMP_GAIN_LEFT, 1, RW, NV), ++ _REG(CX2072X_DAC2_POWER_STATE, 1, RW, NV), ++ _REG(CX2072X_DAC2_CONVERTER_STREAM_CHANNEL, 1, RW, NV), ++ _REG(CX2072X_ADC1_CONVERTER_FORMAT, 2, RW, NV), ++ _REG(CX2072X_ADC1_AMP_GAIN_RIGHT_0, 1, RW, NV), ++ _REG(CX2072X_ADC1_AMP_GAIN_LEFT_0, 1, RW, NV), ++ _REG(CX2072X_ADC1_AMP_GAIN_RIGHT_1, 1, RW, NV), ++ _REG(CX2072X_ADC1_AMP_GAIN_LEFT_1, 1, RW, NV), ++ _REG(CX2072X_ADC1_AMP_GAIN_RIGHT_2, 1, RW, NV), ++ _REG(CX2072X_ADC1_AMP_GAIN_LEFT_2, 1, RW, NV), ++ _REG(CX2072X_ADC1_AMP_GAIN_RIGHT_3, 1, RW, NV), ++ _REG(CX2072X_ADC1_AMP_GAIN_LEFT_3, 1, RW, NV), ++ _REG(CX2072X_ADC1_AMP_GAIN_RIGHT_4, 1, RW, NV), ++ _REG(CX2072X_ADC1_AMP_GAIN_LEFT_4, 1, RW, NV), ++ _REG(CX2072X_ADC1_AMP_GAIN_RIGHT_5, 1, RW, NV), ++ _REG(CX2072X_ADC1_AMP_GAIN_LEFT_5, 1, RW, NV), ++ _REG(CX2072X_ADC1_AMP_GAIN_RIGHT_6, 1, RW, NV), ++ _REG(CX2072X_ADC1_AMP_GAIN_LEFT_6, 1, RW, NV), ++ _REG(CX2072X_ADC1_CONNECTION_SELECT_CONTROL, 1, RW, NV), ++ _REG(CX2072X_ADC1_POWER_STATE, 1, RW, NV), ++ _REG(CX2072X_ADC1_CONVERTER_STREAM_CHANNEL, 1, RW, NV), ++ _REG(CX2072X_ADC2_CONVERTER_FORMAT, 2, WO, NV), ++ _REG(CX2072X_ADC2_AMP_GAIN_RIGHT_0, 1, RW, NV), ++ _REG(CX2072X_ADC2_AMP_GAIN_LEFT_0, 1, RW, NV), ++ _REG(CX2072X_ADC2_AMP_GAIN_RIGHT_1, 1, RW, NV), ++ _REG(CX2072X_ADC2_AMP_GAIN_LEFT_1, 1, RW, NV), ++ _REG(CX2072X_ADC2_AMP_GAIN_RIGHT_2, 1, RW, NV), ++ _REG(CX2072X_ADC2_AMP_GAIN_LEFT_2, 1, RW, NV), ++ _REG(CX2072X_ADC2_CONNECTION_SELECT_CONTROL, 1, RW, NV), ++ _REG(CX2072X_ADC2_POWER_STATE, 1, RW, NV), ++ _REG(CX2072X_ADC2_CONVERTER_STREAM_CHANNEL, 1, RW, NV), ++ _REG(CX2072X_PORTA_CONNECTION_SELECT_CTRL, 1, RW, NV), ++ _REG(CX2072X_PORTA_POWER_STATE, 1, RW, NV), ++ _REG(CX2072X_PORTA_PIN_CTRL, 1, RW, NV), ++ _REG(CX2072X_PORTA_UNSOLICITED_RESPONSE, 1, RW, NV), ++ _REG(CX2072X_PORTA_PIN_SENSE, 4, RO, VO), ++ _REG(CX2072X_PORTA_EAPD_BTL, 1, RW, NV), ++ _REG(CX2072X_PORTB_POWER_STATE, 1, RW, NV), ++ _REG(CX2072X_PORTB_PIN_CTRL, 1, RW, NV), ++ _REG(CX2072X_PORTB_UNSOLICITED_RESPONSE, 1, RW, NV), ++ _REG(CX2072X_PORTB_PIN_SENSE, 4, RO, VO), ++ _REG(CX2072X_PORTB_EAPD_BTL, 1, RW, NV), ++ _REG(CX2072X_PORTB_GAIN_RIGHT, 1, RW, NV), ++ _REG(CX2072X_PORTB_GAIN_LEFT, 1, RW, NV), ++ _REG(CX2072X_PORTC_POWER_STATE, 1, RW, NV), ++ _REG(CX2072X_PORTC_PIN_CTRL, 1, RW, NV), ++ _REG(CX2072X_PORTC_GAIN_RIGHT, 1, RW, NV), ++ _REG(CX2072X_PORTC_GAIN_LEFT, 1, RW, NV), ++ _REG(CX2072X_PORTD_POWER_STATE, 1, RW, NV), ++ _REG(CX2072X_PORTD_PIN_CTRL, 1, RW, NV), ++ _REG(CX2072X_PORTD_UNSOLICITED_RESPONSE, 1, RW, NV), ++ _REG(CX2072X_PORTD_PIN_SENSE, 4, RO, VO), ++ _REG(CX2072X_PORTD_GAIN_RIGHT, 1, RW, NV), ++ _REG(CX2072X_PORTD_GAIN_LEFT, 1, RW, NV), ++ _REG(CX2072X_PORTE_CONNECTION_SELECT_CTRL, 1, RW, NV), ++ _REG(CX2072X_PORTE_POWER_STATE, 1, RW, NV), ++ _REG(CX2072X_PORTE_PIN_CTRL, 1, RW, NV), ++ _REG(CX2072X_PORTE_UNSOLICITED_RESPONSE, 1, RW, NV), ++ _REG(CX2072X_PORTE_PIN_SENSE, 4, RO, VO), ++ _REG(CX2072X_PORTE_EAPD_BTL, 1, RW, NV), ++ _REG(CX2072X_PORTE_GAIN_RIGHT, 1, RW, NV), ++ _REG(CX2072X_PORTE_GAIN_LEFT, 1, RW, NV), ++ _REG(CX2072X_PORTF_POWER_STATE, 1, RW, NV), ++ _REG(CX2072X_PORTF_PIN_CTRL, 1, RW, NV), ++ _REG(CX2072X_PORTF_UNSOLICITED_RESPONSE, 1, RW, NV), ++ _REG(CX2072X_PORTF_PIN_SENSE, 4, RO, VO), ++ _REG(CX2072X_PORTF_GAIN_RIGHT, 1, RW, NV), ++ _REG(CX2072X_PORTF_GAIN_LEFT, 1, RW, NV), ++ _REG(CX2072X_PORTG_POWER_STATE, 1, RW, NV), ++ _REG(CX2072X_PORTG_PIN_CTRL, 1, RW, NV), ++ _REG(CX2072X_PORTG_CONNECTION_SELECT_CTRL, 1, RW, NV), ++ _REG(CX2072X_PORTG_EAPD_BTL, 1, RW, NV), ++ _REG(CX2072X_PORTM_POWER_STATE, 1, RW, NV), ++ _REG(CX2072X_PORTM_PIN_CTRL, 1, RW, NV), ++ _REG(CX2072X_PORTM_CONNECTION_SELECT_CTRL, 1, RW, NV), ++ _REG(CX2072X_PORTM_EAPD_BTL, 1, RW, NV), ++ _REG(CX2072X_MIXER_POWER_STATE, 1, RW, NV), ++ _REG(CX2072X_MIXER_GAIN_RIGHT_0, 1, RW, NV), ++ _REG(CX2072X_MIXER_GAIN_LEFT_0, 1, WO, NV), ++ _REG(CX2072X_MIXER_GAIN_RIGHT_1, 1, RW, NV), ++ _REG(CX2072X_MIXER_GAIN_LEFT_1, 1, RW, NV), ++ _REG(CX2072X_EQ_ENABLE_BYPASS, 2, RW, NV), ++ _REG(CX2072X_EQ_B0_COEFF, 2, WO, VO), ++ _REG(CX2072X_EQ_B1_COEFF, 2, WO, VO), ++ _REG(CX2072X_EQ_B2_COEFF, 2, WO, VO), ++ _REG(CX2072X_EQ_A1_COEFF, 2, WO, VO), ++ _REG(CX2072X_EQ_A2_COEFF, 2, WO, VO), ++ _REG(CX2072X_EQ_G_COEFF, 1, WO, VO), ++ _REG(CX2072X_EQ_BAND, 1, WO, VO), ++ _REG(CX2072X_SPKR_DRC_ENABLE_STEP, 1, RW, NV), ++ _REG(CX2072X_SPKR_DRC_CONTROL, 4, RW, NV), ++ _REG(CX2072X_SPKR_DRC_TEST, 4, RW, NV), ++ _REG(CX2072X_DIGITAL_BIOS_TEST0, 4, RW, NV), ++ _REG(CX2072X_DIGITAL_BIOS_TEST2, 4, RW, NV), ++ _REG(CX2072X_I2SPCM_CONTROL1, 4, RW, NV), ++ _REG(CX2072X_I2SPCM_CONTROL2, 4, RW, NV), ++ _REG(CX2072X_I2SPCM_CONTROL3, 4, RW, NV), ++ _REG(CX2072X_I2SPCM_CONTROL4, 4, RW, NV), ++ _REG(CX2072X_I2SPCM_CONTROL5, 4, RW, NV), ++ _REG(CX2072X_I2SPCM_CONTROL6, 4, RW, NV), ++ _REG(CX2072X_UM_INTERRUPT_CRTL_E, 4, RW, NV), ++ _REG(CX2072X_CODEC_TEST20, 2, RW, NV), ++ _REG(CX2072X_CODEC_TEST26, 2, RW, NV), ++ _REG(CX2072X_ANALOG_TEST4, 2, RW, NV), ++ _REG(CX2072X_ANALOG_TEST5, 2, RW, NV), ++ _REG(CX2072X_ANALOG_TEST6, 2, WO, NV), ++ _REG(CX2072X_ANALOG_TEST7, 2, RW, NV), ++ _REG(CX2072X_ANALOG_TEST8, 2, RW, NV), ++ _REG(CX2072X_ANALOG_TEST9, 2, RW, NV), ++ _REG(CX2072X_ANALOG_TEST10, 2, RW, NV), ++ _REG(CX2072X_ANALOG_TEST11, 2, RW, NV), ++ _REG(CX2072X_ANALOG_TEST12, 2, RW, NV), ++ _REG(CX2072X_ANALOG_TEST13, 2, RW, NV), ++ _REG(CX2072X_DIGITAL_TEST0, 2, RW, NV), ++ _REG(CX2072X_DIGITAL_TEST1, 2, RW, NV), ++ _REG(CX2072X_DIGITAL_TEST11, 2, RW, NV), ++ _REG(CX2072X_DIGITAL_TEST12, 2, RW, NV), ++ _REG(CX2072X_DIGITAL_TEST15, 2, RW, NV), ++ _REG(CX2072X_DIGITAL_TEST16, 2, RW, NV), ++ _REG(CX2072X_DIGITAL_TEST17, 2, RW, NV), ++ _REG(CX2072X_DIGITAL_TEST18, 2, RW, NV), ++ _REG(CX2072X_DIGITAL_TEST19, 2, RW, NV), ++ _REG(CX2072X_DIGITAL_TEST20, 2, RW, NV), ++}; ++#endif ++ ++/* codec private data */ ++struct cx2072x_priv { ++ struct regmap *regmap; ++ unsigned int mclk; ++ struct device *dev; ++ struct snd_soc_codec *codec; ++ struct snd_soc_dai_driver *dai_drv; ++ int is_biason; ++ struct snd_soc_jack *jack; ++ bool jack_detecting; ++ bool jack_mic; ++ int jack_mode; ++ int jack_flips; ++ unsigned int jack_state; ++ int audsmt_enable; ++ struct mutex lock; ++ unsigned int bclk_ratio; ++ ++#ifdef ENABLE_MIC_POP_WA ++ struct delayed_work mic_pop_workq; ++#endif ++ ++ bool plbk_dsp_en; ++ bool plbk_dsp_changed; ++ bool plbk_dsp_init; ++ bool pll_changed; ++ bool i2spcm_changed; ++ int sample_size; /*used for non-PCM mode*/ ++ int frame_size; /*used for non-PCM mode*/ ++ int sample_rate; ++ unsigned int dai_fmt; ++ int tdm_rx_mask; ++ int tdm_tx_mask; ++ int tdm_slot_width; ++ int tdm_slots; ++ u32 rev_id; ++}; ++ ++/* ++* DAC/ADC Volume ++* ++* max : 74 : 0 dB ++* ( in 1 dB step ) ++* min : 0 : -74 dB ++*/ ++static const DECLARE_TLV_DB_SCALE(adc_tlv, -7400, 100, 0); ++static const DECLARE_TLV_DB_SCALE(dac_tlv, -7400, 100, 0); ++static const DECLARE_TLV_DB_SCALE(boost_tlv, 0, 1200, 0); ++ ++#define get_cx2072x_priv(_codec_) \ ++ ((struct cx2072x_priv *) snd_soc_codec_get_drvdata(_codec_)) ++ ++/* Lookup table for PRE_DIV*/ ++static struct { ++ unsigned int mclk; ++ unsigned int div; ++} MCLK_PRE_DIV[] = { ++ { 6144000, 1 }, ++ { 12288000, 2 }, ++ { 19200000, 3 }, ++ { 26000000, 4 }, ++ { 28224000, 5 }, ++ { 36864000, 6 }, ++ { 36864000, 7 },/* Don't use div 7*/ ++ { 48000000, 8 }, ++ { 49152000, 8 }, ++}; ++ ++/* ++ * cx2072x register cache. ++ */ ++static const struct reg_default cx2072x_reg_defaults[] = { ++ { 0x0414, 0x00000003 }, /*2072X_AFG_POWER_STATE */ ++ { 0x0420, 0x00000000 }, /*2072X_UM_RESPONSE */ ++ { 0x0454, 0x00000000 }, /*2072X_GPIO_DATA */ ++ { 0x0458, 0x00000000 }, /*2072X_GPIO_ENABLE */ ++ { 0x045c, 0x00000000 }, /*2072X_GPIO_DIRECTION */ ++ { 0x0460, 0x00000000 }, /*2072X_GPIO_WAKE */ ++ { 0x0464, 0x00000000 }, /*2072X_GPIO_UM_ENABLE */ ++ { 0x0468, 0x00000000 }, /*2072X_GPIO_STICKY_MASK */ ++ { 0x43c8, 0x00000031 }, /*2072X_DAC1_CONVERTER_FORMAT */ ++ { 0x41c0, 0x0000004a }, /*2072X_DAC1_AMP_GAIN_RIGHT */ ++ { 0x41e0, 0x0000004a }, /*2072X_DAC1_AMP_GAIN_LEFT */ ++ { 0x4014, 0x00000433 }, /*2072X_DAC1_POWER_STATE */ ++ { 0x4018, 0x00000000 }, /*2072X_DAC1_CONVERTER_STREAM_CHANNEL */ ++ { 0x4030, 0x00000000 }, /*2072X_DAC1_EAPD_ENABLE */ ++ { 0x47c8, 0x00000031 }, /*2072X_DAC2_CONVERTER_FORMAT */ ++ { 0x45c0, 0x0000004a }, /*2072X_DAC2_AMP_GAIN_RIGHT */ ++ { 0x45e0, 0x0000004a }, /*2072X_DAC2_AMP_GAIN_LEFT */ ++ { 0x4414, 0x00000433 }, /*2072X_DAC2_POWER_STATE */ ++ { 0x4418, 0x00000000 }, /*2072X_DAC2_CONVERTER_STREAM_CHANNEL */ ++ { 0x4fc8, 0x00000031 }, /*2072X_ADC1_CONVERTER_FORMAT */ ++ { 0x4d80, 0x0000004a }, /*2072X_ADC1_AMP_GAIN_RIGHT_0 */ ++ { 0x4da0, 0x0000004a }, /*2072X_ADC1_AMP_GAIN_LEFT_0 */ ++ { 0x4d84, 0x0000004a }, /*2072X_ADC1_AMP_GAIN_RIGHT_1 */ ++ { 0x4da4, 0x0000004a }, /*2072X_ADC1_AMP_GAIN_LEFT_1 */ ++ { 0x4d88, 0x0000004a }, /*2072X_ADC1_AMP_GAIN_RIGHT_2 */ ++ { 0x4da8, 0x0000004a }, /*2072X_ADC1_AMP_GAIN_LEFT_2 */ ++ { 0x4d8c, 0x0000004a }, /*2072X_ADC1_AMP_GAIN_RIGHT_3 */ ++ { 0x4dac, 0x0000004a }, /*2072X_ADC1_AMP_GAIN_LEFT_3 */ ++ { 0x4d90, 0x0000004a }, /*2072X_ADC1_AMP_GAIN_RIGHT_4 */ ++ { 0x4db0, 0x0000004a }, /*2072X_ADC1_AMP_GAIN_LEFT_4 */ ++ { 0x4d94, 0x0000004a }, /*2072X_ADC1_AMP_GAIN_RIGHT_5 */ ++ { 0x4db4, 0x0000004a }, /*2072X_ADC1_AMP_GAIN_LEFT_5 */ ++ { 0x4d98, 0x0000004a }, /*2072X_ADC1_AMP_GAIN_RIGHT_6 */ ++ { 0x4db8, 0x0000004a }, /*2072X_ADC1_AMP_GAIN_LEFT_6 */ ++ { 0x4c04, 0x00000000 }, /*2072X_ADC1_CONNECTION_SELECT_CONTROL */ ++ { 0x4c14, 0x00000433 }, /*2072X_ADC1_POWER_STATE */ ++ { 0x4c18, 0x00000000 }, /*2072X_ADC1_CONVERTER_STREAM_CHANNEL */ ++ { 0x53c8, 0x00000031 }, /*2072X_ADC2_CONVERTER_FORMAT */ ++ { 0x5180, 0x0000004a }, /*2072X_ADC2_AMP_GAIN_RIGHT_0 */ ++ { 0x51a0, 0x0000004a }, /*2072X_ADC2_AMP_GAIN_LEFT_0 */ ++ { 0x5184, 0x0000004a }, /*2072X_ADC2_AMP_GAIN_RIGHT_1 */ ++ { 0x51a4, 0x0000004a }, /*2072X_ADC2_AMP_GAIN_LEFT_1 */ ++ { 0x5188, 0x0000004a }, /*2072X_ADC2_AMP_GAIN_RIGHT_2 */ ++ { 0x51a8, 0x0000004a }, /*2072X_ADC2_AMP_GAIN_LEFT_2 */ ++ { 0x5004, 0x00000000 }, /*2072X_ADC2_CONNECTION_SELECT_CONTROL */ ++ { 0x5014, 0x00000433 }, /*2072X_ADC2_POWER_STATE */ ++ { 0x5018, 0x00000000 }, /*2072X_ADC2_CONVERTER_STREAM_CHANNEL */ ++ { 0x5804, 0x00000000 }, /*2072X_PORTA_CONNECTION_SELECT_CTRL */ ++ { 0x5814, 0x00000433 }, /*2072X_PORTA_POWER_STATE */ ++ { 0x581c, 0x000000c0 }, /*2072X_PORTA_PIN_CTRL */ ++ { 0x5820, 0x00000000 }, /*2072X_PORTA_UNSOLICITED_RESPONSE */ ++ { 0x5824, 0x00000000 }, /*2072X_PORTA_PIN_SENSE */ ++ { 0x5830, 0x00000002 }, /*2072X_PORTA_EAPD_BTL */ ++ { 0x6014, 0x00000433 }, /*2072X_PORTB_POWER_STATE */ ++ { 0x601c, 0x00000000 }, /*2072X_PORTB_PIN_CTRL */ ++ { 0x6020, 0x00000000 }, /*2072X_PORTB_UNSOLICITED_RESPONSE */ ++ { 0x6024, 0x00000000 }, /*2072X_PORTB_PIN_SENSE */ ++ { 0x6030, 0x00000002 }, /*2072X_PORTB_EAPD_BTL */ ++ { 0x6180, 0x00000000 }, /*2072X_PORTB_GAIN_RIGHT */ ++ { 0x61a0, 0x00000000 }, /*2072X_PORTB_GAIN_LEFT */ ++ { 0x6814, 0x00000433 }, /*2072X_PORTC_POWER_STATE */ ++ { 0x681c, 0x00000000 }, /*2072X_PORTC_PIN_CTRL */ ++ { 0x6980, 0x00000000 }, /*2072X_PORTC_GAIN_RIGHT */ ++ { 0x69a0, 0x00000000 }, /*2072X_PORTC_GAIN_LEFT */ ++ { 0x6414, 0x00000433 }, /*2072X_PORTD_POWER_STATE */ ++ { 0x641c, 0x00000020 }, /*2072X_PORTD_PIN_CTRL */ ++ { 0x6420, 0x00000000 }, /*2072X_PORTD_UNSOLICITED_RESPONSE */ ++ { 0x6424, 0x00000000 }, /*2072X_PORTD_PIN_SENSE */ ++ { 0x6580, 0x00000000 }, /*2072X_PORTD_GAIN_RIGHT */ ++ { 0x65a0, 0x00000000 }, /*2072X_PORTD_GAIN_LEFT */ ++ { 0x7404, 0x00000000 }, /*2072X_PORTE_CONNECTION_SELECT_CTRL */ ++ { 0x7414, 0x00000433 }, /*2072X_PORTE_POWER_STATE */ ++ { 0x741c, 0x00000040 }, /*2072X_PORTE_PIN_CTRL */ ++ { 0x7420, 0x00000000 }, /*2072X_PORTE_UNSOLICITED_RESPONSE */ ++ { 0x7424, 0x00000000 }, /*2072X_PORTE_PIN_SENSE */ ++ { 0x7430, 0x00000002 }, /*2072X_PORTE_EAPD_BTL */ ++ { 0x7580, 0x00000000 }, /*2072X_PORTE_GAIN_RIGHT */ ++ { 0x75a0, 0x00000000 }, /*2072X_PORTE_GAIN_LEFT */ ++ { 0x7814, 0x00000433 }, /*2072X_PORTF_POWER_STATE */ ++ { 0x781c, 0x00000000 }, /*2072X_PORTF_PIN_CTRL */ ++ { 0x7820, 0x00000000 }, /*2072X_PORTF_UNSOLICITED_RESPONSE */ ++ { 0x7824, 0x00000000 }, /*2072X_PORTF_PIN_SENSE */ ++ { 0x7980, 0x00000000 }, /*2072X_PORTF_GAIN_RIGHT */ ++ { 0x79a0, 0x00000000 }, /*2072X_PORTF_GAIN_LEFT */ ++ { 0x5c14, 0x00000433 }, /*2072X_PORTG_POWER_STATE */ ++ { 0x5c1c, 0x00000040 }, /*2072X_PORTG_PIN_CTRL */ ++ { 0x5c04, 0x00000000 }, /*2072X_PORTG_CONNECTION_SELECT_CTRL */ ++ { 0x5c30, 0x00000002 }, /*2072X_PORTG_EAPD_BTL */ ++ { 0x8814, 0x00000433 }, /*2072X_PORTM_POWER_STATE */ ++ { 0x881c, 0x00000000 }, /*2072X_PORTM_PIN_CTRL */ ++ { 0x8804, 0x00000000 }, /*2072X_PORTM_CONNECTION_SELECT_CTRL */ ++ { 0x8830, 0x00000002 }, /*2072X_PORTM_EAPD_BTL */ ++ { 0x5414, 0x00000433 }, /*2072X_MIXER_POWER_STATE */ ++ { 0x5580, 0x0000004a }, /*2072X_MIXER_GAIN_RIGHT_0 */ ++ { 0x55a0, 0x0000004a }, /*2072X_MIXER_GAIN_LEFT_0 */ ++ { 0x5584, 0x0000004a }, /*2072X_MIXER_GAIN_RIGHT_1 */ ++ { 0x55a4, 0x0000004a }, /*2072X_MIXER_GAIN_LEFT_1 */ ++ { 0x6d00, 0x0000720c }, /*2072X_EQ_ENABLE_BYPASS */ ++ { 0x6d10, 0x040065a4 }, /*2072X_SPKR_DRC_ENABLE_STEP */ ++ { 0x6d14, 0x007b0024 }, /*2072X_SPKR_DRC_CONTROL */ ++ { 0X6D18, 0x00000000 }, /*2072X_SPKR_DRC_TEST */ ++ { 0x6d80, 0x001f008a }, /*2072X_DIGITAL_BIOS_TEST0 */ ++ { 0x6d84, 0x00990026 }, /*2072X_DIGITAL_BIOS_TEST2 */ ++ { 0x6e00, 0x00010001 }, /*2072X_I2SPCM_CONTROL1 */ ++ { 0x6e04, 0x00000000 }, /*2072X_I2SPCM_CONTROL2 */ ++ { 0x6e08, 0x00000000 }, /*2072X_I2SPCM_CONTROL3 */ ++ { 0x6e0c, 0x00000000 }, /*2072X_I2SPCM_CONTROL4 */ ++ { 0x6e10, 0x00000000 }, /*2072X_I2SPCM_CONTROL5 */ ++ { 0x6e18, 0x00000000 }, /*2072X_I2SPCM_CONTROL6 */ ++ { 0x6e14, 0x00000000 }, /*2072X_UM_INTERRUPT_CRTL_E */ ++ { 0x7310, 0x00000600 }, /*2072X_CODEC_TEST20 */ ++ { 0x7328, 0x00000208 }, /*2072X_CODEC_TEST26 */ ++ { 0x7190, 0x00000000 }, /*2072X_ANALOG_TEST4 */ ++ { 0x7194, 0x00000000 }, /*2072X_ANALOG_TEST5 */ ++ { 0x7198, 0x0000059a }, /*2072X_ANALOG_TEST6 */ ++ { 0x719c, 0x000000a7 }, /*2072X_ANALOG_TEST7 */ ++ { 0x71a0, 0x00000017 }, /*2072X_ANALOG_TEST8 */ ++ { 0x71a4, 0x00000000 }, /*2072X_ANALOG_TEST9 */ ++ { 0x71a8, 0x00000285 }, /*2072X_ANALOG_TEST10 */ ++ { 0x71ac, 0x00000000 }, /*2072X_ANALOG_TEST11 */ ++ { 0x71b0, 0x00000000 }, /*2072X_ANALOG_TEST12 */ ++ { 0x71b4, 0x00000000 }, /*2072X_ANALOG_TEST13 */ ++ { 0x7204, 0x00000242 }, /*2072X_DIGITAL_TEST1 */ ++ { 0x7224, 0x00000000 }, /*2072X_DIGITAL_TEST11 */ ++ { 0x7230, 0x00000084 }, /*2072X_DIGITAL_TEST12 */ ++ { 0x723c, 0x00000077 }, /*2072X_DIGITAL_TEST15 */ ++ { 0x7080, 0x00000021 }, /*2072X_DIGITAL_TEST16 */ ++ { 0x7084, 0x00000018 }, /*2072X_DIGITAL_TEST17 */ ++ { 0x7088, 0x00000024 }, /*2072X_DIGITAL_TEST18 */ ++ { 0x708c, 0x00000001 }, /*2072X_DIGITAL_TEST19 */ ++ { 0x7090, 0x00000002 }, /*2072X_DIGITAL_TEST20 */ ++}; ++ ++ ++/* ++ * cx2072x patch. ++ */ ++static const struct reg_sequence cx2072x_patch[] = { ++ { 0x71A4, 0x080 }, /* DC offset Calibration */ ++ { 0x71a8, 0x287 }, /* Set max spk power to 1.5 W */ ++ { 0x7328, 0xa8c }, /* Set average spk power to 1.5W*/ ++ { 0x7310, 0xf01 }, /* */ ++ { 0x7328, 0xa8f }, /* */ ++ { 0x7124, 0x001 }, /* Enable 30 Hz High pass filter*/ ++ { 0x718c, 0x300 }, /* Disable PCBEEP pad */ ++ { 0x731c, 0x100 }, /* Disable SnM mode */ ++ { 0x641c, 0x020 }, /* Enable PortD input */ ++ { 0x0458, 0x040 }, /* Enable GPIO7 pin for button */ ++ { 0x0464, 0x040 }, /* Enable UM for GPIO7 */ ++ { 0x0420, 0x080 }, /* Enable button response */ ++ { 0x7230, 0x0c4 }, /* Enable headset button */ ++ { 0x7200, 0x415 }, /* Power down class-d during idle*/ ++}; ++ ++ ++/* return register size */ ++static unsigned int cx2072x_register_size(struct device *dev, ++ unsigned int reg) ++{ ++ switch (reg) { ++ case CX2072X_VENDOR_ID: ++ case CX2072X_REVISION_ID: ++ case CX2072X_PORTA_PIN_SENSE: ++ case CX2072X_PORTB_PIN_SENSE: ++ case CX2072X_PORTD_PIN_SENSE: ++ case CX2072X_PORTE_PIN_SENSE: ++ case CX2072X_PORTF_PIN_SENSE: ++ case CX2072X_I2SPCM_CONTROL1: ++ case CX2072X_I2SPCM_CONTROL2: ++ case CX2072X_I2SPCM_CONTROL3: ++ case CX2072X_I2SPCM_CONTROL4: ++ case CX2072X_I2SPCM_CONTROL5: ++ case CX2072X_I2SPCM_CONTROL6: ++ case CX2072X_UM_INTERRUPT_CRTL_E: ++ case CX2072X_EQ_G_COEFF: ++ /*case CX2072X_SPKR_DRC_ENABLE_STEP:*/ ++ case CX2072X_SPKR_DRC_CONTROL: ++ case CX2072X_SPKR_DRC_TEST: ++ case CX2072X_DIGITAL_BIOS_TEST0: ++ case CX2072X_DIGITAL_BIOS_TEST2: ++ return 4; ++ case CX2072X_EQ_ENABLE_BYPASS: ++ case CX2072X_EQ_B0_COEFF: ++ case CX2072X_EQ_B1_COEFF: ++ case CX2072X_EQ_B2_COEFF: ++ case CX2072X_EQ_A1_COEFF: ++ case CX2072X_EQ_A2_COEFF: ++ case CX2072X_DAC1_CONVERTER_FORMAT: ++ case CX2072X_DAC2_CONVERTER_FORMAT: ++ case CX2072X_ADC1_CONVERTER_FORMAT: ++ case CX2072X_ADC2_CONVERTER_FORMAT: ++ case CX2072X_CODEC_TEST20: ++ case CX2072X_CODEC_TEST26: ++ case CX2072X_ANALOG_TEST3: ++ case CX2072X_ANALOG_TEST4: ++ case CX2072X_ANALOG_TEST5: ++ case CX2072X_ANALOG_TEST6: ++ case CX2072X_ANALOG_TEST7: ++ case CX2072X_ANALOG_TEST8: ++ case CX2072X_ANALOG_TEST9: ++ case CX2072X_ANALOG_TEST10: ++ case CX2072X_ANALOG_TEST11: ++ case CX2072X_ANALOG_TEST12: ++ case CX2072X_ANALOG_TEST13: ++ case CX2072X_DIGITAL_TEST0: ++ case CX2072X_DIGITAL_TEST1: ++ case CX2072X_DIGITAL_TEST11: ++ case CX2072X_DIGITAL_TEST12: ++ case CX2072X_DIGITAL_TEST15: ++ case CX2072X_DIGITAL_TEST16: ++ case CX2072X_DIGITAL_TEST17: ++ case CX2072X_DIGITAL_TEST18: ++ case CX2072X_DIGITAL_TEST19: ++ case CX2072X_DIGITAL_TEST20: ++ return 2; ++ default: ++ return 1; ++ } ++ ++} ++ ++#ifdef CXDBG_REG_DUMP ++static const char *cx2072x_get_reg_name(struct device *dev, unsigned int reg) ++{ ++ int i; ++ ++ for (i = 0; i < ARRAY_SIZE(cx2072x_regs); i++) ++ if (cx2072x_regs[i].addr == reg) ++ return cx2072x_regs[i].name; ++ ++ dev_err(dev, "Unkown reg %08x\n", reg); ++ ++ return "Unknown reg"; ++} ++#endif ++ ++static int cx2072x_reg_write(void *context, unsigned int reg, ++ unsigned int value) ++{ ++ struct i2c_client *client = context; ++ unsigned int i; ++ unsigned int size; ++ uint8_t buf[6]; ++ int ret; ++ struct device *dev = &client->dev; ++ ++ size = cx2072x_register_size(dev, reg); ++ if (size == 0) ++ return -EINVAL; ++#ifdef CXDBG_REG_DUMP ++ dev_dbg(dev, "I2C write address %40s,%d <= %08x\n", ++ cx2072x_get_reg_name(dev, reg), size, value); ++#endif ++ ++ if (reg == CX2072X_UM_INTERRUPT_CRTL_E) { ++ /* workaround to update the MSB byte only */ ++ reg += 3; ++ size = 1; ++ value >>= 24; ++ } ++ ++ buf[0] = reg >> 8; ++ buf[1] = reg & 0xff; ++ ++ for (i = 2; i < (size + 2); ++i) { ++ buf[i] = value; ++ value >>= 8; ++ } ++ ++ ret = i2c_master_send(client, buf, size + 2); ++ if (ret == size + 2) { ++ ret = 0; ++ } else if (ret < 0) { ++ dev_err(dev, ++ "I2C write address failed\n"); ++ } else { ++ dev_err(dev, ++ "I2C write failed\n"); ++ ret = -EIO; ++ } ++ return ret; ++} ++ ++static int cx2072x_reg_bulk_write(struct snd_soc_codec *codec, ++ unsigned int reg, const void *val, size_t val_count) ++{ ++ /*fix me*/ ++ struct i2c_client *client = to_i2c_client(codec->dev); ++ uint8_t buf[2 + MAC_EQ_COEFF]; ++ int ret; ++ struct device *dev = &client->dev; ++ ++#ifdef CXDBG_REG_DUMP ++ dev_dbg(dev, "I2C bulk write address %40s,%zd\n", ++ cx2072x_get_reg_name(dev, reg), val_count); ++#endif ++ ++ if (val_count > MAC_EQ_COEFF) { ++ dev_err(dev, ++ "cx2072x_reg_bulk_write failed, writing count = %zd\n", ++ val_count); ++ return -EINVAL; ++ } ++ ++ buf[0] = reg >> 8; ++ buf[1] = reg & 0xff; ++ ++ memcpy(&buf[2], val, val_count); ++ ++ ret = i2c_master_send(client, buf, val_count + 2); ++ if (ret == val_count + 2) ++ return 0; ++ else if (ret < 0) { ++ dev_err(dev, ++ "I2C bulk write address failed\n"); ++ } else { ++ dev_err(dev, ++ "I2C bulk write address failed\n"); ++ ret = -EIO; ++ } ++ return ret; ++} ++ ++ ++/* get suggested pre_div valuce from mclk frequency */ ++static unsigned int get_div_from_mclk(unsigned int mclk) ++{ ++ unsigned int div = 8; ++ int i = 0; ++ ++ for (i = 0; i < ARRAY_SIZE(MCLK_PRE_DIV); i++) { ++ if (mclk <= MCLK_PRE_DIV[i].mclk) { ++ div = MCLK_PRE_DIV[i].div; ++ break; ++ } ++ } ++ return div; ++} ++static int cx2072x_reg_read(void *context, unsigned int reg, ++ unsigned int *value) ++{ ++ int ret; ++ unsigned int size; ++ uint8_t send_buf[2]; ++ unsigned int recv_buf = 0; ++ struct i2c_client *client = context; ++ struct i2c_msg msgs[2]; ++ struct device *dev = &client->dev; ++ ++ size = cx2072x_register_size(dev, reg); ++ if (size == 0) ++ return -EINVAL; ++ ++ send_buf[0] = reg >> 8; ++ send_buf[1] = reg & 0xff; ++ ++ msgs[0].addr = client->addr; ++ msgs[0].len = sizeof(send_buf); ++ msgs[0].buf = send_buf; ++ msgs[0].flags = 0; ++ ++ msgs[1].addr = client->addr; ++ msgs[1].len = size; ++ msgs[1].buf = (uint8_t *)&recv_buf; ++ msgs[1].flags = I2C_M_RD; ++ ++ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); ++ if (ret < 0) { ++ dev_err(dev, ++ "Failed to register codec: %d\n", ret); ++ return ret; ++ } else if (ret != ARRAY_SIZE(msgs)) { ++ dev_err(dev, ++ "Failed to register codec: %d\n", ret); ++ return -EIO; ++ } ++ ++ *value = recv_buf; ++ ++#ifdef CXDBG_REG_DUMP ++ dev_dbg(dev, ++ "I2C read address %40s,%d => %08x\n", ++ cx2072x_get_reg_name(dev, reg), size, *value); ++#endif ++ return 0; ++} ++ ++static int cx2072x_config_headset_det(struct cx2072x_priv *cx2072x) ++{ ++ const int interrupt_gpio_pin = 1; ++ ++ dev_dbg(cx2072x->dev, ++ "Configure interrupt pin: %d\n", interrupt_gpio_pin); ++ /*No-sticky input type*/ ++ regmap_write(cx2072x->regmap, CX2072X_GPIO_STICKY_MASK, 0x1f); ++ /*Use GPOI0 as intrrupt output pin*/ ++ regmap_write(cx2072x->regmap, CX2072X_UM_INTERRUPT_CRTL_E, 0x12 << 24); ++ ++ /* Enables unsolitited message on PortA*/ ++ regmap_write(cx2072x->regmap, CX2072X_PORTA_UNSOLICITED_RESPONSE, 0x80); ++ ++ /* support both nokia and apple headset set. Monitor time = 275 ms*/ ++ regmap_write(cx2072x->regmap, CX2072X_DIGITAL_TEST15, 0x73); ++ ++ /* Disable TIP detection*/ ++ regmap_write(cx2072x->regmap, CX2072X_ANALOG_TEST12, 0x300); ++ ++ /* Switch MusicD3Live pin to GPIO */ ++ regmap_write(cx2072x->regmap, CX2072X_DIGITAL_TEST1, 0); ++ ++ return 0; ++} ++ ++static int cx2072x_config_pll(struct cx2072x_priv *cx2072x) ++{ ++ struct device *dev = cx2072x->dev; ++ unsigned int pre_div; ++ unsigned int pre_div_val; ++ unsigned int pll_input; ++ unsigned int pll_output; ++ unsigned int int_div; ++ unsigned int frac_div; ++ unsigned int frac_num = 0; ++ unsigned int sample_rate = cx2072x->sample_rate; ++ int pt_sample_per_sync = 2; ++ int pt_clock_per_sample = 96; ++ ++ switch (sample_rate) { ++ case 48000: ++ case 32000: ++ case 24000: ++ case 16000: ++ break; ++ case 96000: ++ pt_sample_per_sync = 1; ++ pt_clock_per_sample = 48; ++ break; ++ case 192000: ++ pt_sample_per_sync = 0; ++ pt_clock_per_sample = 24; ++ break; ++ default: ++ dev_err(dev, "Unsupported sample rate %d\n", sample_rate); ++ return -EINVAL; ++ } ++ ++ ++ /*Configure PLL settings*/ ++ pre_div = get_div_from_mclk(cx2072x->mclk); ++ pll_input = cx2072x->mclk / pre_div; ++ pll_output = sample_rate * 3072; ++ int_div = pll_output / pll_input; ++ frac_div = pll_output - (int_div * pll_input); ++ ++ if (frac_div) { ++ frac_div *= 1000; ++ frac_div /= pll_input; ++ frac_num = ((4000 + frac_div) * ((1 << 20) - 4)); ++ do_div(frac_num, 7); ++ frac_num = (frac_num + 499) / 1000; ++ } ++ pre_div_val = (pre_div - 1) * 2; ++ ++ regmap_write(cx2072x->regmap, CX2072X_ANALOG_TEST4, 0X40 ++ | (pre_div_val << 8)); ++ if (frac_div == 0) { ++ /*Int mode*/ ++ regmap_write(cx2072x->regmap, CX2072X_ANALOG_TEST7, 0x100); ++ } else { ++ /*frac mode*/ ++ regmap_write(cx2072x->regmap, CX2072X_ANALOG_TEST6, ++ frac_num & 0xfff); ++ regmap_write(cx2072x->regmap, CX2072X_ANALOG_TEST7, ++ (unsigned char)(frac_num >> 12)); ++ } ++ int_div--; ++ regmap_write(cx2072x->regmap, CX2072X_ANALOG_TEST8, ++ (unsigned char)int_div & 0xffff); ++ ++ /* configure PLL tracking*/ ++ if (frac_div == 0) { ++ /* disable PLL tracking*/ ++ regmap_write(cx2072x->regmap, CX2072X_DIGITAL_TEST16, 0x00); ++ } else { ++ /* configure and enable PLL tracking*/ ++ regmap_write(cx2072x->regmap, CX2072X_DIGITAL_TEST16, ++ (pt_sample_per_sync << 4) & 0xf0); ++ regmap_write(cx2072x->regmap, CX2072X_DIGITAL_TEST17, ++ pt_clock_per_sample); ++ regmap_write(cx2072x->regmap, CX2072X_DIGITAL_TEST18, ++ pt_clock_per_sample * 3 / 2); ++ regmap_write(cx2072x->regmap, CX2072X_DIGITAL_TEST19, 0x01); ++ regmap_write(cx2072x->regmap, CX2072X_DIGITAL_TEST20, 0x02); ++ regmap_update_bits(cx2072x->regmap, CX2072X_DIGITAL_TEST16, ++ 0X01, 0X01); ++ } ++ ++ return 0; ++} ++ ++ ++static int cx2072x_config_i2spcm(struct cx2072x_priv *cx2072x) ++{ ++ struct device *dev = cx2072x->dev; ++ ++ int is_i2s = 0; ++ int has_one_bit_delay = 0; ++ int is_right_j = 0; ++ int is_frame_inv = 0; ++ int is_bclk_inv = 0; ++ int pulse_len = 1; ++ int frame_len = cx2072x->frame_size; ++ int sample_size = cx2072x->sample_size; ++ int i2s_right_slot; ++ int i2s_right_pause_interval = 0; ++ int i2s_right_pause_pos; ++ int is_big_endian = 1; ++ const int slots_per_channel = cx2072x->tdm_slot_width / BITS_PER_SLOT; ++ const unsigned fmt = cx2072x->dai_fmt; ++ ++ REG_I2SPCM_CTRL_REG1 reg1 = { .ulVal = 0 }; ++ REG_I2SPCM_CTRL_REG2 reg2 = { .ulVal = 0 }; ++ REG_I2SPCM_CTRL_REG3 reg3 = { .ulVal = 0 }; ++ REG_I2SPCM_CTRL_REG4 reg4 = { .ulVal = 0 }; ++ REG_I2SPCM_CTRL_REG5 reg5 = { .ulVal = 0 }; ++ REG_I2SPCM_CTRL_REG6 reg6 = { .ulVal = 0 }; ++ REG_DIGITAL_BIOS_TEST2 regDBT2 = { .ulVal = 0 }; ++ ++ if (frame_len <= 0) { ++ dev_err(dev, "Incorrect frame len %d\n", frame_len); ++ return -EINVAL; ++ } ++ ++ if (sample_size <= 0) { ++ dev_err(dev, "Incorrect sample size %d\n", sample_size); ++ return -EINVAL; ++ } ++ ++ /*fix me */ ++ regDBT2.ulVal = 0xac; ++ ++ /* set master/slave */ ++ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { ++ case SND_SOC_DAIFMT_CBM_CFM: ++ reg2.r.tx_master = 1; ++ reg3.r.rx_master = 1; ++ dev_err(dev, "DAI master mode is not implemented yet.\n"); ++ return -EINVAL; ++ case SND_SOC_DAIFMT_CBS_CFS: ++ break; ++ default: ++ dev_err(dev, "Unsupported DAI master mode\n"); ++ return -EINVAL; ++ } ++ ++ /* set format */ ++ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { ++ case SND_SOC_DAIFMT_I2S: ++ is_i2s = 1; ++ has_one_bit_delay = 1; ++ pulse_len = frame_len / 2; ++ break; ++ case SND_SOC_DAIFMT_RIGHT_J: ++ is_i2s = 1; ++ is_right_j = 1; ++ pulse_len = frame_len / 2; ++ break; ++ case SND_SOC_DAIFMT_LEFT_J: ++ is_i2s = 1; ++ pulse_len = frame_len / 2; ++ break; ++ case SND_SOC_DAIFMT_DSP_A: ++ has_one_bit_delay = 1; ++ break; ++ case SND_SOC_DAIFMT_DSP_B: ++ break; ++ default: ++ dev_err(dev, "Unsupported DAI format\n"); ++ return -EINVAL; ++ } ++ ++ /* clock inversion */ ++ switch (fmt & SND_SOC_DAIFMT_INV_MASK) { ++ case SND_SOC_DAIFMT_NB_NF: ++ is_frame_inv = is_i2s ? 1 : 0; ++ is_bclk_inv = is_i2s ? 1 : 0; ++ break; ++ case SND_SOC_DAIFMT_IB_IF: ++ is_frame_inv = is_i2s ? 0 : 1; ++ is_bclk_inv = is_i2s ? 0 : 1; ++ break; ++ case SND_SOC_DAIFMT_IB_NF: ++ is_frame_inv = is_i2s ? 1 : 0; ++ is_bclk_inv = is_i2s ? 0 : 1; ++ break; ++ case SND_SOC_DAIFMT_NB_IF: ++ is_frame_inv = is_i2s ? 0 : 1; ++ is_bclk_inv = is_i2s ? 1 : 0; ++ break; ++ default: ++ dev_err(dev, "Unsupported DAI clock inversion\n"); ++ return -EINVAL; ++ } ++ ++ cx2072x->dai_fmt = fmt; ++ ++ reg1.r.rx_data_one_line = 1; ++ reg1.r.tx_data_one_line = 1; ++ ++ if (is_i2s) { ++ i2s_right_slot = (frame_len / 2) / BITS_PER_SLOT; ++ i2s_right_pause_interval = (frame_len / 2) % BITS_PER_SLOT; ++ i2s_right_pause_pos = i2s_right_slot * BITS_PER_SLOT; ++ } ++ ++ reg1.r.rx_ws_pol = is_frame_inv; ++ reg1.r.rx_ws_wid = pulse_len - 1; ++ ++ reg1.r.rx_frm_len = frame_len / BITS_PER_SLOT - 1; ++ reg1.r.rx_sa_size = (sample_size / BITS_PER_SLOT) - 1; ++ ++ reg1.r.tx_ws_pol = reg1.r.rx_ws_pol; ++ reg1.r.tx_ws_wid = pulse_len - 1; ++ reg1.r.tx_frm_len = reg1.r.rx_frm_len; ++ reg1.r.tx_sa_size = reg1.r.rx_sa_size; ++ ++ reg2.r.tx_endian_sel = is_big_endian ? 0 : 1; ++ reg2.r.tx_dstart_dly = has_one_bit_delay; ++ ++ reg3.r.rx_endian_sel = is_big_endian ? 0 : 1; ++ reg3.r.rx_dstart_dly = has_one_bit_delay; ++ ++ if (is_i2s) { ++ reg2.r.tx_en_ch1 = 1; ++ reg2.r.tx_en_ch2 = 1; ++ reg2.r.tx_slot_1 = 0; ++ reg2.r.tx_slot_2 = i2s_right_slot; ++ reg3.r.rx_en_ch1 = 1; ++ reg3.r.rx_en_ch2 = 1; ++ reg3.r.rx_slot_1 = 0; ++ reg3.r.rx_slot_2 = i2s_right_slot; ++ reg6.r.rx_pause_start_pos = i2s_right_pause_pos; ++ reg6.r.rx_pause_cycles = i2s_right_pause_interval; ++ reg6.r.tx_pause_start_pos = i2s_right_pause_pos; ++ reg6.r.tx_pause_cycles = i2s_right_pause_interval; ++ } else { ++ reg2.r.tx_en_ch1 = cx2072x->tdm_tx_mask & 0x01 ? 1 : 0; ++ reg2.r.tx_en_ch2 = cx2072x->tdm_tx_mask & 0x02 ? 1 : 0; ++ reg2.r.tx_en_ch3 = cx2072x->tdm_tx_mask & 0x04 ? 1 : 0; ++ reg2.r.tx_en_ch4 = cx2072x->tdm_tx_mask & 0x08 ? 1 : 0; ++ reg2.r.tx_slot_1 = 0; ++ reg2.r.tx_slot_2 = slots_per_channel * 1; ++ reg2.r.tx_slot_3 = slots_per_channel * 2; ++ reg2.r.tx_slot_4 = slots_per_channel * 3; ++ ++ reg3.r.rx_en_ch1 = cx2072x->tdm_rx_mask & 0x01 ? 1 : 0; ++ reg3.r.rx_en_ch2 = cx2072x->tdm_rx_mask & 0x02 ? 1 : 0; ++ reg3.r.rx_en_ch3 = cx2072x->tdm_rx_mask & 0x04 ? 1 : 0; ++ reg3.r.rx_en_ch4 = cx2072x->tdm_rx_mask & 0x08 ? 1 : 0; ++ reg3.r.rx_slot_1 = 0; ++ reg3.r.rx_slot_2 = slots_per_channel * 1; ++ reg3.r.rx_slot_3 = slots_per_channel * 2; ++ reg3.r.rx_slot_4 = slots_per_channel * 3; ++ } ++ regDBT2.r.i2s_bclk_invert = is_bclk_inv; ++ ++ reg1.r.rx_data_one_line = 1; ++ reg1.r.tx_data_one_line = 1; ++ ++#ifdef ENABLE_MIC_POP_WA ++ /*Mute I2S TX*/ ++ reg4.ulVal |= 0x2; ++#endif ++ regmap_write(cx2072x->regmap, CX2072X_I2SPCM_CONTROL1, reg1.ulVal); ++ regmap_write(cx2072x->regmap, CX2072X_I2SPCM_CONTROL2, reg2.ulVal); ++ regmap_write(cx2072x->regmap, CX2072X_I2SPCM_CONTROL3, reg3.ulVal); ++ regmap_write(cx2072x->regmap, CX2072X_I2SPCM_CONTROL4, reg4.ulVal); ++ regmap_write(cx2072x->regmap, CX2072X_I2SPCM_CONTROL5, reg5.ulVal); ++ regmap_write(cx2072x->regmap, CX2072X_I2SPCM_CONTROL6, reg6.ulVal); ++ /*enable bclk and EAPD input*/ ++ if (cx2072x->rev_id == CX2072X_REV_A2) ++ regmap_update_bits(cx2072x->regmap, CX2072X_DIGITAL_BIOS_TEST2, ++ 0x84, 0xFF); ++ else ++ regmap_write(cx2072x->regmap, CX2072X_DIGITAL_BIOS_TEST2, ++ regDBT2.ulVal); ++ ++ return 0; ++} ++ ++ ++static void cx2072x_dsp_init(struct snd_soc_codec *codec) ++{ ++ struct cx2072x_priv *cx2072x = get_cx2072x_priv(codec); ++ int i, j, band; ++ unsigned char *pCoef = &cx2072x_eq_coeff_array[0][0]; ++ ++ regmap_write(cx2072x->regmap, CX2072X_EQ_ENABLE_BYPASS, 0x6e0f); ++ ++ for (i = 0; i < MAX_EQ_BAND; i++) { ++ for (j = 0; j < 2; j++) { ++ cx2072x_reg_bulk_write(codec, CX2072X_EQ_B0_COEFF, ++ pCoef + (MAC_EQ_COEFF*i), MAC_EQ_COEFF); ++ band = i + (j << 3) + (1 << 6); ++ regmap_write(cx2072x->regmap, CX2072X_EQ_BAND, band); ++ mdelay(5); ++ } ++ } ++ ++ cx2072x_reg_bulk_write(codec, CX2072X_SPKR_DRC_ENABLE_STEP, ++ cx2072x_drc_array, MAX_DRC_REGS); ++} ++static void cx2072x_update_dsp(struct snd_soc_codec *codec) ++{ ++ unsigned int afg_reg; ++ struct cx2072x_priv *cx2072x = get_cx2072x_priv(codec); ++ ++ regmap_read(cx2072x->regmap, CX2072X_AFG_POWER_STATE, &afg_reg); ++ ++ if (!cx2072x->plbk_dsp_changed) { ++ /*nothing change*/ ++ return; ++ } ++ ++ if ((afg_reg & 0xf) != 0) { ++ /*skip since device is on D3 mode*/ ++ return; ++ } ++ ++ if (cx2072x->plbk_dsp_en && !cx2072x->plbk_dsp_init) { ++ cx2072x_dsp_init(codec); ++ cx2072x->plbk_dsp_init = true; ++ } ++ ++ if (cx2072x->plbk_dsp_en) { ++ regmap_write(cx2072x->regmap, CX2072X_EQ_ENABLE_BYPASS, ++ 0x6203); ++ regmap_write(cx2072x->regmap, CX2072X_SPKR_DRC_ENABLE_STEP, ++ 0x65); ++ } else { ++ /*By pass DRC and EQ.*/ ++ regmap_write(cx2072x->regmap, CX2072X_EQ_ENABLE_BYPASS, ++ 0x620c); ++ regmap_write(cx2072x->regmap, CX2072X_SPKR_DRC_ENABLE_STEP, ++ 0xa4); ++ } ++ cx2072x->plbk_dsp_changed = false; ++} ++ ++ ++ ++static int afg_power_ev(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); ++ struct cx2072x_priv *cx2072x = get_cx2072x_priv(codec); ++ ++ switch (event) { ++ case SND_SOC_DAPM_POST_PMU: ++ regmap_update_bits(cx2072x->regmap, CX2072X_DIGITAL_BIOS_TEST0, ++ 0x00, 0x10); ++ break; ++ ++ case SND_SOC_DAPM_PRE_PMD: ++ regmap_update_bits(cx2072x->regmap, CX2072X_DIGITAL_BIOS_TEST0, ++ 0x10, 0x10); ++ break; ++ } ++ ++ return 0; ++} ++ ++#ifdef ENABLE_MIC_POP_WA ++/* ++ * This work will be called at ADC widget power on time. ++ * to reduce initial mic pop noise caused by hardware ++ */ ++ ++static void cx2072x_anit_mic_pop_work(struct work_struct *work) ++{ ++ struct snd_soc_dapm_context *dapm = ++ container_of(work, struct snd_soc_dapm_context, ++ delayed_work.work); ++ struct snd_soc_codec *codec = snd_soc_dapm_to_codec(dapm); ++ struct cx2072x_priv *cx2072x = get_cx2072x_priv(codec); ++ ++ dev_dbg(cx2072x->dev, "Unmute I2S TX\n"); ++ /*Unmute I2S TX*/ ++ ++ regmap_update_bits(cx2072x->regmap, CX2072X_I2SPCM_CONTROL4, ++ 0x2, 0x0); ++} ++#endif ++ ++static int adc1_power_ev(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++#ifdef ENABLE_MIC_POP_WA ++ struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); ++ struct cx2072x_priv *cx2072x = get_cx2072x_priv(codec); ++ ++ switch (event) { ++ case SND_SOC_DAPM_POST_PMU: ++ /* Umute I2S TX after 300 ms to get around the mic ++ * pop noise issue. ++ */ ++ schedule_delayed_work(&codec->dapm.delayed_work, ++ msecs_to_jiffies(300)); ++ break; ++ ++ case SND_SOC_DAPM_POST_PMD: ++ /*Mute TX I2S*/ ++ ++ regmap_update_bits(cx2072x->regmap, CX2072X_I2SPCM_CONTROL4, ++ 0x2, 0x2); ++ break; ++ } ++#endif ++ return 0; ++} ++ ++ ++static int portg_power_ev(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); ++ ++ switch (event) { ++ case SND_SOC_DAPM_POST_PMU: ++ cx2072x_update_dsp(codec); ++ break; ++ default: ++ break; ++ } ++ return 0; ++} ++static int cx2072x_plbk_dsp_info(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_info *uinfo) ++{ ++ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; ++ uinfo->count = 1; ++ uinfo->value.integer.min = 0; ++ uinfo->value.integer.max = 1; ++ return 0; ++} ++ ++static int cx2072x_plbk_dsp_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); ++ struct cx2072x_priv *cx2072x = get_cx2072x_priv(codec); ++ ++ ucontrol->value.integer.value[0] = cx2072x->plbk_dsp_en; ++ ++ return 0; ++} ++ ++static int cx2072x_plbk_dsp_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); ++ struct cx2072x_priv *cx2072x = get_cx2072x_priv(codec); ++ const bool en_dsp = ucontrol->value.integer.value[0]; ++ ++ if (ucontrol->value.integer.value[0] > 1) ++ return -EINVAL; ++ ++ if (cx2072x->plbk_dsp_en != en_dsp) { ++ cx2072x->plbk_dsp_en = en_dsp; ++ cx2072x->plbk_dsp_changed = true; ++ cx2072x_update_dsp(codec); ++ } ++ return 0; ++} ++ ++ ++#define CX2072X_PLBK_DSP_SWITCH(xname) {\ ++ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ ++ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,\ ++ .info = cx2072x_plbk_dsp_info, \ ++ .get = cx2072x_plbk_dsp_get, .put = cx2072x_plbk_dsp_put} ++ ++static const struct snd_kcontrol_new cx2072x_snd_controls[] = { ++ ++ SOC_DOUBLE_R_TLV("PortD Boost", CX2072X_PORTD_GAIN_LEFT, ++ CX2072X_PORTD_GAIN_RIGHT, 0, 3, 0, boost_tlv), ++ SOC_DOUBLE_R_TLV("PortC Boost", CX2072X_PORTC_GAIN_LEFT, ++ CX2072X_PORTC_GAIN_RIGHT, 0, 3, 0, boost_tlv), ++ SOC_DOUBLE_R_TLV("PortB Boost", CX2072X_PORTB_GAIN_LEFT, ++ CX2072X_PORTB_GAIN_RIGHT, 0, 3, 0, boost_tlv), ++ ++ SOC_DOUBLE_R_TLV("PortD ADC1 Volume", CX2072X_ADC1_AMP_GAIN_LEFT_1, ++ CX2072X_ADC1_AMP_GAIN_RIGHT_1, 0, 0x4a, 0, adc_tlv), ++ SOC_DOUBLE_R_TLV("PortC ADC1 Volume", CX2072X_ADC1_AMP_GAIN_LEFT_2, ++ CX2072X_ADC1_AMP_GAIN_RIGHT_2, 0, 0x4a, 0, adc_tlv), ++ SOC_DOUBLE_R_TLV("PortB ADC1 Volume", CX2072X_ADC1_AMP_GAIN_LEFT_0, ++ CX2072X_ADC1_AMP_GAIN_RIGHT_0, 0, 0x4a, 0, adc_tlv), ++ ++ SOC_DOUBLE_R_TLV("DAC1 Volume", CX2072X_DAC1_AMP_GAIN_LEFT, ++ CX2072X_DAC1_AMP_GAIN_RIGHT, 0, 0x4a, 0, dac_tlv), ++ ++ SOC_DOUBLE_R("DAC1 Mute", CX2072X_DAC1_AMP_GAIN_LEFT, ++ CX2072X_DAC1_AMP_GAIN_RIGHT, 7, 1, 0), ++ ++ SOC_DOUBLE_R_TLV("DAC2 Volume", CX2072X_DAC2_AMP_GAIN_LEFT, ++ CX2072X_DAC2_AMP_GAIN_RIGHT, 0, 0x4a, 0, dac_tlv), ++ ++ CX2072X_PLBK_DSP_SWITCH("Playback DSP Switch"), ++}; ++ ++/** ++* cx2072x_hs_jack_report: Report jack notification to upper layer ++* @codec : pointer variable to codec having information related to codec ++* @jack : Pointer variable to snd_soc_jack having information of codec ++* and pin number$ ++* @report : Provides informaton of whether it is headphone or microphone ++* ++*/ ++int cx2072x_hs_jack_report(struct snd_soc_codec *codec) ++{ ++ unsigned int jack; ++ unsigned int type = 0; ++ int state = 0; ++ struct cx2072x_priv *cx2072x = get_cx2072x_priv(codec); ++ ++ ++ regcache_cache_bypass(cx2072x->regmap, true); ++ cx2072x->jack_state = CX_JACK_NONE; ++ regmap_read(cx2072x->regmap, CX2072X_PORTA_PIN_SENSE, &jack); ++ jack = jack >> 24; ++ regmap_read(cx2072x->regmap, CX2072X_DIGITAL_TEST11, &type); ++ regcache_cache_bypass(cx2072x->regmap, false); ++ if (jack == 0x80) { ++ type = type >> 8; ++ ++ if (type & 0x8) { ++ state |= SND_JACK_HEADSET; ++ cx2072x->jack_state = CX_JACK_APPLE_HEADSET; ++ if (type & 0x2) ++ state |= SND_JACK_BTN_0; ++ } else if (type & 0x4) { ++ state |= SND_JACK_HEADPHONE; ++ cx2072x->jack_state = CX_JACK_NOKIE_HEADSET; ++ } else { ++ state |= SND_JACK_HEADPHONE; ++ cx2072x->jack_state = CX_JACK_HEADPHONE; ++ } ++ ++ } ++ ++ ++ /* clear interrupt */ ++ regmap_write(cx2072x->regmap, CX2072X_UM_INTERRUPT_CRTL_E, 0x12 << 24); ++ ++ dev_err(codec->dev, "CX2072X_HSDETECT type=0x%X,Jack state = %x\n", ++ type, state); ++ return state; ++} ++EXPORT_SYMBOL_GPL(cx2072x_hs_jack_report); ++ ++ ++ ++ ++ ++static int cx2072x_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, ++ unsigned int rx_mask, int slots, int slot_width) ++{ ++ struct snd_soc_codec *codec = dai->codec; ++ struct cx2072x_priv *cx2072x = snd_soc_codec_get_drvdata(codec); ++ ++ if (slots == 0) ++ goto out; ++ ++ ++ switch (rx_mask) { ++ case 1 ... 0xf: ++ default: ++ return -EINVAL; ++ } ++ ++ ++ switch (tx_mask) { ++ case 1 ... 0xf: ++ default: ++ return -EINVAL; ++ } ++ ++out: ++ cx2072x->tdm_rx_mask = rx_mask; ++ cx2072x->tdm_tx_mask = tx_mask; ++ cx2072x->tdm_slot_width = slot_width; ++ cx2072x->tdm_slots = slots; ++ return 0; ++} ++ ++static int cx2072x_hw_params(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params, ++ struct snd_soc_dai *dai) ++{ ++ struct snd_soc_codec *codec = dai->codec; ++ struct cx2072x_priv *cx2072x = get_cx2072x_priv(codec); ++ struct device *dev = codec->dev; ++ const unsigned int sample_rate = params_rate(params); ++ int sample_size, frame_size; ++ ++ /* Data sizes if not using TDM */ ++ sample_size = params_width(params); ++ ++ if (sample_size < 0) ++ return sample_size; ++ ++ frame_size = snd_soc_params_to_frame_size(params); ++ if (frame_size < 0) ++ return frame_size; ++ ++ if (cx2072x->bclk_ratio) ++ frame_size = cx2072x->bclk_ratio; ++ ++ switch (sample_rate) { ++ case 48000: ++ case 32000: ++ case 24000: ++ case 16000: ++ case 96000: ++ case 192000: ++ break; ++ default: ++ dev_err(dev, "Unsupported sample rate %d\n", sample_rate); ++ return -EINVAL; ++ } ++ ++ dev_dbg(dev, "Sample size %d bits, frame = %d bits, rate = %d Hz\n", ++ sample_size, frame_size, sample_rate); ++ ++ cx2072x->frame_size = frame_size; ++ cx2072x->sample_size = sample_size; ++ cx2072x->sample_rate = sample_rate; ++ ++ if (cx2072x->pll_changed) { ++ cx2072x_config_pll(cx2072x); ++ cx2072x->pll_changed = false; ++ } ++ if (cx2072x->i2spcm_changed) { ++ cx2072x_config_i2spcm(cx2072x); ++ cx2072x->i2spcm_changed = false; ++ } ++ ++ return 0; ++} ++ ++ ++static int cx2072x_digital_mute(struct snd_soc_dai *dai, int mute) ++{ ++ return 0; ++} ++ ++static int cx2072x_set_dai_bclk_ratio(struct snd_soc_dai *dai, ++ unsigned int ratio) ++{ ++ struct snd_soc_codec *codec = dai->codec; ++ struct cx2072x_priv *cx2072x = get_cx2072x_priv(codec); ++ ++ cx2072x->bclk_ratio = ratio; ++ return 0; ++} ++ ++ ++static int cx2072x_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, ++ unsigned int freq, int dir) ++{ ++ struct snd_soc_codec *codec = dai->codec; ++ struct cx2072x_priv *cx2072x = get_cx2072x_priv(codec); ++ struct device *dev = codec->dev; ++ ++ if (freq == 0) { ++ dev_dbg(dev, "MCLK: Switch to internal OSC\n"); ++ return 0; ++ } ++ cx2072x->mclk = freq; ++ switch (clk_id) { ++ case CX2072X_MCLK_EXTERNAL_PLL: ++ dev_dbg(dev, "MCLK: Switch to external PLL\n"); ++ break; ++ case CX2072X_MCLK_INTERNAL_OSC: ++ dev_err(dev, "Unsupported DAI format\n"); ++ break; ++ default: ++ dev_dbg(dev, "the MCLK is not configured\n"); ++ break; ++ } ++ ++ return 0; ++} ++ ++static int cx2072x_set_dai_fmt(struct snd_soc_dai *dai, ++ unsigned int fmt) ++{ ++ struct snd_soc_codec *codec = dai->codec; ++ struct cx2072x_priv *cx2072x = get_cx2072x_priv(codec); ++ struct device *dev = codec->dev; ++ ++ /* set master/slave */ ++ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { ++ case SND_SOC_DAIFMT_CBM_CFM: ++ case SND_SOC_DAIFMT_CBS_CFS: ++ break; ++ default: ++ dev_err(dev, "Unsupported DAI master mode\n"); ++ return -EINVAL; ++ } ++ ++ /* set format */ ++ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { ++ case SND_SOC_DAIFMT_I2S: ++ case SND_SOC_DAIFMT_RIGHT_J: ++ case SND_SOC_DAIFMT_LEFT_J: ++ case SND_SOC_DAIFMT_DSP_A: ++ case SND_SOC_DAIFMT_DSP_B: ++ break; ++ default: ++ dev_err(dev, "Unsupported DAI format\n"); ++ return -EINVAL; ++ } ++ ++ /* clock inversion */ ++ switch (fmt & SND_SOC_DAIFMT_INV_MASK) { ++ case SND_SOC_DAIFMT_NB_NF: ++ case SND_SOC_DAIFMT_IB_IF: ++ case SND_SOC_DAIFMT_IB_NF: ++ case SND_SOC_DAIFMT_NB_IF: ++ break; ++ default: ++ dev_err(dev, "Unsupported DAI clock inversion\n"); ++ return -EINVAL; ++ } ++ ++ cx2072x->dai_fmt = fmt; ++ return 0; ++} ++ ++ ++static const char * const dac_enum_text[] = { ++ "DAC1 Switch", "DAC2 Switch", ++}; ++ ++static const struct soc_enum porta_dac_enum = ++SOC_ENUM_SINGLE(CX2072X_PORTA_CONNECTION_SELECT_CTRL, 0, 2, dac_enum_text); ++ ++static const struct snd_kcontrol_new porta_mux = ++SOC_DAPM_ENUM("PortA Mux", porta_dac_enum); ++ ++static const struct soc_enum portg_dac_enum = ++SOC_ENUM_SINGLE(CX2072X_PORTG_CONNECTION_SELECT_CTRL, 0, 2, dac_enum_text); ++ ++static const struct snd_kcontrol_new portg_mux = ++SOC_DAPM_ENUM("PortG Mux", portg_dac_enum); ++ ++static const struct soc_enum porte_dac_enum = ++SOC_ENUM_SINGLE(CX2072X_PORTE_CONNECTION_SELECT_CTRL, 0, 2, dac_enum_text); ++ ++static const struct snd_kcontrol_new porte_mux = ++SOC_DAPM_ENUM("PortE Mux", porte_dac_enum); ++ ++static const char * const adc1in_sel_text[] = { ++ "PortB Switch", "PortD Switch", "PortC Switch", "Widget15 Switch", ++ "PortE Switch", "PortF Switch", "PortH Switch" ++}; ++ ++static const struct soc_enum adc1in_sel_enum = ++SOC_ENUM_SINGLE(CX2072X_ADC1_CONNECTION_SELECT_CONTROL, 0, 7, adc1in_sel_text); ++ ++static const struct snd_kcontrol_new adc1_mux = ++SOC_DAPM_ENUM("ADC1 Mux", adc1in_sel_enum); ++ ++#define CX2072X_DAPM_SUPPLY_S(wname, wsubseq, wreg, wshift, wmask, won_val, \ ++ woff_val, wevent, wflags) \ ++ {.id = snd_soc_dapm_supply, .name = wname, .kcontrol_news = NULL, \ ++ .num_kcontrols = 0, .reg = wreg, .shift = wshift, .mask = wmask, \ ++ .on_val = won_val, .off_val = woff_val, \ ++ .subseq = wsubseq, .event = wevent, .event_flags = wflags} ++ ++#define CX2072X_DAPM_SWITCH(wname, wreg, wshift, wmask, won_val, woff_val, \ ++ wevent, wflags) \ ++ {.id = snd_soc_dapm_switch, .name = wname, .kcontrol_news = NULL, \ ++ .num_kcontrols = 0, .reg = wreg, .shift = wshift, .mask = wmask, \ ++ .on_val = won_val, .off_val = woff_val, \ ++ .event = wevent, .event_flags = wflags} ++ ++ ++#define CX2072X_DAPM_SWITCH(wname, wreg, wshift, wmask, won_val, woff_val, \ ++ wevent, wflags) \ ++ {.id = snd_soc_dapm_switch, .name = wname, .kcontrol_news = NULL, \ ++ .num_kcontrols = 0, .reg = wreg, .shift = wshift, .mask = wmask, \ ++ .on_val = won_val, .off_val = woff_val, \ ++ .event = wevent, .event_flags = wflags} ++ ++#define CX2072X_DAPM_REG_E(wid, wname, wreg, wshift, wmask, won_val, woff_val, \ ++ wevent, wflags) \ ++ {.id = wid, .name = wname, .kcontrol_news = NULL, .num_kcontrols = 0, \ ++ .reg = wreg, .shift = wshift, .mask = wmask, \ ++ .on_val = won_val, .off_val = woff_val, \ ++ .event = wevent, .event_flags = wflags} ++ ++static const struct snd_soc_dapm_widget cx2072x_dapm_widgets[] = { ++ /*Playback*/ ++ SND_SOC_DAPM_AIF_IN("In AIF", "Playback", 0, SND_SOC_NOPM, 0, 0), ++ ++ SND_SOC_DAPM_REG(snd_soc_dapm_dac, "DAC1", CX2072X_DAC1_POWER_STATE, ++ 0, 0xFFF, 0x00, 0x03), ++ ++ SND_SOC_DAPM_REG(snd_soc_dapm_dac, "DAC2", CX2072X_DAC2_POWER_STATE, ++ 0, 0xFFF, 0x00, 0x03), ++ ++ SND_SOC_DAPM_MUX("PortA Mux", SND_SOC_NOPM, 0, 0, &porta_mux), ++ SND_SOC_DAPM_MUX("PortG Mux", SND_SOC_NOPM, 0, 0, &portg_mux), ++ SND_SOC_DAPM_MUX("PortE Mux", SND_SOC_NOPM, 0, 0, &porte_mux), ++ ++ SND_SOC_DAPM_REG(snd_soc_dapm_switch, "PortA", ++ CX2072X_PORTA_POWER_STATE, 0, 0xFFF, 0x00, 0x03), ++ SND_SOC_DAPM_REG(snd_soc_dapm_switch, "PortG", ++ CX2072X_PORTG_POWER_STATE, 0, 0xFFF, 0x00, 0x03), ++ CX2072X_DAPM_SWITCH("PortG", CX2072X_PORTG_POWER_STATE, 0, 0xFF, ++ 0x00, 0x03, portg_power_ev, SND_SOC_DAPM_POST_PMU), ++ ++ CX2072X_DAPM_SUPPLY_S("AFG Power", 0, CX2072X_AFG_POWER_STATE, ++ 0, 0xFFF, 0x00, 0x03, afg_power_ev, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ ++ SND_SOC_DAPM_OUTPUT("PORTA"), ++ SND_SOC_DAPM_OUTPUT("PORTG"), ++ SND_SOC_DAPM_OUTPUT("PORTE"), ++ ++ /*Capture*/ ++ SND_SOC_DAPM_AIF_OUT("Out AIF", "Capture", 0, SND_SOC_NOPM, 0, 0), ++ ++ CX2072X_DAPM_REG_E(snd_soc_dapm_adc, "ADC1", CX2072X_ADC1_POWER_STATE, ++ 0, 0xFF, 0x00, 0x03, adc1_power_ev, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), ++ ++ SND_SOC_DAPM_REG(snd_soc_dapm_adc, "ADC2", CX2072X_ADC2_POWER_STATE, ++ 0, 0xFF, 0x00, 0x03), ++ ++ SND_SOC_DAPM_MUX("ADC1 Mux", SND_SOC_NOPM, 0, 0, &adc1_mux), ++ ++ SND_SOC_DAPM_REG(snd_soc_dapm_switch, "PortB", ++ CX2072X_PORTB_POWER_STATE, 0, 0xFFF, 0x00, 0x03), ++ SND_SOC_DAPM_REG(snd_soc_dapm_switch, "PortC", ++ CX2072X_PORTC_POWER_STATE, 0, 0xFFF, 0x00, 0x03), ++ SND_SOC_DAPM_REG(snd_soc_dapm_switch, "PortD", ++ CX2072X_PORTD_POWER_STATE, 0, 0xFFF, 0x00, 0x03), ++ SND_SOC_DAPM_REG(snd_soc_dapm_switch, "PortE", ++ CX2072X_PORTE_POWER_STATE, 0, 0xFFF, 0x00, 0x03), ++ SND_SOC_DAPM_REG(snd_soc_dapm_switch, "Widget15", ++ CX2072X_MIXER_POWER_STATE, 0, 0xFFF, 0x00, 0x03), ++ ++ SND_SOC_DAPM_INPUT("PORTB"), ++ SND_SOC_DAPM_INPUT("PORTC"), ++ SND_SOC_DAPM_INPUT("PORTD"), ++ ++ SND_SOC_DAPM_MICBIAS("Headset Bias", CX2072X_ANALOG_TEST11, 1, 0), ++ SND_SOC_DAPM_MICBIAS("PortD Mic Bias", CX2072X_PORTD_PIN_CTRL, 2, 0), ++ SND_SOC_DAPM_MICBIAS("PortB Mic Bias", CX2072X_PORTB_PIN_CTRL, 2, 0), ++ ++}; ++ ++static const struct snd_soc_dapm_route cx2072x_intercon[] = { ++ ++ /* Playback */ ++ {"In AIF", NULL, "AFG Power"}, ++ {"DAC1", NULL, "In AIF"}, ++ {"DAC2", NULL, "In AIF"}, ++ {"PortA Mux", "DAC1 Switch", "DAC1"}, ++ {"PortA Mux", "DAC2 Switch", "DAC2"}, ++ {"PortG Mux", "DAC1 Switch", "DAC1"}, ++ {"PortG Mux", "DAC2 Switch", "DAC2"}, ++ {"PortE Mux", "DAC1 Switch", "DAC1"}, ++ {"PortE Mux", "DAC2 Switch", "DAC2"}, ++ {"Widget15", NULL, "DAC1"}, ++ {"Widget15", NULL, "DAC2"}, ++ {"PortA", NULL, "PortA Mux"}, ++ {"PortG", NULL, "PortG Mux"}, ++ {"PortE", NULL, "PortE Mux"}, ++ {"PORTA", NULL, "PortA"}, ++ {"PORTG", NULL, "PortG"}, ++ {"PORTE", NULL, "PortE"}, ++ ++ /* Capture */ ++ {"PORTD", NULL, "Headset Bias"}, ++ {"PortD", NULL, "PORTD"}, ++ {"PortC", NULL, "PORTC"}, ++ {"PortB", NULL, "PORTB"}, ++ {"ADC1 Mux", "PortD Switch", "PortD"}, ++ {"ADC1 Mux", "PortC Switch", "PortC"}, ++ {"ADC1 Mux", "PortB Switch", "PortB"}, ++ {"ADC1 Mux", "Widget15 Switch", "Widget15"}, ++ {"ADC1", NULL, "ADC1 Mux"}, ++ {"Out AIF", NULL, "ADC1"}, ++ {"Out AIF", NULL, "AFG Power"}, ++ ++}; ++ ++ ++static void cx2072x_sw_reset(struct cx2072x_priv *cx2072x) ++{ ++ ++ regmap_write(cx2072x->regmap, CX2072X_AFG_FUNCTION_RESET, 0x01); ++ regmap_write(cx2072x->regmap, CX2072X_AFG_FUNCTION_RESET, 0x01); ++} ++ ++static int cx2072x_init(struct snd_soc_codec *codec) ++{ ++ struct cx2072x_priv *cx2072x = get_cx2072x_priv(codec); ++ int ret; ++ ++ cx2072x_sw_reset(cx2072x); ++ ++ cx2072x->plbk_dsp_changed = true; ++ cx2072x->plbk_dsp_init = false; ++ ++ regmap_update_bits(cx2072x->regmap, CX2072X_DIGITAL_TEST15, ++ 0x00, 0x06); /* reduce the monitor time.*/ ++ ++ cx2072x_config_headset_det(cx2072x); ++ ++ regmap_update_bits(cx2072x->regmap, CX2072X_PORTC_PIN_CTRL, ++ 0x20, 0x20); /*reduce the monitor time*/ ++ ++ ret = regmap_register_patch(cx2072x->regmap, cx2072x_patch, ++ ARRAY_SIZE(cx2072x_patch)); ++ if (ret) ++ return ret; ++ ++ /*enable bclk and EAPD input*/ ++ if (cx2072x->rev_id == CX2072X_REV_A2) ++ regmap_update_bits(cx2072x->regmap, CX2072X_DIGITAL_BIOS_TEST2, ++ 0x84, 0xFF); ++ ++ return 0; ++} ++static int cx2072x_set_bias_level(struct snd_soc_codec *codec, ++enum snd_soc_bias_level level) ++{ ++ struct cx2072x_priv *cx2072x = get_cx2072x_priv(codec); ++ const enum snd_soc_bias_level old_level = ++ snd_soc_codec_get_bias_level(codec); ++ ++ switch (level) { ++ case SND_SOC_BIAS_ON: ++ dev_dbg(cx2072x->dev, "SND_SOC_BIAS_ON\n"); ++ /* Enable Headset Mic Bias */ ++ if (cx2072x->is_biason == 0) ++ cx2072x->is_biason = 1; ++ break; ++ case SND_SOC_BIAS_PREPARE: ++ dev_dbg(cx2072x->dev, "SND_SOC_BIAS_PREPARE\n"); ++ if (SND_SOC_BIAS_STANDBY == old_level) { ++ dev_dbg(cx2072x->dev, "SND_SOC_BIAS_STANDBY = > SND_SOC_BIAS_PREPARE\n"); ++#ifdef INTEL_MCLK_CONTROL ++ /*FIXME: added some delay to make sure the MCLK ++ * is stable before Codec is on. */ ++ mdelay(3); ++ dev_dbg(cx2072x->dev, "Turn on MCLK\n"); ++#endif ++ } ++ break; ++ case SND_SOC_BIAS_STANDBY: ++ dev_dbg(cx2072x->dev, "SND_SOC_BIAS_STANDBY\n"); ++ ++ if (SND_SOC_BIAS_OFF == old_level) { ++ dev_dbg(codec->dev, "Initialize codec\n"); ++#ifdef INTEL_MCLK_CONTROL ++ dev_dbg(cx2072x->dev, "Turn on MCLK\n"); ++ /*FIXME: added some delay to make sure the MCLK is ++ stable before Codec is on. */ ++ mdelay(3); ++#endif ++ cx2072x_init(codec); ++ } ++ /*power device into standby mode*/ ++#ifdef INTEL_MCLK_CONTROL ++ dev_dbg(cx2072x->dev, "Turn off MCLK\n"); ++#endif ++ break; ++ case SND_SOC_BIAS_OFF: ++ dev_dbg(cx2072x->dev, "SND_SOC_BIAS_OFF\n"); ++ /* Gracefully shutdwon codec*/ ++ ++ regmap_write(cx2072x->regmap, CX2072X_PORTA_POWER_STATE, 3); ++ regmap_write(cx2072x->regmap, CX2072X_PORTB_POWER_STATE, 3); ++ regmap_write(cx2072x->regmap, CX2072X_PORTC_POWER_STATE, 3); ++ regmap_write(cx2072x->regmap, CX2072X_PORTD_POWER_STATE, 3); ++ regmap_write(cx2072x->regmap, CX2072X_PORTG_POWER_STATE, 3); ++ regmap_write(cx2072x->regmap, CX2072X_ADC1_POWER_STATE, 3); ++ regmap_write(cx2072x->regmap, CX2072X_ADC2_POWER_STATE, 3); ++ regmap_write(cx2072x->regmap, CX2072X_DAC1_POWER_STATE, 3); ++ regmap_write(cx2072x->regmap, CX2072X_DAC2_POWER_STATE, 3); ++ regmap_write(cx2072x->regmap, CX2072X_AFG_POWER_STATE, 3); ++ regmap_update_bits(cx2072x->regmap, CX2072X_DIGITAL_BIOS_TEST0, ++ 0x10, 0x10); ++ /*Shutdown codec completely*/ ++ cx2072x_sw_reset(cx2072x); ++ break; ++ } ++ return 0; ++} ++ ++ ++static int cx2072x_probe(struct snd_soc_codec *codec) ++{ ++ struct cx2072x_priv *cx2072x = get_cx2072x_priv(codec); ++ int ret = 0; ++ unsigned int ven_id; ++ ++ cx2072x->codec = codec; ++ codec->control_data = cx2072x->regmap; ++ ++ dev_dbg(codec->dev, "codec version: 4.4.18\n"); ++ regmap_read(cx2072x->regmap, CX2072X_VENDOR_ID, &ven_id); ++ regmap_read(cx2072x->regmap, CX2072X_REVISION_ID, &cx2072x->rev_id); ++ dev_dbg(codec->dev, "codec version: %08x,%08x\n", ++ ven_id, cx2072x->rev_id); ++ ++#ifdef ENABLE_MIC_POP_WA ++ INIT_DELAYED_WORK(&codec->dapm.delayed_work, ++ cx2072x_anit_mic_pop_work); ++#endif ++ ++ /*power on device*/ ++ cx2072x_set_bias_level(codec, SND_SOC_BIAS_STANDBY); ++ ++ return ret; ++} ++ ++static int cx2072x_remove(struct snd_soc_codec *codec) ++{ ++ struct cx2072x_priv *cx2072x = get_cx2072x_priv(codec); ++ /*power off device*/ ++ cx2072x_set_bias_level(cx2072x->codec, SND_SOC_BIAS_OFF); ++ return 0; ++} ++ ++#ifdef CONFIG_PM ++static int cx2072x_runtime_suspend(struct device *dev) ++{ ++ struct cx2072x_priv *cx2072x = dev_get_drvdata(dev); ++ ++ dev_dbg(cx2072x->codec->dev, "%s----%d\n", __func__, __LINE__); ++ cx2072x_set_bias_level(cx2072x->codec, SND_SOC_BIAS_OFF); ++ ++ return 0; ++} ++ ++static int cx2072x_runtime_resume(struct device *dev) ++{ ++ struct cx2072x_priv *cx2072x = dev_get_drvdata(dev); ++ ++ dev_dbg(cx2072x->codec->dev, "%s----%d\n", __func__, __LINE__); ++ cx2072x_set_bias_level(cx2072x->codec, SND_SOC_BIAS_STANDBY); ++ return 0; ++} ++#else ++#define cx2072x_suspend NULL ++#define cx2072x_resume NULL ++#endif ++ ++ ++ ++ ++static bool cx2072x_readable_register(struct device *dev, unsigned int reg) ++{ ++ switch (reg) { ++ ++ case CX2072X_VENDOR_ID: ++ case CX2072X_REVISION_ID: ++ case CX2072X_CURRENT_BCLK_FREQUENCY: ++ case CX2072X_AFG_POWER_STATE: ++ case CX2072X_UM_RESPONSE: ++ case CX2072X_GPIO_DATA: ++ case CX2072X_GPIO_ENABLE: ++ case CX2072X_GPIO_DIRECTION: ++ case CX2072X_GPIO_WAKE: ++ case CX2072X_GPIO_UM_ENABLE: ++ case CX2072X_GPIO_STICKY_MASK: ++ case CX2072X_DAC1_CONVERTER_FORMAT: ++ case CX2072X_DAC1_AMP_GAIN_RIGHT: ++ case CX2072X_DAC1_AMP_GAIN_LEFT: ++ case CX2072X_DAC1_POWER_STATE: ++ case CX2072X_DAC1_CONVERTER_STREAM_CHANNEL: ++ case CX2072X_DAC1_EAPD_ENABLE: ++ case CX2072X_DAC2_CONVERTER_FORMAT: ++ case CX2072X_DAC2_AMP_GAIN_RIGHT: ++ case CX2072X_DAC2_AMP_GAIN_LEFT: ++ case CX2072X_DAC2_POWER_STATE: ++ case CX2072X_DAC2_CONVERTER_STREAM_CHANNEL: ++ case CX2072X_ADC1_CONVERTER_FORMAT: ++ case CX2072X_ADC1_AMP_GAIN_RIGHT_0: ++ case CX2072X_ADC1_AMP_GAIN_LEFT_0: ++ case CX2072X_ADC1_AMP_GAIN_RIGHT_1: ++ case CX2072X_ADC1_AMP_GAIN_LEFT_1: ++ case CX2072X_ADC1_AMP_GAIN_RIGHT_2: ++ case CX2072X_ADC1_AMP_GAIN_LEFT_2: ++ case CX2072X_ADC1_AMP_GAIN_RIGHT_3: ++ case CX2072X_ADC1_AMP_GAIN_LEFT_3: ++ case CX2072X_ADC1_AMP_GAIN_RIGHT_4: ++ case CX2072X_ADC1_AMP_GAIN_LEFT_4: ++ case CX2072X_ADC1_AMP_GAIN_RIGHT_5: ++ case CX2072X_ADC1_AMP_GAIN_LEFT_5: ++ case CX2072X_ADC1_AMP_GAIN_RIGHT_6: ++ case CX2072X_ADC1_AMP_GAIN_LEFT_6: ++ case CX2072X_ADC1_CONNECTION_SELECT_CONTROL: ++ case CX2072X_ADC1_POWER_STATE: ++ case CX2072X_ADC1_CONVERTER_STREAM_CHANNEL: ++ case CX2072X_ADC2_CONVERTER_FORMAT: ++ case CX2072X_ADC2_AMP_GAIN_RIGHT_0: ++ case CX2072X_ADC2_AMP_GAIN_LEFT_0: ++ case CX2072X_ADC2_AMP_GAIN_RIGHT_1: ++ case CX2072X_ADC2_AMP_GAIN_LEFT_1: ++ case CX2072X_ADC2_AMP_GAIN_RIGHT_2: ++ case CX2072X_ADC2_AMP_GAIN_LEFT_2: ++ case CX2072X_ADC2_CONNECTION_SELECT_CONTROL: ++ case CX2072X_ADC2_POWER_STATE: ++ case CX2072X_ADC2_CONVERTER_STREAM_CHANNEL: ++ case CX2072X_PORTA_CONNECTION_SELECT_CTRL: ++ case CX2072X_PORTA_POWER_STATE: ++ case CX2072X_PORTA_PIN_CTRL: ++ case CX2072X_PORTA_UNSOLICITED_RESPONSE: ++ case CX2072X_PORTA_PIN_SENSE: ++ case CX2072X_PORTA_EAPD_BTL: ++ case CX2072X_PORTB_POWER_STATE: ++ case CX2072X_PORTB_PIN_CTRL: ++ case CX2072X_PORTB_UNSOLICITED_RESPONSE: ++ case CX2072X_PORTB_PIN_SENSE: ++ case CX2072X_PORTB_EAPD_BTL: ++ case CX2072X_PORTB_GAIN_RIGHT: ++ case CX2072X_PORTB_GAIN_LEFT: ++ case CX2072X_PORTC_POWER_STATE: ++ case CX2072X_PORTC_PIN_CTRL: ++ case CX2072X_PORTC_GAIN_RIGHT: ++ case CX2072X_PORTC_GAIN_LEFT: ++ case CX2072X_PORTD_POWER_STATE: ++ case CX2072X_PORTD_PIN_CTRL: ++ case CX2072X_PORTD_UNSOLICITED_RESPONSE: ++ case CX2072X_PORTD_PIN_SENSE: ++ case CX2072X_PORTD_GAIN_RIGHT: ++ case CX2072X_PORTD_GAIN_LEFT: ++ case CX2072X_PORTE_CONNECTION_SELECT_CTRL: ++ case CX2072X_PORTE_POWER_STATE: ++ case CX2072X_PORTE_PIN_CTRL: ++ case CX2072X_PORTE_UNSOLICITED_RESPONSE: ++ case CX2072X_PORTE_PIN_SENSE: ++ case CX2072X_PORTE_EAPD_BTL: ++ case CX2072X_PORTE_GAIN_RIGHT: ++ case CX2072X_PORTE_GAIN_LEFT: ++ case CX2072X_PORTF_POWER_STATE: ++ case CX2072X_PORTF_PIN_CTRL: ++ case CX2072X_PORTF_UNSOLICITED_RESPONSE: ++ case CX2072X_PORTF_PIN_SENSE: ++ case CX2072X_PORTF_GAIN_RIGHT: ++ case CX2072X_PORTF_GAIN_LEFT: ++ case CX2072X_PORTG_POWER_STATE: ++ case CX2072X_PORTG_PIN_CTRL: ++ case CX2072X_PORTG_CONNECTION_SELECT_CTRL: ++ case CX2072X_PORTG_EAPD_BTL: ++ case CX2072X_PORTM_POWER_STATE: ++ case CX2072X_PORTM_PIN_CTRL: ++ case CX2072X_PORTM_CONNECTION_SELECT_CTRL: ++ case CX2072X_PORTM_EAPD_BTL: ++ case CX2072X_MIXER_POWER_STATE: ++ case CX2072X_MIXER_GAIN_RIGHT_0: ++ case CX2072X_MIXER_GAIN_LEFT_0: ++ case CX2072X_MIXER_GAIN_RIGHT_1: ++ case CX2072X_MIXER_GAIN_LEFT_1: ++ case CX2072X_EQ_ENABLE_BYPASS: ++ case CX2072X_EQ_B0_COEFF: ++ case CX2072X_EQ_B1_COEFF: ++ case CX2072X_EQ_B2_COEFF: ++ case CX2072X_EQ_A1_COEFF: ++ case CX2072X_EQ_A2_COEFF: ++ case CX2072X_EQ_G_COEFF: ++ case CX2072X_SPKR_DRC_ENABLE_STEP: ++ case CX2072X_SPKR_DRC_CONTROL: ++ case CX2072X_SPKR_DRC_TEST: ++ case CX2072X_DIGITAL_BIOS_TEST0: ++ case CX2072X_DIGITAL_BIOS_TEST2: ++ case CX2072X_I2SPCM_CONTROL1: ++ case CX2072X_I2SPCM_CONTROL2: ++ case CX2072X_I2SPCM_CONTROL3: ++ case CX2072X_I2SPCM_CONTROL4: ++ case CX2072X_I2SPCM_CONTROL5: ++ case CX2072X_I2SPCM_CONTROL6: ++ case CX2072X_UM_INTERRUPT_CRTL_E: ++ case CX2072X_CODEC_TEST2: ++ case CX2072X_CODEC_TEST20: ++ case CX2072X_CODEC_TEST26: ++ case CX2072X_ANALOG_TEST4: ++ case CX2072X_ANALOG_TEST5: ++ case CX2072X_ANALOG_TEST6: ++ case CX2072X_ANALOG_TEST7: ++ case CX2072X_ANALOG_TEST8: ++ case CX2072X_ANALOG_TEST9: ++ case CX2072X_ANALOG_TEST10: ++ case CX2072X_ANALOG_TEST11: ++ case CX2072X_ANALOG_TEST12: ++ case CX2072X_ANALOG_TEST13: ++ case CX2072X_DIGITAL_TEST0: ++ case CX2072X_DIGITAL_TEST1: ++ case CX2072X_DIGITAL_TEST11: ++ case CX2072X_DIGITAL_TEST12: ++ case CX2072X_DIGITAL_TEST15: ++ case CX2072X_DIGITAL_TEST16: ++ case CX2072X_DIGITAL_TEST17: ++ case CX2072X_DIGITAL_TEST18: ++ case CX2072X_DIGITAL_TEST19: ++ case CX2072X_DIGITAL_TEST20: ++ return true; ++ default: ++ return false; ++ } ++} ++ ++static bool cx2072x_volatile_register(struct device *dev, unsigned int reg) ++{ ++ switch (reg) { ++ case CX2072X_VENDOR_ID: ++ case CX2072X_REVISION_ID: ++ case CX2072X_UM_INTERRUPT_CRTL_E: ++ case CX2072X_DIGITAL_TEST11: ++ case CX2072X_PORTA_PIN_SENSE: ++ case CX2072X_PORTB_PIN_SENSE: ++ case CX2072X_PORTD_PIN_SENSE: ++ case CX2072X_PORTE_PIN_SENSE: ++ case CX2072X_PORTF_PIN_SENSE: ++ case CX2072X_EQ_G_COEFF: ++ case CX2072X_EQ_BAND: ++ return true; ++ default: ++ return false; ++ ++ } ++} ++ ++static struct snd_soc_codec_driver soc_codec_driver_cx2072x = { ++ .probe = cx2072x_probe, ++ .remove = cx2072x_remove, ++ .set_bias_level = cx2072x_set_bias_level, ++ .component_driver = { ++ .controls = cx2072x_snd_controls, ++ .num_controls = ARRAY_SIZE(cx2072x_snd_controls), ++ .dapm_widgets = cx2072x_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(cx2072x_dapm_widgets), ++ .dapm_routes = cx2072x_intercon, ++ .num_dapm_routes = ARRAY_SIZE(cx2072x_intercon), ++ } ++}; ++ ++/* ++ * DAI ops ++ */ ++static struct snd_soc_dai_ops cx2072x_dai_ops = { ++ .set_sysclk = cx2072x_set_dai_sysclk, ++ .set_fmt = cx2072x_set_dai_fmt, ++ .set_tdm_slot = cx2072x_set_tdm_slot, ++ .hw_params = cx2072x_hw_params, ++ .digital_mute = cx2072x_digital_mute, ++ .set_bclk_ratio = cx2072x_set_dai_bclk_ratio ++}; ++ ++/* ++ * DAI driver ++ */ ++static struct snd_soc_dai_driver soc_codec_cx2072x_dai = { ++ .name = "cx2072x-hifi", ++ .playback = { ++ .stream_name = "Playback", ++ .channels_min = 1, ++ .channels_max = 2, ++ .rates = CX2072X_RATES_DSP, ++ .formats = CX2072X_FORMATS, ++ }, ++ .capture = { ++ .stream_name = "Capture", ++ .channels_min = 1, ++ .channels_max = 2, ++ .rates = CX2072X_RATES_DSP, ++ .formats = CX2072X_FORMATS, ++ }, ++ .ops = &cx2072x_dai_ops, ++ .symmetric_rates = 1, ++}; ++EXPORT_SYMBOL_GPL(soc_codec_cx2072x_dai); ++ ++static const struct regmap_config cx2072x_regmap = { ++ .reg_bits = 16, ++ .val_bits = 32, ++ .max_register = CX2072X_REG_MAX, .reg_defaults = cx2072x_reg_defaults, ++ .num_reg_defaults = ARRAY_SIZE(cx2072x_reg_defaults), ++ .cache_type = REGCACHE_RBTREE, ++ ++ .readable_reg = cx2072x_readable_register, ++ .volatile_reg = cx2072x_volatile_register, ++ .reg_read = cx2072x_reg_read, ++ .reg_write = cx2072x_reg_write, ++}; ++ ++ ++static int cx2072x_i2c_probe(struct i2c_client *i2c, ++ const struct i2c_device_id *id) ++{ ++ int ret = -1; ++ struct cx2072x_priv *cx2072x; ++ ++ dev_dbg(&i2c->dev, "CX2072X codec driver i2c probe() is called\n"); ++ ++ cx2072x = (struct cx2072x_priv *)devm_kzalloc( ++ &i2c->dev, sizeof(struct cx2072x_priv), GFP_KERNEL); ++ if (cx2072x == NULL) { ++ dev_err(&i2c->dev, "Out of memory!\n"); ++ return -ENOMEM; ++ } ++ ++ mutex_init(&cx2072x->lock); ++ ++ cx2072x->regmap = devm_regmap_init(&i2c->dev, NULL, i2c, ++ &cx2072x_regmap); ++ if (IS_ERR(&cx2072x->regmap)) { ++ ret = PTR_ERR(cx2072x->regmap); ++ dev_err(&i2c->dev, "Failed to init regmap: %d\n", ret); ++ return ret; ++ } ++ ++ i2c_set_clientdata(i2c, cx2072x); ++ ++ cx2072x->dev = &i2c->dev; ++ cx2072x->pll_changed = true; ++ cx2072x->i2spcm_changed = true; ++ ++ /* setts the frame size to ++ * Frame size = number of channel * sample width ++ */ ++ cx2072x->bclk_ratio = 0; ++ ++ ret = snd_soc_register_codec(cx2072x->dev, ++ &soc_codec_driver_cx2072x, &soc_codec_cx2072x_dai, ++ 1); ++ if (ret < 0) ++ dev_err(cx2072x->dev, ++ "Failed to register codec: %d\n", ret); ++ else ++ dev_dbg(cx2072x->dev, ++ "%s: Register codec.\n", __func__); ++ ++ return ret; ++} ++ ++static int cx2072x_i2c_remove(struct i2c_client *client) ++{ ++ snd_soc_unregister_codec(&client->dev); ++ return 0; ++} ++ ++static void cx2072x_i2c_shutdown(struct i2c_client *client) ++{ ++ struct cx2072x_priv *cx2072x = i2c_get_clientdata(client); ++ cx2072x_set_bias_level(cx2072x->codec, SND_SOC_BIAS_OFF); ++} ++ ++ ++const struct dev_pm_ops cx2072x_pm_ops = { ++ SET_RUNTIME_PM_OPS(cx2072x_runtime_suspend, cx2072x_runtime_resume, ++ NULL) ++}; ++static const struct i2c_device_id cx2072x_i2c_id[] = { ++ { "cx20721", 0 }, ++ { "cx20722", 0 }, ++ { "14F10720", 0 }, ++ {} ++}; ++MODULE_DEVICE_TABLE(i2c, cx2072x_i2c_id); ++ ++static const struct of_device_id cx2072x_of_match[] = { ++ { .compatible = "cnxt,cx20721", }, ++ { .compatible = "cnxt,cx20723", }, ++ { .compatible = "cnxt,cx7601", }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, cx2072x_of_match); ++#ifdef CONFIG_ACPI ++static struct acpi_device_id cx2072x_acpi_match[] = { ++ { "14F10720", 0 }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(acpi, cx2072x_acpi_match); ++#endif ++ ++static struct i2c_driver cx2072x_i2c_driver = { ++ .probe = cx2072x_i2c_probe, ++ .remove = cx2072x_i2c_remove, ++ .shutdown = cx2072x_i2c_shutdown, ++ .id_table = cx2072x_i2c_id, ++ .driver = { ++ .name = "cx2072x", ++ .owner = THIS_MODULE, ++ .of_match_table = cx2072x_of_match, ++#ifdef CONFIG_ACPI ++ .acpi_match_table = ACPI_PTR(cx2072x_acpi_match), ++#endif ++ .pm = &cx2072x_pm_ops, ++ }, ++}; ++ ++module_i2c_driver(cx2072x_i2c_driver); ++ ++MODULE_DESCRIPTION("ASoC cx2072x Codec Driver"); ++MODULE_AUTHOR("Simon Ho "); ++MODULE_LICENSE("GPL"); ++ +diff --git a/sound/soc/codecs/cx2072x.h b/sound/soc/codecs/cx2072x.h +new file mode 100644 +index 000000000000..67b90c184c6b +--- /dev/null ++++ b/sound/soc/codecs/cx2072x.h +@@ -0,0 +1,339 @@ ++/* ++ * ALSA SoC CX2072x Solana codec driver ++ * ++ * Copyright: (C) 2016 Conexant Systems, Inc. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 as ++ * published by the Free Software Foundation. ++ * ++ ************************************************************************/ ++ ++#define NUM_OF_DAI 1 ++#define CX2072X_MCLK_PLL 1 ++#define CX2072X_MCLK_EXTERNAL_PLL 1 ++#define CX2072X_MCLK_INTERNAL_OSC 2 ++ ++/*#define CX2072X_RATES SNDRV_PCM_RATE_8000_192000*/ ++#define CX2072X_RATES_DSP SNDRV_PCM_RATE_48000 ++ ++#define CX2072X_REG_MAX 0x8a3c ++#define AUDDRV_VERSION(major0, major1, minor, build) \ ++ ((major0)<<24 | (major1)<<16 | (minor)<<8 | (build)) ++ ++#define CX2072X_VENDOR_ID 0x0200 ++#define CX2072X_REVISION_ID 0x0208 ++#define CX2072X_CURRENT_BCLK_FREQUENCY 0x00dc ++#define CX2072X_AFG_POWER_STATE 0x0414 ++#define CX2072X_UM_RESPONSE 0x0420 ++#define CX2072X_GPIO_DATA 0x0454 ++#define CX2072X_GPIO_ENABLE 0x0458 ++#define CX2072X_GPIO_DIRECTION 0x045c ++#define CX2072X_GPIO_WAKE 0x0460 ++#define CX2072X_GPIO_UM_ENABLE 0x0464 ++#define CX2072X_GPIO_STICKY_MASK 0x0468 ++#define CX2072X_AFG_FUNCTION_RESET 0x07FC ++#define CX2072X_DAC1_CONVERTER_FORMAT 0x43c8 ++#define CX2072X_DAC1_AMP_GAIN_RIGHT 0x41c0 ++#define CX2072X_DAC1_AMP_GAIN_LEFT 0x41e0 ++#define CX2072X_DAC1_POWER_STATE 0x4014 ++#define CX2072X_DAC1_CONVERTER_STREAM_CHANNEL 0x4018 ++#define CX2072X_DAC1_EAPD_ENABLE 0x4030 ++#define CX2072X_DAC2_CONVERTER_FORMAT 0x47c8 ++#define CX2072X_DAC2_AMP_GAIN_RIGHT 0x45c0 ++#define CX2072X_DAC2_AMP_GAIN_LEFT 0x45e0 ++#define CX2072X_DAC2_POWER_STATE 0x4414 ++#define CX2072X_DAC2_CONVERTER_STREAM_CHANNEL 0x4418 ++#define CX2072X_ADC1_CONVERTER_FORMAT 0x4fc8 ++#define CX2072X_ADC1_AMP_GAIN_RIGHT_0 0x4d80 ++#define CX2072X_ADC1_AMP_GAIN_LEFT_0 0x4da0 ++#define CX2072X_ADC1_AMP_GAIN_RIGHT_1 0x4d84 ++#define CX2072X_ADC1_AMP_GAIN_LEFT_1 0x4da4 ++#define CX2072X_ADC1_AMP_GAIN_RIGHT_2 0x4d88 ++#define CX2072X_ADC1_AMP_GAIN_LEFT_2 0x4da8 ++#define CX2072X_ADC1_AMP_GAIN_RIGHT_3 0x4d8c ++#define CX2072X_ADC1_AMP_GAIN_LEFT_3 0x4dac ++#define CX2072X_ADC1_AMP_GAIN_RIGHT_4 0x4d90 ++#define CX2072X_ADC1_AMP_GAIN_LEFT_4 0x4db0 ++#define CX2072X_ADC1_AMP_GAIN_RIGHT_5 0x4d94 ++#define CX2072X_ADC1_AMP_GAIN_LEFT_5 0x4db4 ++#define CX2072X_ADC1_AMP_GAIN_RIGHT_6 0x4d98 ++#define CX2072X_ADC1_AMP_GAIN_LEFT_6 0x4db8 ++#define CX2072X_ADC1_CONNECTION_SELECT_CONTROL 0x4c04 ++#define CX2072X_ADC1_POWER_STATE 0x4c14 ++#define CX2072X_ADC1_CONVERTER_STREAM_CHANNEL 0x4c18 ++#define CX2072X_ADC2_CONVERTER_FORMAT 0x53c8 ++#define CX2072X_ADC2_AMP_GAIN_RIGHT_0 0x5180 ++#define CX2072X_ADC2_AMP_GAIN_LEFT_0 0x51a0 ++#define CX2072X_ADC2_AMP_GAIN_RIGHT_1 0x5184 ++#define CX2072X_ADC2_AMP_GAIN_LEFT_1 0x51a4 ++#define CX2072X_ADC2_AMP_GAIN_RIGHT_2 0x5188 ++#define CX2072X_ADC2_AMP_GAIN_LEFT_2 0x51a8 ++#define CX2072X_ADC2_CONNECTION_SELECT_CONTROL 0x5004 ++#define CX2072X_ADC2_POWER_STATE 0x5014 ++#define CX2072X_ADC2_CONVERTER_STREAM_CHANNEL 0x5018 ++#define CX2072X_PORTA_CONNECTION_SELECT_CTRL 0x5804 ++#define CX2072X_PORTA_POWER_STATE 0x5814 ++#define CX2072X_PORTA_PIN_CTRL 0x581c ++#define CX2072X_PORTA_UNSOLICITED_RESPONSE 0x5820 ++#define CX2072X_PORTA_PIN_SENSE 0x5824 ++#define CX2072X_PORTA_EAPD_BTL 0x5830 ++#define CX2072X_PORTB_POWER_STATE 0x6014 ++#define CX2072X_PORTB_PIN_CTRL 0x601c ++#define CX2072X_PORTB_UNSOLICITED_RESPONSE 0x6020 ++#define CX2072X_PORTB_PIN_SENSE 0x6024 ++#define CX2072X_PORTB_EAPD_BTL 0x6030 ++#define CX2072X_PORTB_GAIN_RIGHT 0x6180 ++#define CX2072X_PORTB_GAIN_LEFT 0x61a0 ++#define CX2072X_PORTC_POWER_STATE 0x6814 ++#define CX2072X_PORTC_PIN_CTRL 0x681c ++#define CX2072X_PORTC_GAIN_RIGHT 0x6980 ++#define CX2072X_PORTC_GAIN_LEFT 0x69a0 ++#define CX2072X_PORTD_POWER_STATE 0x6414 ++#define CX2072X_PORTD_PIN_CTRL 0x641c ++#define CX2072X_PORTD_UNSOLICITED_RESPONSE 0x6420 ++#define CX2072X_PORTD_PIN_SENSE 0x6424 ++#define CX2072X_PORTD_GAIN_RIGHT 0x6580 ++#define CX2072X_PORTD_GAIN_LEFT 0x65a0 ++#define CX2072X_PORTE_CONNECTION_SELECT_CTRL 0x7404 ++#define CX2072X_PORTE_POWER_STATE 0x7414 ++#define CX2072X_PORTE_PIN_CTRL 0x741c ++#define CX2072X_PORTE_UNSOLICITED_RESPONSE 0x7420 ++#define CX2072X_PORTE_PIN_SENSE 0x7424 ++#define CX2072X_PORTE_EAPD_BTL 0x7430 ++#define CX2072X_PORTE_GAIN_RIGHT 0x7580 ++#define CX2072X_PORTE_GAIN_LEFT 0x75a0 ++#define CX2072X_PORTF_POWER_STATE 0x7814 ++#define CX2072X_PORTF_PIN_CTRL 0x781c ++#define CX2072X_PORTF_UNSOLICITED_RESPONSE 0x7820 ++#define CX2072X_PORTF_PIN_SENSE 0x7824 ++#define CX2072X_PORTF_GAIN_RIGHT 0x7980 ++#define CX2072X_PORTF_GAIN_LEFT 0x79a0 ++#define CX2072X_PORTG_POWER_STATE 0x5c14 ++#define CX2072X_PORTG_PIN_CTRL 0x5c1c ++#define CX2072X_PORTG_CONNECTION_SELECT_CTRL 0x5c04 ++#define CX2072X_PORTG_EAPD_BTL 0x5c30 ++#define CX2072X_PORTM_POWER_STATE 0x8814 ++#define CX2072X_PORTM_PIN_CTRL 0x881c ++#define CX2072X_PORTM_CONNECTION_SELECT_CTRL 0x8804 ++#define CX2072X_PORTM_EAPD_BTL 0x8830 ++#define CX2072X_MIXER_POWER_STATE 0x5414 ++#define CX2072X_MIXER_GAIN_RIGHT_0 0x5580 ++#define CX2072X_MIXER_GAIN_LEFT_0 0x55a0 ++#define CX2072X_MIXER_GAIN_RIGHT_1 0x5584 ++#define CX2072X_MIXER_GAIN_LEFT_1 0x55a4 ++#define CX2072X_EQ_ENABLE_BYPASS 0x6d00 ++#define CX2072X_EQ_B0_COEFF 0x6d02 ++#define CX2072X_EQ_B1_COEFF 0x6d04 ++#define CX2072X_EQ_B2_COEFF 0x6d06 ++#define CX2072X_EQ_A1_COEFF 0x6d08 ++#define CX2072X_EQ_A2_COEFF 0x6d0a ++#define CX2072X_EQ_G_COEFF 0x6d0c ++#define CX2072X_EQ_BAND 0x6d0d ++#define CX2072X_SPKR_DRC_ENABLE_STEP 0x6d10 ++#define CX2072X_SPKR_DRC_CONTROL 0x6d14 ++#define CX2072X_SPKR_DRC_TEST 0X6D18 ++#define CX2072X_DIGITAL_BIOS_TEST0 0x6d80 ++#define CX2072X_DIGITAL_BIOS_TEST2 0x6d84 ++#define CX2072X_I2SPCM_CONTROL1 0x6e00 ++#define CX2072X_I2SPCM_CONTROL2 0x6e04 ++#define CX2072X_I2SPCM_CONTROL3 0x6e08 ++#define CX2072X_I2SPCM_CONTROL4 0x6e0c ++#define CX2072X_I2SPCM_CONTROL5 0x6e10 ++#define CX2072X_I2SPCM_CONTROL6 0x6e18 ++#define CX2072X_UM_INTERRUPT_CRTL_E 0x6e14 ++#define CX2072X_CODEC_TEST2 0x7108 ++#define CX2072X_CODEC_TEST20 0x7310 ++#define CX2072X_CODEC_TEST26 0x7328 ++#define CX2072X_ANALOG_TEST3 0x718c ++#define CX2072X_ANALOG_TEST4 0x7190 ++#define CX2072X_ANALOG_TEST5 0x7194 ++#define CX2072X_ANALOG_TEST6 0x7198 ++#define CX2072X_ANALOG_TEST7 0x719c ++#define CX2072X_ANALOG_TEST8 0x71a0 ++#define CX2072X_ANALOG_TEST9 0x71a4 ++#define CX2072X_ANALOG_TEST10 0x71a8 ++#define CX2072X_ANALOG_TEST11 0x71ac ++#define CX2072X_ANALOG_TEST12 0x71b0 ++#define CX2072X_ANALOG_TEST13 0x71b4 ++#define CX2072X_DIGITAL_TEST0 0x7200 ++#define CX2072X_DIGITAL_TEST1 0x7204 ++#define CX2072X_DIGITAL_TEST11 0x722c ++#define CX2072X_DIGITAL_TEST12 0x7230 ++#define CX2072X_DIGITAL_TEST15 0x723c ++#define CX2072X_DIGITAL_TEST16 0x7080 ++#define CX2072X_DIGITAL_TEST17 0x7084 ++#define CX2072X_DIGITAL_TEST18 0x7088 ++#define CX2072X_DIGITAL_TEST19 0x708c ++#define CX2072X_DIGITAL_TEST20 0x7090 ++ ++#define INVALID_GPIO -1 ++#define MAX_EQ_BAND 7 ++#define MAC_EQ_COEFF 11 ++#define MAX_DRC_REGS 9 ++#define MIC_EQ_COEFF 10 ++/* ++static unsigned char cx2072x_eq_coeff_flat[MAX_EQ_BAND][MAC_EQ_COEFF] = { ++ {0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03}, ++ {0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03}, ++ {0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03}, ++ {0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03}, ++ {0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03}, ++ {0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03}, ++ {0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03}, ++}; ++*/ ++/* ++static unsigned char cx2072x_miceq_coeff_array[][MIC_EQ_COEFF] = { ++ {0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ++ {0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ++ {0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ++ {0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ++ {0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ++ {0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ++ {0x42, 0x17, 0x85, 0x2e, 0x42, 0x17, 0x0a, 0x5d, 0xed, 0xc5}, ++}; ++*/ ++ ++ ++enum cx2072x_jack_types { ++ CX_JACK_NONE = 0x0000, ++ CX_JACK_HEADPHONE = 0x0001, ++ CX_JACK_APPLE_HEADSET = 0x0002, ++ CX_JACK_NOKIE_HEADSET = 0x0003, ++ ++}; ++ ++int cx2072x_hs_jack_report(struct snd_soc_codec *codec); ++ ++typedef enum _REG_SAMPLE_SIZE{ ++ SAMPLE_SIZE_8_BITS = 0 , ++ SAMPLE_SIZE_16_BITS = 1 , ++ SAMPLE_SIZE_24_BITS = 2 , ++ SAMPLE_SIZE_RESERVED = 3 ++}REG_SAMPLE_SIZE; ++ ++typedef union _REG_I2SPCM_CTRL_REG1{ ++ struct { ++ u32 rx_data_one_line :1; ++ u32 rx_ws_pol :1; ++ u32 rx_ws_wid :7; ++ u32 rx_frm_len :5; ++ u32 rx_sa_size :2; ++ u32 tx_data_one_line :1; ++ u32 tx_ws_pol :1; ++ u32 tx_ws_wid :7; ++ u32 tx_frm_len :5; ++ u32 tx_sa_size :2; ++ }r; ++ u32 ulVal; ++}REG_I2SPCM_CTRL_REG1; ++ ++ ++typedef union _REG_I2SPCM_CTRL_REG2{ ++ struct { ++ u32 tx_en_ch1 :1; ++ u32 tx_en_ch2 :1; ++ u32 tx_en_ch3 :1; ++ u32 tx_en_ch4 :1; ++ u32 tx_en_ch5 :1; ++ u32 tx_en_ch6 :1; ++ u32 tx_slot_1 :5; ++ u32 tx_slot_2 :5; ++ u32 tx_slot_3 :5; ++ u32 tx_slot_4 :5; ++ u32 res :1; ++ u32 tx_data_neg_bclk :1; ++ u32 tx_master :1; ++ u32 tx_tri_n :1; ++ u32 tx_endian_sel :1; ++ u32 tx_dstart_dly :1; ++ }r; ++ u32 ulVal; ++}REG_I2SPCM_CTRL_REG2; ++ ++typedef union _REG_I2SPCM_CTRL_REG3{ ++ struct { ++ u32 rx_en_ch1 :1; ++ u32 rx_en_ch2 :1; ++ u32 rx_en_ch3 :1; ++ u32 rx_en_ch4 :1; ++ u32 rx_en_ch5 :1; ++ u32 rx_en_ch6 :1; ++ u32 rx_slot_1 :5; ++ u32 rx_slot_2 :5; ++ u32 rx_slot_3 :5; ++ u32 rx_slot_4 :5; ++ u32 res :1; ++ u32 rx_data_neg_bclk :1; ++ u32 rx_master :1; ++ u32 rx_tri_n :1; ++ u32 rx_endian_sel :1; ++ u32 rx_dstart_dly :1; ++ }r; ++ u32 ulVal; ++}REG_I2SPCM_CTRL_REG3; ++ ++ ++typedef union _REG_I2SPCM_CTRL_REG4{ ++ struct { ++ u32 rx_mute :1; ++ u32 tx_mute :1; ++ u32 reserved :1; ++ u32 dac_34_independent :1; ++ u32 dac_bclk_lrck_share:1; ++ u32 bclk_lrck_share_en :1; ++ u32 reserved2 :2; ++ u32 rx_last_dac_ch_en :1; ++ u32 rx_last_dac_ch :3; ++ u32 tx_last_adc_ch_en :1; ++ u32 tx_last_adc_ch :3; ++ u32 rx_slot_5 :5; ++ u32 rx_slot_6 :5; ++ u32 reserved3 :6; ++ }r; ++ u32 ulVal; ++}REG_I2SPCM_CTRL_REG4; ++ ++ ++typedef union _REG_I2SPCM_CTRL_REG5{ ++ struct { ++ u32 tx_slot_5 :5; ++ u32 reserved :3; ++ u32 tx_slot_6 :5; ++ u32 reserved2 :3; ++ u32 reserved3 :8; ++ u32 i2s_pcm_clk_div :7; ++ u32 i2s_pcm_clk_div_chan_en :1; ++ }r; ++ u32 ulVal; ++}REG_I2SPCM_CTRL_REG5; ++ ++typedef union _REG_I2SPCM_CTRL_REG6{ ++ struct { ++ u32 reserved :5; ++ u32 rx_pause_cycles :3; ++ u32 rx_pause_start_pos :8; ++ u32 reserved2 :5; ++ u32 tx_pause_cycles :3; ++ u32 tx_pause_start_pos :8; ++ }r; ++ u32 ulVal; ++}REG_I2SPCM_CTRL_REG6; ++ ++typedef union _REG_DIGITAL_BIOS_TEST2{ ++ struct { ++ u32 pull_down_eapd :2; ++ u32 input_en_eapd_pad :1; ++ u32 push_pull_mode :1; ++ u32 eapd_pad_output_driver :2; ++ u32 pll_source :1; ++ u32 i2s_bclk_en :1; ++ u32 i2s_bclk_invert :1; ++ u32 pll_ref_clock :1; ++ u32 class_d_sheild_clk:1; ++ u32 audio_pll_bypass_mode:1; ++ u32 reserved :4; ++ }r; ++ u32 ulVal; ++}REG_DIGITAL_BIOS_TEST2; +diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c +index 10c2a564a715..77bde13147bd 100644 +--- a/sound/soc/codecs/rt5645.c ++++ b/sound/soc/codecs/rt5645.c +@@ -3545,8 +3545,10 @@ MODULE_DEVICE_TABLE(i2c, rt5645_i2c_id); + #ifdef CONFIG_ACPI + static const struct acpi_device_id rt5645_acpi_match[] = { + { "10EC5645", 0 }, ++ { "10EC5648", 0 }, + { "10EC5650", 0 }, + { "10EC5640", 0 }, ++ { "10EC3270", 0 }, + {}, + }; + MODULE_DEVICE_TABLE(acpi, rt5645_acpi_match); +@@ -3658,8 +3660,7 @@ static int rt5645_i2c_probe(struct i2c_client *i2c, + GPIOD_IN); + + if (IS_ERR(rt5645->gpiod_hp_det)) { +- dev_err(&i2c->dev, "failed to initialize gpiod\n"); +- return PTR_ERR(rt5645->gpiod_hp_det); ++ dev_info(&i2c->dev, "failed to initialize gpiod\n"); + } + + for (i = 0; i < ARRAY_SIZE(rt5645->supplies); i++) +diff --git a/sound/soc/codecs/rt5670.c b/sound/soc/codecs/rt5670.c +index 49caf1393aeb..fdc14e50d3b9 100644 +--- a/sound/soc/codecs/rt5670.c ++++ b/sound/soc/codecs/rt5670.c +@@ -2813,6 +2813,8 @@ MODULE_DEVICE_TABLE(i2c, rt5670_i2c_id); + #ifdef CONFIG_ACPI + static const struct acpi_device_id rt5670_acpi_match[] = { + { "10EC5670", 0}, ++ { "10EC5672", 0}, ++ { "10EC5640", 0}, /* quirk */ + { }, + }; + MODULE_DEVICE_TABLE(acpi, rt5670_acpi_match); +diff --git a/sound/soc/codecs/tlv320aic31xx.c b/sound/soc/codecs/tlv320aic31xx.c +index be1a64bfd320..a1b4e14b347a 100644 +--- a/sound/soc/codecs/tlv320aic31xx.c ++++ b/sound/soc/codecs/tlv320aic31xx.c +@@ -192,46 +192,68 @@ static const struct aic31xx_rate_divs aic31xx_divs[] = { + {12000000, 8000, 8, 1920, 128, 48, 2, 128, 48, 2}, + {12000000, 8000, 8, 1920, 128, 32, 3, 128, 32, 3}, + {12500000, 8000, 7, 8643, 128, 48, 2, 128, 48, 2}, ++ {19200000, 8000, 5, 1200, 128, 48, 2, 128, 48, 2}, ++ {19200000, 8000, 5, 1200, 128, 32, 3, 128, 32, 3}, + /* 11.025k rate */ + {12000000, 11025, 7, 5264, 128, 32, 2, 128, 32, 2}, + {12000000, 11025, 8, 4672, 128, 24, 3, 128, 24, 3}, + {12500000, 11025, 7, 2253, 128, 32, 2, 128, 32, 2}, ++ {19200000, 11025, 4, 7040, 128, 24, 2, 128, 32, 2}, ++ {19200000, 11025, 5, 2920, 128, 32, 3, 128, 24, 3}, + /* 16k rate */ + {12000000, 16000, 8, 1920, 128, 24, 2, 128, 24, 2}, + {12000000, 16000, 8, 1920, 128, 16, 3, 128, 16, 3}, + {12500000, 16000, 7, 8643, 128, 24, 2, 128, 24, 2}, ++ {19200000, 16000, 5, 1200, 128, 24, 2, 128, 24, 2}, ++ {19200000, 16000, 5, 1200, 128, 16, 3, 128, 16, 3}, + /* 22.05k rate */ + {12000000, 22050, 7, 5264, 128, 16, 2, 128, 16, 2}, + {12000000, 22050, 8, 4672, 128, 12, 3, 128, 12, 3}, + {12500000, 22050, 7, 2253, 128, 16, 2, 128, 16, 2}, ++ {19200000, 22050, 4, 7040, 128, 16, 2, 128, 16, 2}, ++ {19200000, 22050, 5, 2920, 128, 12, 3, 128, 12, 3}, + /* 32k rate */ + {12000000, 32000, 8, 1920, 128, 12, 2, 128, 12, 2}, + {12000000, 32000, 8, 1920, 128, 8, 3, 128, 8, 3}, + {12500000, 32000, 7, 8643, 128, 12, 2, 128, 12, 2}, ++ {19200000, 32000, 1, 0000, 32, 1, 2, 32, 1, 2}, ++ {19200000, 32000, 5, 1200, 128, 8, 3, 128, 8, 3}, + /* 44.1k rate */ + {12000000, 44100, 7, 5264, 128, 8, 2, 128, 8, 2}, + {12000000, 44100, 8, 4672, 128, 6, 3, 128, 6, 3}, + {12500000, 44100, 7, 2253, 128, 8, 2, 128, 8, 2}, ++ {19200000, 44100, 4, 7040, 128, 8, 2, 128, 8, 2}, ++ {19200000, 44100, 5, 2920, 128, 6, 3, 128, 6, 3}, + /* 48k rate */ + {12000000, 48000, 8, 1920, 128, 8, 2, 128, 8, 2}, + {12000000, 48000, 7, 6800, 96, 5, 4, 96, 5, 4}, + {12500000, 48000, 7, 8643, 128, 8, 2, 128, 8, 2}, ++ {19200000, 48000, 5, 1200, 128, 8, 2, 128, 8, 2}, ++ {19200000, 48000, 4, 8000, 96, 5, 4, 96, 5, 4}, + /* 88.2k rate */ + {12000000, 88200, 7, 5264, 64, 8, 2, 64, 8, 2}, + {12000000, 88200, 8, 4672, 64, 6, 3, 64, 6, 3}, + {12500000, 88200, 7, 2253, 64, 8, 2, 64, 8, 2}, ++ {19200000, 88200, 4, 7040, 64, 8, 2, 64, 8, 2}, ++ {19200000, 88200, 5, 2920, 64, 6, 3, 64, 6, 3}, + /* 96k rate */ + {12000000, 96000, 8, 1920, 64, 8, 2, 64, 8, 2}, + {12000000, 96000, 7, 6800, 48, 5, 4, 48, 5, 4}, + {12500000, 96000, 7, 8643, 64, 8, 2, 64, 8, 2}, ++ {19200000, 96000, 5, 1200, 64, 8, 2, 64, 8, 2}, ++ {19200000, 96000, 4, 8000, 48, 5, 4, 48, 5, 4}, + /* 176.4k rate */ + {12000000, 176400, 7, 5264, 32, 8, 2, 32, 8, 2}, + {12000000, 176400, 8, 4672, 32, 6, 3, 32, 6, 3}, + {12500000, 176400, 7, 2253, 32, 8, 2, 32, 8, 2}, ++ {19200000, 176400, 4, 7040, 32, 8, 2, 32, 8, 2}, ++ {19200000, 176400, 5, 2920, 32, 6, 3, 32, 6, 3}, + /* 192k rate */ + {12000000, 192000, 8, 1920, 32, 8, 2, 32, 8, 2}, + {12000000, 192000, 7, 6800, 24, 5, 4, 24, 5, 4}, + {12500000, 192000, 7, 8643, 32, 8, 2, 32, 8, 2}, ++ {19200000, 192000, 5, 1200, 32, 8, 2, 32, 8, 2}, ++ {19200000, 192000, 4, 8000, 24, 5, 4, 24, 5, 4}, + }; + + static const char * const ldac_in_text[] = { +diff --git a/sound/soc/intel/Kconfig b/sound/soc/intel/Kconfig +index fd5d1e091038..76de542c66b7 100644 +--- a/sound/soc/intel/Kconfig ++++ b/sound/soc/intel/Kconfig +@@ -172,6 +172,32 @@ config SND_SOC_INTEL_BYTCR_RT5651_MACH + Say Y if you have such a device. + If unsure select "N". + ++config SND_SOC_INTEL_BYTCR_AIC3100_MACH ++ tristate "ASoC Audio driver for Intel Baytrail and Baytrail-CR with TLV320AIC31XX codec" ++ depends on X86 && I2C && ACPI ++ select SND_SOC_TLV320AIC31XX ++ select SND_SST_MFLD_PLATFORM ++ select SND_SST_IPC_ACPI ++ select SND_SOC_INTEL_SST_MATCH if ACPI ++ help ++ This adds support for ASoC machine driver for Intel(R) Baytrail and Baytrail-CR ++ platforms with TLV320AIC31XX audio codec. ++ Say Y if you have such a device. ++ If unsure select "N". ++ ++config SND_SOC_INTEL_CHT_CX2072X_MACH ++ tristate "ASoC Audio driver for Intel Baytrail and Cherrytrail with CX2072X codec" ++ depends on X86_INTEL_LPSS && I2C && ACPI ++ select SND_SOC_CX2072X ++ select SND_SST_MFLD_PLATFORM ++ select SND_SST_IPC_ACPI ++ select SND_SOC_INTEL_SST_MATCH if ACPI ++ help ++ This adds support for ASoC machine driver for Intel(R) Cherrytrail & Braswell ++ platforms with Conexant CX2072X audio codec. ++ Say Y if you have such a device. ++ If unsure select "N". ++ + config SND_SOC_INTEL_CHT_BSW_RT5672_MACH + tristate "ASoC Audio driver for Intel Cherrytrail & Braswell with RT5672 codec" + depends on X86_INTEL_LPSS && I2C && ACPI +diff --git a/sound/soc/intel/atom/sst/sst_acpi.c b/sound/soc/intel/atom/sst/sst_acpi.c +index 0a88537ca58a..4e230282205d 100644 +--- a/sound/soc/intel/atom/sst/sst_acpi.c ++++ b/sound/soc/intel/atom/sst/sst_acpi.c +@@ -400,6 +400,7 @@ static int sst_acpi_remove(struct platform_device *pdev) + static unsigned long cht_machine_id; + + #define CHT_SURFACE_MACH 1 ++#define BYT_THINKPAD_10 2 + + static int cht_surface_quirk_cb(const struct dmi_system_id *id) + { +@@ -407,6 +408,23 @@ static int cht_surface_quirk_cb(const struct dmi_system_id *id) + return 1; + } + ++static int byt_thinkpad10_quirk_cb(const struct dmi_system_id *id) ++{ ++ cht_machine_id = BYT_THINKPAD_10; ++ return 1; ++} ++ ++ ++static const struct dmi_system_id byt_table[] = { ++ { ++ .callback = byt_thinkpad10_quirk_cb, ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "20C3001VHH"), ++ }, ++ }, ++ { } ++}; + + static const struct dmi_system_id cht_table[] = { + { +@@ -424,6 +442,10 @@ static struct sst_acpi_mach cht_surface_mach = { + "10EC5640", "cht-bsw-rt5645", "intel/fw_sst_22a8.bin", "cht-bsw", NULL, + &chv_platform_data }; + ++static struct sst_acpi_mach byt_thinkpad_10 = { ++ "10EC5640", "cht-bsw-rt5672", "intel/fw_sst_0f28.bin", "cht-bsw", NULL, ++ &byt_rvp_platform_data }; ++ + static struct sst_acpi_mach *cht_quirk(void *arg) + { + struct sst_acpi_mach *mach = arg; +@@ -436,8 +458,21 @@ static struct sst_acpi_mach *cht_quirk(void *arg) + return mach; + } + ++static struct sst_acpi_mach *byt_quirk(void *arg) ++{ ++ struct sst_acpi_mach *mach = arg; ++ ++ dmi_check_system(byt_table); ++ ++ if (cht_machine_id == BYT_THINKPAD_10) ++ return &byt_thinkpad_10; ++ else ++ return mach; ++} ++ ++ + static struct sst_acpi_mach sst_acpi_bytcr[] = { +- {"10EC5640", "bytcr_rt5640", "intel/fw_sst_0f28.bin", "bytcr_rt5640", NULL, ++ {"10EC5640", "bytcr_rt5640", "intel/fw_sst_0f28.bin", "bytcr_rt5640", byt_quirk, + &byt_rvp_platform_data }, + {"10EC5642", "bytcr_rt5640", "intel/fw_sst_0f28.bin", "bytcr_rt5640", NULL, + &byt_rvp_platform_data }, +@@ -445,6 +480,16 @@ static struct sst_acpi_mach sst_acpi_bytcr[] = { + &byt_rvp_platform_data }, + {"10EC5651", "bytcr_rt5651", "intel/fw_sst_0f28.bin", "bytcr_rt5651", NULL, + &byt_rvp_platform_data }, ++ /* some Baytrail platforms rely on RT5645, use CHT machine driver */ ++ {"10EC5645", "cht-bsw-rt5645", "intel/fw_sst_0f28.bin", "cht-bsw", NULL, ++ &byt_rvp_platform_data }, ++ {"10EC5648", "cht-bsw-rt5645", "intel/fw_sst_0f28.bin", "cht-bsw", NULL, ++ &byt_rvp_platform_data }, ++ /* use CHT driver to Baytrail Chromebooks */ ++ {"193C9890", "cht-bsw-max98090", "intel/fw_sst_0f28.bin", "cht-bsw", NULL, ++ &byt_rvp_platform_data }, ++ {"14F10720", "cht-cx2072x", "intel/fw_sst_0f28.bin", "cht-bsw", NULL, ++ &byt_rvp_platform_data }, + {}, + }; + +@@ -456,7 +501,9 @@ static struct sst_acpi_mach sst_acpi_chv[] = { + &chv_platform_data }, + {"10EC5650", "cht-bsw-rt5645", "intel/fw_sst_22a8.bin", "cht-bsw", NULL, + &chv_platform_data }, +- {"193C9890", "cht-bsw-max98090", "intel/fw_sst_22a8.bin", "cht-bsw", NULL, ++ {"10EC3270", "cht-bsw-rt5645", "intel/fw_sst_22a8.bin", "cht-bsw", NULL, ++ &chv_platform_data }, ++ {"14F10720", "cht-cx2072x", "intel/fw_sst_22a8.bin", "cht-bsw", NULL, + &chv_platform_data }, + /* some CHT-T platforms rely on RT5640, use Baytrail machine driver */ + {"10EC5640", "bytcr_rt5640", "intel/fw_sst_22a8.bin", "bytcr_rt5640", cht_quirk, +diff --git a/sound/soc/intel/boards/Makefile b/sound/soc/intel/boards/Makefile +index 5639f10774e6..68dd93d91de9 100644 +--- a/sound/soc/intel/boards/Makefile ++++ b/sound/soc/intel/boards/Makefile +@@ -7,6 +7,8 @@ snd-soc-sst-bxt-da7219_max98357a-objs := bxt_da7219_max98357a.o + snd-soc-sst-bxt-rt298-objs := bxt_rt298.o + snd-soc-sst-bytcr-rt5640-objs := bytcr_rt5640.o + snd-soc-sst-bytcr-rt5651-objs := bytcr_rt5651.o ++snd-soc-sst-bytcr-aic3100-objs := bytcr_aic3100.o ++snd-soc-sst-cht-cx2072x-objs := cht_cx2072x.o + snd-soc-sst-cht-bsw-rt5672-objs := cht_bsw_rt5672.o + snd-soc-sst-cht-bsw-rt5645-objs := cht_bsw_rt5645.o + snd-soc-sst-cht-bsw-max98090_ti-objs := cht_bsw_max98090_ti.o +@@ -23,6 +25,8 @@ obj-$(CONFIG_SND_SOC_INTEL_BROADWELL_MACH) += snd-soc-sst-broadwell.o + obj-$(CONFIG_SND_SOC_INTEL_BDW_RT5677_MACH) += snd-soc-sst-bdw-rt5677-mach.o + obj-$(CONFIG_SND_SOC_INTEL_BYTCR_RT5640_MACH) += snd-soc-sst-bytcr-rt5640.o + obj-$(CONFIG_SND_SOC_INTEL_BYTCR_RT5651_MACH) += snd-soc-sst-bytcr-rt5651.o ++obj-$(CONFIG_SND_SOC_INTEL_BYTCR_AIC3100_MACH) += snd-soc-sst-bytcr-aic3100.o ++obj-$(CONFIG_SND_SOC_INTEL_CHT_CX2072X_MACH) += snd-soc-sst-cht-cx2072x.o + obj-$(CONFIG_SND_SOC_INTEL_CHT_BSW_RT5672_MACH) += snd-soc-sst-cht-bsw-rt5672.o + obj-$(CONFIG_SND_SOC_INTEL_CHT_BSW_RT5645_MACH) += snd-soc-sst-cht-bsw-rt5645.o + obj-$(CONFIG_SND_SOC_INTEL_CHT_BSW_MAX98090_TI_MACH) += snd-soc-sst-cht-bsw-max98090_ti.o +diff --git a/sound/soc/intel/boards/bytcr_aic3100.c b/sound/soc/intel/boards/bytcr_aic3100.c +new file mode 100644 +index 000000000000..f6a45f685933 +--- /dev/null ++++ b/sound/soc/intel/boards/bytcr_aic3100.c +@@ -0,0 +1,608 @@ ++/* ++ * bytcr_aic3100.c - ASoc Machine driver for Intel Byt CR platform ++ * ++ * Copyright (C) 2016 Intel Corporation ++ * ++ * Author: Pierre-Louis Bossart ++ * Based on earlier 3.14 work by Praveen Diwakar and Gurudatta Bhakte ++ * ++ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; version 2 of the License. ++ * ++ * This program is distributed in the hope that it will be useful, but ++ * WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "../../codecs/tlv320aic31xx.h" ++#include "../atom/sst-atom-controls.h" ++#include "../common/sst-acpi.h" ++#include "../common/sst-dsp.h" ++ ++enum { ++ BYT_AIC3100_IN1_MAP, ++}; ++ ++#define BYT_AIC3100_MAP(quirk) ((quirk) & 0xff) ++#define BYT_AIC3100_SSP2_AIF BIT(16) /* default true, use SSP2 */ ++#define BYT_AIC3100_MCLK_25MHZ BIT(18) /* default false, use 19.2MHz */ ++ ++struct byt_aic3100_private { ++ struct clk *mclk; ++}; ++ ++static unsigned long byt_aic3100_quirk = ++ BYT_AIC3100_IN1_MAP | BYT_AIC3100_SSP2_AIF; ++ ++static void log_quirks(struct device *dev) ++{ ++ if (BYT_AIC3100_MAP(byt_aic3100_quirk) == BYT_AIC3100_IN1_MAP) ++ dev_info(dev, "quirk IN1_MAP enabled"); ++ if (byt_aic3100_quirk & BYT_AIC3100_SSP2_AIF) ++ dev_info(dev, "quirk SSP2_AIF enabled"); ++ else ++ dev_info(dev, "quirk SSP0_AIF enabled"); ++ if (byt_aic3100_quirk & BYT_AIC3100_MCLK_25MHZ) ++ dev_info(dev, "quirk MCLK_25MHZ enabled"); ++ else ++ dev_info(dev, "quirk MCLK_19200kHZ enabled"); ++} ++ ++#define BYT_CODEC_DAI1 "tlv320aic31xx-hifi" ++ ++static inline struct snd_soc_dai *byt_get_codec_dai(struct snd_soc_card *card) ++{ ++ struct snd_soc_pcm_runtime *rtd; ++ ++ list_for_each_entry(rtd, &card->rtd_list, list) { ++ if (!strncmp(rtd->codec_dai->name, BYT_CODEC_DAI1, ++ strlen(BYT_CODEC_DAI1))) ++ return rtd->codec_dai; ++ } ++ return NULL; ++} ++ ++static int platform_clock_control(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *k, int event) ++{ ++ struct snd_soc_dapm_context *dapm = w->dapm; ++ struct snd_soc_card *card = dapm->card; ++ struct snd_soc_dai *codec_dai; ++ struct byt_aic3100_private *priv = snd_soc_card_get_drvdata(card); ++ int ret; ++ ++ codec_dai = byt_get_codec_dai(card); ++ if (!codec_dai) { ++ dev_err(card->dev, ++ "Codec dai not found; Unable to set platform clock\n"); ++ return -EIO; ++ } ++ ++ if (SND_SOC_DAPM_EVENT_ON(event)) { ++ if (priv->mclk) { ++ ret = clk_prepare_enable(priv->mclk); ++ if (ret < 0) { ++ dev_err(card->dev, ++ "could not configure MCLK state"); ++ return ret; ++ } ++ } ++ ++ if (byt_aic3100_quirk & BYT_AIC3100_MCLK_25MHZ) { ++ ret = snd_soc_dai_set_sysclk(codec_dai, ++ AIC31XX_PLL_CLKIN_MCLK, ++ 25000000, ++ SND_SOC_CLOCK_IN); ++ } else { ++ ret = snd_soc_dai_set_sysclk(codec_dai, ++ AIC31XX_PLL_CLKIN_MCLK, ++ 19200000, ++ SND_SOC_CLOCK_IN); ++ } ++ } else { ++ /* ++ * Set codec clock source to internal clock before ++ * turning off the platform clock. Codec needs clock ++ * for Jack detection and button press ++ */ ++ /* FIXME: use RC oscillator? */ ++ ret = snd_soc_dai_set_sysclk(codec_dai, AIC31XX_PLL_CLKIN_MCLK, ++ 0, SND_SOC_CLOCK_IN); ++ if (!ret) { ++ if (priv->mclk) ++ clk_disable_unprepare(priv->mclk); ++ } ++ } ++ ++ if (ret < 0) { ++ dev_err(card->dev, "can't set codec sysclk: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static const struct snd_soc_dapm_widget byt_aic3100_widgets[] = { ++ SND_SOC_DAPM_HP("Headphone", NULL), ++ SND_SOC_DAPM_MIC("Headset Mic", NULL), ++ SND_SOC_DAPM_MIC("Internal Mic", NULL), ++ SND_SOC_DAPM_SPK("Speakers", NULL), ++ SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, ++ platform_clock_control, SND_SOC_DAPM_PRE_PMU | ++ SND_SOC_DAPM_POST_PMD), ++ ++}; ++ ++static const struct snd_soc_dapm_route byt_aic3100_audio_map[] = { ++ /* External Speakers: HFL, HFR */ ++ {"Speakers", NULL, "SPK"}, ++ ++ /* Headset Stereophone(Headphone): HSOL, HSOR */ ++ {"Headphone", NULL, "HPL"}, ++ {"Headphone", NULL, "HPR"}, ++ ++ {"Headphone", NULL, "Platform Clock"}, ++ {"Headset Mic", NULL, "Platform Clock"}, ++ {"Internal Mic", NULL, "Platform Clock"}, ++ {"Speaker", NULL, "Platform Clock"}, ++ ++}; ++ ++static const struct snd_soc_dapm_route byt_aic3100_intmic_in1_map[] = { ++ {"micbias", NULL, "Internal Mic"}, ++ /* Headset Mic: Headset Mic with bias */ ++ {"MIC1RP", NULL, "Headset Mic"}, ++}; ++ ++static const struct snd_soc_dapm_route byt_aic3100_ssp2_aif_map[] = { ++ {"ssp2 Tx", NULL, "codec_out0"}, ++ {"ssp2 Tx", NULL, "codec_out1"}, ++ {"codec_in0", NULL, "ssp2 Rx"}, ++ {"codec_in1", NULL, "ssp2 Rx"}, ++ ++ {"Playback", NULL, "ssp2 Tx"}, ++ {"ssp2 Rx", NULL, "Capture"}, ++}; ++ ++static const struct snd_soc_dapm_route byt_aic3100_ssp0_aif_map[] = { ++ {"ssp0 Tx", NULL, "modem_out"}, ++ {"modem_in", NULL, "ssp0 Rx"}, ++ ++ {"Playback", NULL, "ssp0 Tx"}, ++ {"ssp0 Rx", NULL, "Capture"}, ++}; ++ ++static const struct snd_kcontrol_new byt_aic3100_controls[] = { ++ SOC_DAPM_PIN_SWITCH("Headphone"), ++ SOC_DAPM_PIN_SWITCH("Headset Mic"), ++ SOC_DAPM_PIN_SWITCH("Internal Mic"), ++ SOC_DAPM_PIN_SWITCH("Speaker"), ++}; ++ ++static int byt_aic3100_aif1_hw_params(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params) ++{ ++ struct snd_soc_pcm_runtime *rtd = substream->private_data; ++ struct snd_soc_dai *codec_dai = rtd->codec_dai; ++ int ret; ++ ++ if (byt_aic3100_quirk & BYT_AIC3100_MCLK_25MHZ) { ++ ret = snd_soc_dai_set_sysclk(codec_dai, AIC31XX_PLL_CLKIN_MCLK, ++ 25000000, ++ SND_SOC_CLOCK_IN); ++ } else { ++ ret = snd_soc_dai_set_sysclk(codec_dai, AIC31XX_PLL_CLKIN_MCLK, ++ 19200000, ++ SND_SOC_CLOCK_IN); ++ } ++ ++ if (ret < 0) { ++ dev_err(rtd->dev, "can't set codec clock %d\n", ret); ++ return ret; ++ } ++ ++ if (byt_aic3100_quirk & BYT_AIC3100_MCLK_25MHZ) { ++ ret = snd_soc_dai_set_pll(codec_dai, 0, AIC31XX_PLL_CLKIN_MCLK, ++ 250000000, params_rate(params)); ++ } else { ++ ret = snd_soc_dai_set_pll(codec_dai, 0, AIC31XX_PLL_CLKIN_MCLK, ++ 19200000, params_rate(params)); ++ ++ } ++ ++ if (ret < 0) { ++ dev_err(rtd->dev, "can't set codec pll: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++/* uncomment when we have an actual quirk ++static int byt_aic3100_quirk_cb(const struct dmi_system_id *id) ++{ ++ byt_aic3100_quirk = (unsigned long)id->driver_data; ++ return 1; ++} ++*/ ++ ++static const struct dmi_system_id byt_aic3100_quirk_table[] = { ++ {} ++}; ++ ++static int byt_aic3100_init(struct snd_soc_pcm_runtime *runtime) ++{ ++ int ret; ++ struct snd_soc_card *card = runtime->card; ++ const struct snd_soc_dapm_route *custom_map; ++ struct byt_aic3100_private *priv = snd_soc_card_get_drvdata(card); ++ int num_routes; ++ ++ card->dapm.idle_bias_off = true; ++ ++ ret = snd_soc_add_card_controls(card, byt_aic3100_controls, ++ ARRAY_SIZE(byt_aic3100_controls)); ++ if (ret) { ++ dev_err(card->dev, "unable to add card controls\n"); ++ return ret; ++ } ++ ++ switch (BYT_AIC3100_MAP(byt_aic3100_quirk)) { ++ case BYT_AIC3100_IN1_MAP: ++ default: ++ custom_map = byt_aic3100_intmic_in1_map; ++ num_routes = ARRAY_SIZE(byt_aic3100_intmic_in1_map); ++ } ++ ++ ret = snd_soc_dapm_add_routes(&card->dapm, custom_map, num_routes); ++ if (ret) ++ return ret; ++ ++ if (byt_aic3100_quirk & BYT_AIC3100_SSP2_AIF) { ++ ret = snd_soc_dapm_add_routes(&card->dapm, ++ byt_aic3100_ssp2_aif_map, ++ ARRAY_SIZE(byt_aic3100_ssp2_aif_map)); ++ } else { ++ ret = snd_soc_dapm_add_routes(&card->dapm, ++ byt_aic3100_ssp0_aif_map, ++ ARRAY_SIZE(byt_aic3100_ssp0_aif_map)); ++ } ++ if (ret) ++ return ret; ++ ++ snd_soc_dapm_ignore_suspend(&card->dapm, "Headphone"); ++ snd_soc_dapm_ignore_suspend(&card->dapm, "Speaker"); ++ ++ if (priv->mclk) { ++ /* ++ * The firmware might enable the clock at ++ * boot (this information may or may not ++ * be reflected in the enable clock register). ++ * To change the rate we must disable the clock ++ * first to cover these cases. Due to common ++ * clock framework restrictions that do not allow ++ * to disable a clock that has not been enabled, ++ * we need to enable the clock first. ++ */ ++ ret = clk_prepare_enable(priv->mclk); ++ if (!ret) ++ clk_disable_unprepare(priv->mclk); ++ ++ if (byt_aic3100_quirk & BYT_AIC3100_MCLK_25MHZ) ++ ret = clk_set_rate(priv->mclk, 25000000); ++ else ++ ret = clk_set_rate(priv->mclk, 19200000); ++ ++ if (ret) ++ dev_err(card->dev, "unable to set MCLK rate\n"); ++ } ++ ++ return ret; ++} ++ ++static const struct snd_soc_pcm_stream byt_aic3100_dai_params = { ++ .formats = SNDRV_PCM_FMTBIT_S24_LE, ++ .rate_min = 48000, ++ .rate_max = 48000, ++ .channels_min = 2, ++ .channels_max = 2, ++}; ++ ++static int byt_aic3100_codec_fixup(struct snd_soc_pcm_runtime *rtd, ++ struct snd_pcm_hw_params *params) ++{ ++ struct snd_interval *rate = hw_param_interval(params, ++ SNDRV_PCM_HW_PARAM_RATE); ++ struct snd_interval *channels = hw_param_interval(params, ++ SNDRV_PCM_HW_PARAM_CHANNELS); ++ int ret; ++ ++ /* The DSP will covert the FE rate to 48k, stereo */ ++ rate->min = rate->max = 48000; ++ channels->min = channels->max = 2; ++ ++ if (!(byt_aic3100_quirk & BYT_AIC3100_SSP2_AIF)) { ++ ++ /* set SSP0 to 16-bit */ ++ params_set_format(params, SNDRV_PCM_FORMAT_S16_LE); ++ ++ /* ++ * Default mode for SSP configuration is TDM 4 slot, ++ * override config with explicit setting to I2S 2ch 16-bit. ++ * The word length is set with dai_set_tdm_slot() since there ++ * is no other API exposed ++ */ ++ ret = snd_soc_dai_set_fmt(rtd->cpu_dai, ++ SND_SOC_DAIFMT_I2S | ++ SND_SOC_DAIFMT_NB_IF | ++ SND_SOC_DAIFMT_CBS_CFS ++ ); ++ if (ret < 0) { ++ dev_err(rtd->dev, ++ "can't set format to I2S, err %d\n", ret); ++ return ret; ++ } ++ ++ ret = snd_soc_dai_set_tdm_slot(rtd->cpu_dai, 0x3, 0x3, 2, 16); ++ if (ret < 0) { ++ dev_err(rtd->dev, ++ "can't set I2S config, err %d\n", ret); ++ return ret; ++ } ++ ++ } else { ++ ++ /* set SSP2 to 24-bit */ ++ params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); ++ ++ /* ++ * Default mode for SSP configuration is TDM 4 slot, ++ * override config with explicit setting to I2S 2ch 24-bit. ++ * The word length is set with dai_set_tdm_slot() since ++ * there is no other API exposed ++ */ ++ ret = snd_soc_dai_set_fmt(rtd->cpu_dai, ++ SND_SOC_DAIFMT_I2S | ++ SND_SOC_DAIFMT_NB_IF | ++ SND_SOC_DAIFMT_CBS_CFS ++ ); ++ if (ret < 0) { ++ dev_err(rtd->dev, ++ "can't set format to I2S, err %d\n", ret); ++ return ret; ++ } ++ ++ ret = snd_soc_dai_set_tdm_slot(rtd->cpu_dai, 0x3, 0x3, 2, 24); ++ if (ret < 0) { ++ dev_err(rtd->dev, ++ "can't set I2S config, err %d\n", ret); ++ return ret; ++ } ++ } ++ return 0; ++} ++ ++static int byt_aic3100_aif1_startup(struct snd_pcm_substream *substream) ++{ ++ return snd_pcm_hw_constraint_single(substream->runtime, ++ SNDRV_PCM_HW_PARAM_RATE, 48000); ++} ++ ++static struct snd_soc_ops byt_aic3100_aif1_ops = { ++ .startup = byt_aic3100_aif1_startup, ++}; ++ ++static struct snd_soc_ops byt_aic3100_be_ssp2_ops = { ++ .hw_params = byt_aic3100_aif1_hw_params, ++}; ++ ++static struct snd_soc_dai_link byt_aic3100_dais[] = { ++ [MERR_DPCM_AUDIO] = { ++ .name = "Baytrail Audio Port", ++ .stream_name = "Baytrail Audio", ++ .cpu_dai_name = "media-cpu-dai", ++ .codec_dai_name = "snd-soc-dummy-dai", ++ .codec_name = "snd-soc-dummy", ++ .platform_name = "sst-mfld-platform", ++ .ignore_suspend = 1, ++ .dynamic = 1, ++ .dpcm_playback = 1, ++ .dpcm_capture = 1, ++ .ops = &byt_aic3100_aif1_ops, ++ }, ++ [MERR_DPCM_DEEP_BUFFER] = { ++ .name = "Deep-Buffer Audio Port", ++ .stream_name = "Deep-Buffer Audio", ++ .cpu_dai_name = "deepbuffer-cpu-dai", ++ .codec_dai_name = "snd-soc-dummy-dai", ++ .codec_name = "snd-soc-dummy", ++ .platform_name = "sst-mfld-platform", ++ .ignore_suspend = 1, ++ .nonatomic = true, ++ .dynamic = 1, ++ .dpcm_playback = 1, ++ .ops = &byt_aic3100_aif1_ops, ++ }, ++ [MERR_DPCM_COMPR] = { ++ .name = "Baytrail Compressed Port", ++ .stream_name = "Baytrail Compress", ++ .cpu_dai_name = "compress-cpu-dai", ++ .codec_dai_name = "snd-soc-dummy-dai", ++ .codec_name = "snd-soc-dummy", ++ .platform_name = "sst-mfld-platform", ++ }, ++ /* back ends */ ++ { ++ .name = "SSP2-Codec", ++ .id = 1, ++ .cpu_dai_name = "ssp2-port", /* overwritten for ssp0 routing */ ++ .platform_name = "sst-mfld-platform", ++ .no_pcm = 1, ++ .codec_dai_name = "tlv320aic31xx-hifi", ++ .codec_name = "i2c-10TI3100:00", /* overwritten with HID */ ++ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF ++ | SND_SOC_DAIFMT_CBS_CFS, ++ .be_hw_params_fixup = byt_aic3100_codec_fixup, ++ .ignore_suspend = 1, ++ .dpcm_playback = 1, ++ .dpcm_capture = 1, ++ .init = byt_aic3100_init, ++ .ops = &byt_aic3100_be_ssp2_ops, ++ }, ++}; ++ ++/* SoC card */ ++static struct snd_soc_card byt_aic3100_card = { ++ .name = "bytcr-aic3100", ++ .owner = THIS_MODULE, ++ .dai_link = byt_aic3100_dais, ++ .num_links = ARRAY_SIZE(byt_aic3100_dais), ++ .dapm_widgets = byt_aic3100_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(byt_aic3100_widgets), ++ .dapm_routes = byt_aic3100_audio_map, ++ .num_dapm_routes = ARRAY_SIZE(byt_aic3100_audio_map), ++ .fully_routed = true, ++}; ++ ++static char byt_aic3100_codec_name[16];/* i2c-:00 with HID being 8 chars */ ++static char byt_aic3100_cpu_dai_name[10]; /* = "ssp[0|2]-port" */ ++ ++static bool is_valleyview(void) ++{ ++ static const struct x86_cpu_id cpu_ids[] = { ++ { X86_VENDOR_INTEL, 6, 55 }, /* Valleyview, Bay Trail */ ++ {} ++ }; ++ ++ if (!x86_match_cpu(cpu_ids)) ++ return false; ++ return true; ++} ++ ++ ++static int snd_byt_aic3100_mc_probe(struct platform_device *pdev) ++{ ++ int ret_val = 0; ++ struct sst_acpi_mach *mach; ++ const char *i2c_name = NULL; ++ int i; ++ int dai_index; ++ struct byt_aic3100_private *priv; ++ bool is_bytcr = false; ++ ++ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_ATOMIC); ++ if (!priv) ++ return -ENOMEM; ++ ++ /* register the soc card */ ++ byt_aic3100_card.dev = &pdev->dev; ++ mach = byt_aic3100_card.dev->platform_data; ++ snd_soc_card_set_drvdata(&byt_aic3100_card, priv); ++ ++ /* fix index of codec dai */ ++ dai_index = MERR_DPCM_COMPR + 1; ++ for (i = 0; i < ARRAY_SIZE(byt_aic3100_dais); i++) { ++ if (!strcmp(byt_aic3100_dais[i].codec_name, ++ "i2c-10TI3100:00")) { ++ dai_index = i; ++ break; ++ } ++ } ++ ++ /* fixup codec name based on HID */ ++ i2c_name = sst_acpi_find_name_from_hid(mach->id); ++ if (i2c_name != NULL) { ++ snprintf(byt_aic3100_codec_name, sizeof(byt_aic3100_codec_name), ++ "%s%s", "i2c-", i2c_name); ++ ++ byt_aic3100_dais[dai_index].codec_name = byt_aic3100_codec_name; ++ } ++ ++ /* ++ * swap SSP0 if bytcr is detected ++ * (will be overridden if DMI quirk is detected) ++ */ ++ if (is_valleyview()) { ++ struct sst_platform_info *p_info = mach->pdata; ++ const struct sst_res_info *res_info = p_info->res_info; ++ ++ if (res_info->acpi_ipc_irq_index == 0) { ++ is_bytcr = true; ++ byt_aic3100_quirk &= ~BYT_AIC3100_SSP2_AIF; ++ } ++ } ++ ++ /* check quirks before creating card */ ++ dmi_check_system(byt_aic3100_quirk_table); ++ log_quirks(&pdev->dev); ++ ++ if (!(byt_aic3100_quirk & BYT_AIC3100_SSP2_AIF)) { ++ ++ /* fixup cpu dai name name */ ++ snprintf(byt_aic3100_cpu_dai_name, ++ sizeof(byt_aic3100_cpu_dai_name), ++ "%s", "ssp0-port"); ++ ++ byt_aic3100_dais[dai_index].cpu_dai_name = ++ byt_aic3100_cpu_dai_name; ++ } ++ ++ if (is_valleyview()) { ++ priv->mclk = devm_clk_get(&pdev->dev, "pmc_plt_clk_3"); ++ if (IS_ERR(priv->mclk)) { ++ dev_err(&pdev->dev, ++ "Failed to get MCLK from pmc_plt_clk_3: %ld\n", ++ PTR_ERR(priv->mclk)); ++ ++ /* ++ * Audio output only works with MCLK enabled ++ */ ++ return PTR_ERR(priv->mclk); ++ } ++ } ++ ++ ret_val = devm_snd_soc_register_card(&pdev->dev, &byt_aic3100_card); ++ ++ if (ret_val) { ++ dev_err(&pdev->dev, "devm_snd_soc_register_card failed %d\n", ++ ret_val); ++ return ret_val; ++ } ++ platform_set_drvdata(pdev, &byt_aic3100_card); ++ return ret_val; ++} ++ ++static struct platform_driver snd_byt_aic3100_mc_driver = { ++ .driver = { ++ .name = "bytcr_aic3100", ++ .pm = &snd_soc_pm_ops, ++ }, ++ .probe = snd_byt_aic3100_mc_probe, ++}; ++ ++module_platform_driver(snd_byt_aic3100_mc_driver); ++ ++MODULE_DESCRIPTION("ASoC Intel(R) Baytrail CR Machine driver"); ++MODULE_AUTHOR("Pierre-Louis Bossart "); ++MODULE_LICENSE("GPL v2"); ++MODULE_ALIAS("platform:bytcr_aic3100"); +diff --git a/sound/soc/intel/boards/bytcr_rt5640.c b/sound/soc/intel/boards/bytcr_rt5640.c +index bff77a1f27fc..c2ecbdc28459 100644 +--- a/sound/soc/intel/boards/bytcr_rt5640.c ++++ b/sound/soc/intel/boards/bytcr_rt5640.c +@@ -389,6 +389,16 @@ static const struct dmi_system_id byt_rt5640_quirk_table[] = { + BYT_RT5640_SSP0_AIF1), + + }, ++ { ++ .callback = byt_rt5640_quirk_cb, ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Insyde"), ++ }, ++ .driver_data = (unsigned long *)(BYT_RT5640_IN3_MAP | ++ BYT_RT5640_MCLK_EN | ++ BYT_RT5640_SSP0_AIF1), ++ ++ }, + {} + }; + +@@ -689,6 +699,10 @@ static bool is_valleyview(void) + return true; + } + ++struct acpi_chan_package { /* ACPICA seems to require 64 bit integers */ ++ u64 aif_value; /* 1: AIF1, 2: AIF2 */ ++ u64 mclock_value; /* usually 25MHz (0x17d7940), ignored */ ++}; + + static int snd_byt_rt5640_mc_probe(struct platform_device *pdev) + { +@@ -698,6 +712,7 @@ static int snd_byt_rt5640_mc_probe(struct platform_device *pdev) + int i; + int dai_index; + struct byt_rt5640_private *priv; ++ bool is_bytcr = false; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_ATOMIC); + if (!priv) +@@ -734,8 +749,52 @@ static int snd_byt_rt5640_mc_probe(struct platform_device *pdev) + struct sst_platform_info *p_info = mach->pdata; + const struct sst_res_info *res_info = p_info->res_info; + +- /* TODO: use CHAN package info from BIOS to detect AIF1/AIF2 */ +- if (res_info->acpi_ipc_irq_index == 0) { ++ if (res_info->acpi_ipc_irq_index == 0) ++ is_bytcr = true; ++ } ++ ++ if (is_bytcr) { ++ /* ++ * Baytrail CR platforms may have CHAN package in BIOS, try ++ * to find relevant routing quirk based as done on Windows ++ * platforms. We have to read the information directly from the ++ * BIOS, at this stage the card is not created and the links ++ * with the codec driver/pdata are non-existent ++ */ ++ ++ struct acpi_chan_package chan_package; ++ ++ /* format specified: 2 64-bit integers */ ++ struct acpi_buffer format = {sizeof("NN"), "NN"}; ++ struct acpi_buffer state = {0, NULL}; ++ struct sst_acpi_package_context pkg_ctx; ++ bool pkg_found = false; ++ ++ state.length = sizeof(chan_package); ++ state.pointer = &chan_package; ++ ++ pkg_ctx.name = "CHAN"; ++ pkg_ctx.length = 2; ++ pkg_ctx.format = &format; ++ pkg_ctx.state = &state; ++ pkg_ctx.data_valid = false; ++ ++ pkg_found = sst_acpi_find_package_from_hid(mach->id, &pkg_ctx); ++ if (pkg_found) { ++ if (chan_package.aif_value == 1) { ++ dev_info(&pdev->dev, "BIOS Routing: AIF1 connected\n"); ++ byt_rt5640_quirk |= BYT_RT5640_SSP0_AIF1; ++ } else if (chan_package.aif_value == 2) { ++ dev_info(&pdev->dev, "BIOS Routing: AIF2 connected\n"); ++ byt_rt5640_quirk |= BYT_RT5640_SSP0_AIF2; ++ } else { ++ dev_info(&pdev->dev, "BIOS Routing isn't valid, ignored\n"); ++ pkg_found = false; ++ } ++ } ++ ++ if (!pkg_found) { ++ /* no BIOS indications, assume SSP0-AIF2 connection */ + byt_rt5640_quirk |= BYT_RT5640_SSP0_AIF2; + } + } +@@ -774,7 +833,21 @@ static int snd_byt_rt5640_mc_probe(struct platform_device *pdev) + dev_err(&pdev->dev, + "Failed to get MCLK from pmc_plt_clk_3: %ld\n", + PTR_ERR(priv->mclk)); +- return PTR_ERR(priv->mclk); ++ ++ if (is_bytcr) { ++ /* ++ * Audio output on Baytrail CR only works ++ * with MCLK enabled ++ */ ++ return PTR_ERR(priv->mclk); ++ } else { ++ /* ++ * Audio output can work with bitclock only ++ * on Baytrail, with a limited audio quality ++ * degradation ++ */ ++ byt_rt5640_quirk &= ~BYT_RT5640_MCLK_EN; ++ } + } + } + +diff --git a/sound/soc/intel/boards/bytcr_rt5651.c b/sound/soc/intel/boards/bytcr_rt5651.c +index 35f591eab3c9..1c672768fc35 100644 +--- a/sound/soc/intel/boards/bytcr_rt5651.c ++++ b/sound/soc/intel/boards/bytcr_rt5651.c +@@ -24,21 +24,134 @@ + #include + #include + #include ++#include ++#include ++#include + #include + #include + #include + #include + #include "../../codecs/rt5651.h" + #include "../atom/sst-atom-controls.h" ++#include "../common/sst-acpi.h" ++ ++enum { ++ BYT_RT5651_DMIC1_MAP, ++ BYT_RT5651_DMIC2_MAP, ++ BYT_RT5651_IN1_MAP, ++}; ++ ++#define BYT_RT5651_MAP(quirk) ((quirk) & 0xff) ++#define BYT_RT5651_DMIC_EN BIT(16) ++#define BYT_RT5651_MCLK_EN BIT(17) ++#define BYT_RT5651_MCLK_25MHZ BIT(18) ++ ++struct byt_rt5651_private { ++ struct clk *mclk; ++}; ++ ++static unsigned long byt_rt5651_quirk = BYT_RT5651_DMIC1_MAP | ++ BYT_RT5651_DMIC_EN | ++ BYT_RT5651_MCLK_EN; ++ ++static void log_quirks(struct device *dev) ++{ ++ if (BYT_RT5651_MAP(byt_rt5651_quirk) == BYT_RT5651_DMIC1_MAP) ++ dev_info(dev, "quirk DMIC1_MAP enabled"); ++ if (BYT_RT5651_MAP(byt_rt5651_quirk) == BYT_RT5651_DMIC2_MAP) ++ dev_info(dev, "quirk DMIC2_MAP enabled"); ++ if (BYT_RT5651_MAP(byt_rt5651_quirk) == BYT_RT5651_IN1_MAP) ++ dev_info(dev, "quirk IN1_MAP enabled"); ++ if (byt_rt5651_quirk & BYT_RT5651_DMIC_EN) ++ dev_info(dev, "quirk DMIC enabled"); ++ if (byt_rt5651_quirk & BYT_RT5651_MCLK_EN) ++ dev_info(dev, "quirk MCLK_EN enabled"); ++ if (byt_rt5651_quirk & BYT_RT5651_MCLK_25MHZ) ++ dev_info(dev, "quirk MCLK_25MHZ enabled"); ++} ++ ++#define BYT_CODEC_DAI1 "rt5651-aif1" ++ ++static inline struct snd_soc_dai *byt_get_codec_dai(struct snd_soc_card *card) ++{ ++ struct snd_soc_pcm_runtime *rtd; ++ ++ list_for_each_entry(rtd, &card->rtd_list, list) { ++ if (!strncmp(rtd->codec_dai->name, BYT_CODEC_DAI1, ++ strlen(BYT_CODEC_DAI1))) ++ return rtd->codec_dai; ++ } ++ return NULL; ++} ++ ++static int platform_clock_control(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *k, int event) ++{ ++ struct snd_soc_dapm_context *dapm = w->dapm; ++ struct snd_soc_card *card = dapm->card; ++ struct snd_soc_dai *codec_dai; ++ struct byt_rt5651_private *priv = snd_soc_card_get_drvdata(card); ++ int ret; ++ ++ codec_dai = byt_get_codec_dai(card); ++ if (!codec_dai) { ++ dev_err(card->dev, ++ "Codec dai not found; Unable to set platform clock\n"); ++ return -EIO; ++ } ++ ++ if (SND_SOC_DAPM_EVENT_ON(event)) { ++ if ((byt_rt5651_quirk & BYT_RT5651_MCLK_EN) && priv->mclk) { ++ ret = clk_prepare_enable(priv->mclk); ++ if (ret < 0) { ++ dev_err(card->dev, ++ "could not configure MCLK state"); ++ return ret; ++ } ++ } ++ ret = snd_soc_dai_set_sysclk(codec_dai, RT5651_SCLK_S_PLL1, ++ 48000 * 512, ++ SND_SOC_CLOCK_IN); ++ } else { ++ /* ++ * Set codec clock source to internal clock before ++ * turning off the platform clock. Codec needs clock ++ * for Jack detection and button press ++ */ ++ ret = snd_soc_dai_set_sysclk(codec_dai, RT5651_SCLK_S_RCCLK, ++ 0, ++ SND_SOC_CLOCK_IN); ++ if (!ret) { ++ if ((byt_rt5651_quirk & BYT_RT5651_MCLK_EN) && priv->mclk) ++ clk_disable_unprepare(priv->mclk); ++ } ++ } ++ ++ if (ret < 0) { ++ dev_err(card->dev, "can't set codec sysclk: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} + + static const struct snd_soc_dapm_widget byt_rt5651_widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Internal Mic", NULL), + SND_SOC_DAPM_SPK("Speaker", NULL), ++ SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, ++ platform_clock_control, SND_SOC_DAPM_PRE_PMU | ++ SND_SOC_DAPM_POST_PMD), ++ + }; + + static const struct snd_soc_dapm_route byt_rt5651_audio_map[] = { ++ {"Headphone", NULL, "Platform Clock"}, ++ {"Headset Mic", NULL, "Platform Clock"}, ++ {"Internal Mic", NULL, "Platform Clock"}, ++ {"Speaker", NULL, "Platform Clock"}, ++ + {"AIF1 Playback", NULL, "ssp2 Tx"}, + {"ssp2 Tx", NULL, "codec_out0"}, + {"ssp2 Tx", NULL, "codec_out1"}, +@@ -67,18 +180,6 @@ static const struct snd_soc_dapm_route byt_rt5651_intmic_in1_map[] = { + {"IN1P", NULL, "Internal Mic"}, + }; + +-enum { +- BYT_RT5651_DMIC1_MAP, +- BYT_RT5651_DMIC2_MAP, +- BYT_RT5651_IN1_MAP, +-}; +- +-#define BYT_RT5651_MAP(quirk) ((quirk) & 0xff) +-#define BYT_RT5651_DMIC_EN BIT(16) +- +-static unsigned long byt_rt5651_quirk = BYT_RT5651_DMIC1_MAP | +- BYT_RT5651_DMIC_EN; +- + static const struct snd_kcontrol_new byt_rt5651_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), +@@ -103,9 +204,27 @@ static int byt_rt5651_aif1_hw_params(struct snd_pcm_substream *substream, + return ret; + } + +- ret = snd_soc_dai_set_pll(codec_dai, 0, RT5651_PLL1_S_BCLK1, +- params_rate(params) * 50, +- params_rate(params) * 512); ++ if (!(byt_rt5651_quirk & BYT_RT5651_MCLK_EN)) { ++ /* 2x25 bit slots on SSP2 */ ++ ret = snd_soc_dai_set_pll(codec_dai, 0, ++ RT5651_PLL1_S_BCLK1, ++ params_rate(params) * 50, ++ params_rate(params) * 512); ++ } ++ else { ++ if (byt_rt5651_quirk & BYT_RT5651_MCLK_25MHZ) { ++ ret = snd_soc_dai_set_pll(codec_dai, 0, ++ RT5651_PLL1_S_MCLK, ++ 25000000, ++ params_rate(params) * 512); ++ } else { ++ ret = snd_soc_dai_set_pll(codec_dai, 0, ++ RT5651_PLL1_S_MCLK, ++ 19200000, ++ params_rate(params) * 512); ++ } ++ } ++ + if (ret < 0) { + dev_err(rtd->dev, "can't set codec pll: %d\n", ret); + return ret; +@@ -114,8 +233,22 @@ static int byt_rt5651_aif1_hw_params(struct snd_pcm_substream *substream, + return 0; + } + ++static int byt_rt5651_quirk_cb(const struct dmi_system_id *id) ++{ ++ byt_rt5651_quirk = (unsigned long)id->driver_data; ++ return 1; ++} ++ + static const struct dmi_system_id byt_rt5651_quirk_table[] = { +- {} ++ { ++ .callback = byt_rt5651_quirk_cb, ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Circuitco"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Minnowboard Max B3 PLATFORM"), ++ }, ++ .driver_data = (unsigned long *)(BYT_RT5651_DMIC1_MAP | ++ BYT_RT5651_DMIC_EN), ++ }, + }; + + static int byt_rt5651_init(struct snd_soc_pcm_runtime *runtime) +@@ -123,6 +256,7 @@ static int byt_rt5651_init(struct snd_soc_pcm_runtime *runtime) + int ret; + struct snd_soc_card *card = runtime->card; + const struct snd_soc_dapm_route *custom_map; ++ struct byt_rt5651_private *priv = snd_soc_card_get_drvdata(card); + int num_routes; + + card->dapm.idle_bias_off = true; +@@ -141,6 +275,9 @@ static int byt_rt5651_init(struct snd_soc_pcm_runtime *runtime) + custom_map = byt_rt5651_intmic_dmic1_map; + num_routes = ARRAY_SIZE(byt_rt5651_intmic_dmic1_map); + } ++ ret = snd_soc_dapm_add_routes(&card->dapm, custom_map, num_routes); ++ if (ret) ++ return ret; + + ret = snd_soc_add_card_controls(card, byt_rt5651_controls, + ARRAY_SIZE(byt_rt5651_controls)); +@@ -151,6 +288,30 @@ static int byt_rt5651_init(struct snd_soc_pcm_runtime *runtime) + snd_soc_dapm_ignore_suspend(&card->dapm, "Headphone"); + snd_soc_dapm_ignore_suspend(&card->dapm, "Speaker"); + ++ if ((byt_rt5651_quirk & BYT_RT5651_MCLK_EN) && priv->mclk) { ++ /* ++ * The firmware might enable the clock at ++ * boot (this information may or may not ++ * be reflected in the enable clock register). ++ * To change the rate we must disable the clock ++ * first to cover these cases. Due to common ++ * clock framework restrictions that do not allow ++ * to disable a clock that has not been enabled, ++ * we need to enable the clock first. ++ */ ++ ret = clk_prepare_enable(priv->mclk); ++ if (!ret) ++ clk_disable_unprepare(priv->mclk); ++ ++ if (byt_rt5651_quirk & BYT_RT5651_MCLK_25MHZ) ++ ret = clk_set_rate(priv->mclk, 25000000); ++ else ++ ret = clk_set_rate(priv->mclk, 19200000); ++ ++ if (ret) ++ dev_err(card->dev, "unable to set MCLK rate\n"); ++ } ++ + return ret; + } + +@@ -298,12 +459,73 @@ static struct snd_soc_card byt_rt5651_card = { + .fully_routed = true, + }; + ++static char byt_rt5651_codec_name[16]; /* i2c-:00 with HID being 8 chars */ ++ ++static bool is_valleyview(void) ++{ ++ static const struct x86_cpu_id cpu_ids[] = { ++ { X86_VENDOR_INTEL, 6, 55 }, /* Valleyview, Bay Trail */ ++ {} ++ }; ++ ++ if (!x86_match_cpu(cpu_ids)) ++ return false; ++ return true; ++} ++ + static int snd_byt_rt5651_mc_probe(struct platform_device *pdev) + { + int ret_val = 0; ++ struct sst_acpi_mach *mach; ++ const char *i2c_name = NULL; ++ int i; ++ int dai_index; ++ struct byt_rt5651_private *priv; ++ ++ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_ATOMIC); ++ if (!priv) ++ return -ENOMEM; + + /* register the soc card */ + byt_rt5651_card.dev = &pdev->dev; ++ mach = byt_rt5651_card.dev->platform_data; ++ ++ /* fix index of codec dai */ ++ dai_index = MERR_DPCM_COMPR + 1; ++ for (i = 0; i < ARRAY_SIZE(byt_rt5651_dais); i++) { ++ if (!strcmp(byt_rt5651_dais[i].codec_name, "i2c-10EC5651:00")) { ++ dai_index = i; ++ break; ++ } ++ } ++ ++ /* fixup codec name based on HID */ ++ i2c_name = sst_acpi_find_name_from_hid(mach->id); ++ if (i2c_name != NULL) { ++ snprintf(byt_rt5651_codec_name, sizeof(byt_rt5651_codec_name), ++ "%s%s", "i2c-", i2c_name); ++ ++ byt_rt5651_dais[dai_index].codec_name = byt_rt5651_codec_name; ++ } ++ ++ /* check quirks before creating card */ ++ dmi_check_system(byt_rt5651_quirk_table); ++ log_quirks(&pdev->dev); ++ ++ if ((byt_rt5651_quirk & BYT_RT5651_MCLK_EN) && (is_valleyview())) { ++ priv->mclk = devm_clk_get(&pdev->dev, "pmc_plt_clk_3"); ++ if (IS_ERR(priv->mclk)) { ++ dev_err(&pdev->dev, ++ "Failed to get MCLK from pmc_plt_clk_3: %ld\n", ++ PTR_ERR(priv->mclk)); ++ /* ++ * Audio output can work with bitclock only ++ * on Baytrail, with a limited audio quality ++ * degradation ++ */ ++ byt_rt5651_quirk &= ~BYT_RT5651_MCLK_EN; ++ } ++ } + + ret_val = devm_snd_soc_register_card(&pdev->dev, &byt_rt5651_card); + +diff --git a/sound/soc/intel/boards/cht_bsw_max98090_ti.c b/sound/soc/intel/boards/cht_bsw_max98090_ti.c +index cdcced9f32b6..a95ae141e3a6 100644 +--- a/sound/soc/intel/boards/cht_bsw_max98090_ti.c ++++ b/sound/soc/intel/boards/cht_bsw_max98090_ti.c +@@ -23,6 +23,9 @@ + #include + #include + #include ++#include ++#include ++#include + #include + #include + #include +@@ -37,6 +40,7 @@ + struct cht_mc_private { + struct snd_soc_jack jack; + bool ts3a227e_present; ++ struct clk *mclk; + }; + + static inline struct snd_soc_dai *cht_get_codec_dai(struct snd_soc_card *card) +@@ -51,11 +55,51 @@ static inline struct snd_soc_dai *cht_get_codec_dai(struct snd_soc_card *card) + return NULL; + } + ++static int platform_clock_control(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *k, int event) ++{ ++ struct snd_soc_dapm_context *dapm = w->dapm; ++ struct snd_soc_card *card = dapm->card; ++ struct snd_soc_dai *codec_dai; ++ struct cht_mc_private *ctx = snd_soc_card_get_drvdata(card); ++ int ret; ++ ++ codec_dai = cht_get_codec_dai(card); ++ if (!codec_dai) { ++ dev_err(card->dev, "Codec dai not found; Unable to set platform clock\n"); ++ return -EIO; ++ } ++ ++ if (SND_SOC_DAPM_EVENT_ON(event)) { ++ if (ctx->mclk) { ++ ret = clk_prepare_enable(ctx->mclk); ++ if (ret < 0) { ++ dev_err(card->dev, ++ "could not configure MCLK state"); ++ return ret; ++ } ++ } ++ } else { ++ ++ /* FIXME: if there is no clock can jack detection work ? */ ++ ++ if (ctx->mclk) { ++ clk_disable_unprepare(ctx->mclk); ++ } ++ } ++ ++ return 0; ++} ++ ++ + static const struct snd_soc_dapm_widget cht_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Int Mic", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), ++ SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, ++ platform_clock_control, SND_SOC_DAPM_PRE_PMU | ++ SND_SOC_DAPM_POST_PMD), + }; + + static const struct snd_soc_dapm_route cht_audio_map[] = { +@@ -72,6 +116,10 @@ static const struct snd_soc_dapm_route cht_audio_map[] = { + {"codec_in0", NULL, "ssp2 Rx" }, + {"codec_in1", NULL, "ssp2 Rx" }, + {"ssp2 Rx", NULL, "HiFi Capture"}, ++ {"Headphone", NULL, "Platform Clock"}, ++ {"Headset Mic", NULL, "Platform Clock"}, ++ {"Int Mic", NULL, "Platform Clock"}, ++ {"Ext Spk", NULL, "Platform Clock"}, + }; + + static const struct snd_kcontrol_new cht_mc_controls[] = { +@@ -153,6 +201,26 @@ static int cht_codec_init(struct snd_soc_pcm_runtime *runtime) + if (ctx->ts3a227e_present) + snd_soc_jack_notifier_register(jack, &cht_jack_nb); + ++ if (ctx->mclk) { ++ /* ++ * The firmware might enable the clock at ++ * boot (this information may or may not ++ * be reflected in the enable clock register). ++ * To change the rate we must disable the clock ++ * first to cover these cases. Due to common ++ * clock framework restrictions that do not allow ++ * to disable a clock that has not been enabled, ++ * we need to enable the clock first. ++ */ ++ ret = clk_prepare_enable(ctx->mclk); ++ if (!ret) ++ clk_disable_unprepare(ctx->mclk); ++ ++ ret = clk_set_rate(ctx->mclk, CHT_PLAT_CLK_3_HZ); ++ ++ if (ret) ++ dev_err(runtime->dev, "unable to set MCLK rate\n"); ++ } + return ret; + } + +@@ -287,6 +355,18 @@ static struct snd_soc_card snd_soc_card_cht = { + .num_controls = ARRAY_SIZE(cht_mc_controls), + }; + ++static bool is_valleyview(void) ++{ ++ static const struct x86_cpu_id cpu_ids[] = { ++ { X86_VENDOR_INTEL, 6, 55 }, /* Valleyview, Bay Trail */ ++ {} ++ }; ++ ++ if (!x86_match_cpu(cpu_ids)) ++ return false; ++ return true; ++} ++ + static int snd_cht_mc_probe(struct platform_device *pdev) + { + int ret_val = 0; +@@ -306,6 +386,17 @@ static int snd_cht_mc_probe(struct platform_device *pdev) + /* register the soc card */ + snd_soc_card_cht.dev = &pdev->dev; + snd_soc_card_set_drvdata(&snd_soc_card_cht, drv); ++ ++ if (is_valleyview()) { ++ drv->mclk = devm_clk_get(&pdev->dev, "pmc_plt_clk_3"); ++ if (IS_ERR(drv->mclk)) { ++ dev_err(&pdev->dev, ++ "Failed to get MCLK from pmc_plt_clk_3: %ld\n", ++ PTR_ERR(drv->mclk)); ++ return PTR_ERR(drv->mclk); ++ } ++ } ++ + ret_val = devm_snd_soc_register_card(&pdev->dev, &snd_soc_card_cht); + if (ret_val) { + dev_err(&pdev->dev, +diff --git a/sound/soc/intel/boards/cht_bsw_rt5645.c b/sound/soc/intel/boards/cht_bsw_rt5645.c +index 56056ed7fcfd..e792f9849f98 100644 +--- a/sound/soc/intel/boards/cht_bsw_rt5645.c ++++ b/sound/soc/intel/boards/cht_bsw_rt5645.c +@@ -23,7 +23,11 @@ + #include + #include + #include ++#include + #include ++#include ++#include ++#include + #include + #include + #include +@@ -33,7 +37,8 @@ + #include "../common/sst-acpi.h" + + #define CHT_PLAT_CLK_3_HZ 19200000 +-#define CHT_CODEC_DAI "rt5645-aif1" ++#define CHT_CODEC_DAI1 "rt5645-aif1" ++#define CHT_CODEC_DAI2 "rt5645-aif2" + + struct cht_acpi_card { + char *codec_id; +@@ -44,15 +49,37 @@ struct cht_acpi_card { + struct cht_mc_private { + struct snd_soc_jack jack; + struct cht_acpi_card *acpi_card; ++ char codec_name[16]; ++ struct clk *mclk; + }; + ++#define CHT_RT5645_MAP(quirk) ((quirk) & 0xff) ++#define CHT_RT5645_SSP2_AIF2 BIT(16) /* default is using AIF1 */ ++#define CHT_RT5645_SSP0_AIF1 BIT(17) ++#define CHT_RT5645_SSP0_AIF2 BIT(18) ++ ++static unsigned long cht_rt5645_quirk = 0; ++ ++static void log_quirks(struct device *dev) ++{ ++ if (cht_rt5645_quirk & CHT_RT5645_SSP2_AIF2) ++ dev_info(dev, "quirk SSP2_AIF2 enabled"); ++ if (cht_rt5645_quirk & CHT_RT5645_SSP0_AIF1) ++ dev_info(dev, "quirk SSP0_AIF1 enabled"); ++ if (cht_rt5645_quirk & CHT_RT5645_SSP0_AIF2) ++ dev_info(dev, "quirk SSP0_AIF2 enabled"); ++} ++ + static inline struct snd_soc_dai *cht_get_codec_dai(struct snd_soc_card *card) + { + struct snd_soc_pcm_runtime *rtd; + + list_for_each_entry(rtd, &card->rtd_list, list) { +- if (!strncmp(rtd->codec_dai->name, CHT_CODEC_DAI, +- strlen(CHT_CODEC_DAI))) ++ if (!strncmp(rtd->codec_dai->name, CHT_CODEC_DAI1, ++ strlen(CHT_CODEC_DAI1))) ++ return rtd->codec_dai; ++ if (!strncmp(rtd->codec_dai->name, CHT_CODEC_DAI2, ++ strlen(CHT_CODEC_DAI2))) + return rtd->codec_dai; + } + return NULL; +@@ -64,6 +91,7 @@ static int platform_clock_control(struct snd_soc_dapm_widget *w, + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_card *card = dapm->card; + struct snd_soc_dai *codec_dai; ++ struct cht_mc_private *ctx = snd_soc_card_get_drvdata(card); + int ret; + + codec_dai = cht_get_codec_dai(card); +@@ -72,19 +100,30 @@ static int platform_clock_control(struct snd_soc_dapm_widget *w, + return -EIO; + } + +- if (!SND_SOC_DAPM_EVENT_OFF(event)) +- return 0; ++ if (SND_SOC_DAPM_EVENT_ON(event)) { ++ if (ctx->mclk) { ++ ret = clk_prepare_enable(ctx->mclk); ++ if (ret < 0) { ++ dev_err(card->dev, ++ "could not configure MCLK state"); ++ return ret; ++ } ++ } ++ } else { ++ /* Set codec sysclk source to its internal clock because codec PLL will ++ * be off when idle and MCLK will also be off when codec is ++ * runtime suspended. Codec needs clock for jack detection and button ++ * press. MCLK is turned off with clock framework or ACPI. ++ */ ++ ret = snd_soc_dai_set_sysclk(codec_dai, RT5645_SCLK_S_RCCLK, ++ 0, SND_SOC_CLOCK_IN); ++ if (ret < 0) { ++ dev_err(card->dev, "can't set codec sysclk: %d\n", ret); ++ return ret; ++ } + +- /* Set codec sysclk source to its internal clock because codec PLL will +- * be off when idle and MCLK will also be off by ACPI when codec is +- * runtime suspended. Codec needs clock for jack detection and button +- * press. +- */ +- ret = snd_soc_dai_set_sysclk(codec_dai, RT5645_SCLK_S_RCCLK, +- 0, SND_SOC_CLOCK_IN); +- if (ret < 0) { +- dev_err(card->dev, "can't set codec sysclk: %d\n", ret); +- return ret; ++ if (ctx->mclk) ++ clk_disable_unprepare(ctx->mclk); + } + + return 0; +@@ -96,7 +135,7 @@ static const struct snd_soc_dapm_widget cht_dapm_widgets[] = { + SND_SOC_DAPM_MIC("Int Mic", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), + SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, +- platform_clock_control, SND_SOC_DAPM_POST_PMD), ++ platform_clock_control, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + }; + + static const struct snd_soc_dapm_route cht_rt5645_audio_map[] = { +@@ -108,12 +147,6 @@ static const struct snd_soc_dapm_route cht_rt5645_audio_map[] = { + {"Headphone", NULL, "HPOR"}, + {"Ext Spk", NULL, "SPOL"}, + {"Ext Spk", NULL, "SPOR"}, +- {"AIF1 Playback", NULL, "ssp2 Tx"}, +- {"ssp2 Tx", NULL, "codec_out0"}, +- {"ssp2 Tx", NULL, "codec_out1"}, +- {"codec_in0", NULL, "ssp2 Rx" }, +- {"codec_in1", NULL, "ssp2 Rx" }, +- {"ssp2 Rx", NULL, "AIF1 Capture"}, + {"Headphone", NULL, "Platform Clock"}, + {"Headset Mic", NULL, "Platform Clock"}, + {"Int Mic", NULL, "Platform Clock"}, +@@ -129,16 +162,42 @@ static const struct snd_soc_dapm_route cht_rt5650_audio_map[] = { + {"Headphone", NULL, "HPOR"}, + {"Ext Spk", NULL, "SPOL"}, + {"Ext Spk", NULL, "SPOR"}, ++ {"Headphone", NULL, "Platform Clock"}, ++ {"Headset Mic", NULL, "Platform Clock"}, ++ {"Int Mic", NULL, "Platform Clock"}, ++ {"Ext Spk", NULL, "Platform Clock"}, ++}; ++ ++static const struct snd_soc_dapm_route cht_rt5645_ssp2_aif1_map[] = { + {"AIF1 Playback", NULL, "ssp2 Tx"}, + {"ssp2 Tx", NULL, "codec_out0"}, + {"ssp2 Tx", NULL, "codec_out1"}, + {"codec_in0", NULL, "ssp2 Rx" }, + {"codec_in1", NULL, "ssp2 Rx" }, + {"ssp2 Rx", NULL, "AIF1 Capture"}, +- {"Headphone", NULL, "Platform Clock"}, +- {"Headset Mic", NULL, "Platform Clock"}, +- {"Int Mic", NULL, "Platform Clock"}, +- {"Ext Spk", NULL, "Platform Clock"}, ++}; ++ ++static const struct snd_soc_dapm_route cht_rt5645_ssp2_aif2_map[] = { ++ {"AIF2 Playback", NULL, "ssp2 Tx"}, ++ {"ssp2 Tx", NULL, "codec_out0"}, ++ {"ssp2 Tx", NULL, "codec_out1"}, ++ {"codec_in0", NULL, "ssp2 Rx" }, ++ {"codec_in1", NULL, "ssp2 Rx" }, ++ {"ssp2 Rx", NULL, "AIF2 Capture"}, ++}; ++ ++static const struct snd_soc_dapm_route cht_rt5645_ssp0_aif1_map[] = { ++ {"AIF1 Playback", NULL, "ssp0 Tx"}, ++ {"ssp0 Tx", NULL, "modem_out"}, ++ {"modem_in", NULL, "ssp0 Rx" }, ++ {"ssp0 Rx", NULL, "AIF1 Capture"}, ++}; ++ ++static const struct snd_soc_dapm_route cht_rt5645_ssp0_aif2_map[] = { ++ {"AIF2 Playback", NULL, "ssp0 Tx"}, ++ {"ssp0 Tx", NULL, "modem_out"}, ++ {"modem_in", NULL, "ssp0 Rx" }, ++ {"ssp0 Rx", NULL, "AIF2 Capture"}, + }; + + static const struct snd_kcontrol_new cht_mc_controls[] = { +@@ -184,11 +243,25 @@ static int cht_aif1_hw_params(struct snd_pcm_substream *substream, + return 0; + } + ++/* uncomment when we have a real quirk ++static int cht_rt5645_quirk_cb(const struct dmi_system_id *id) ++{ ++ cht_rt5645_quirk = (unsigned long)id->driver_data; ++ return 1; ++} ++*/ ++ ++static const struct dmi_system_id cht_rt5645_quirk_table[] = { ++ { ++ }, ++}; ++ + static int cht_codec_init(struct snd_soc_pcm_runtime *runtime) + { + int ret; + int jack_type; + struct snd_soc_codec *codec = runtime->codec; ++ struct snd_soc_card *card = runtime->card; + struct snd_soc_dai *codec_dai = runtime->codec_dai; + struct cht_mc_private *ctx = snd_soc_card_get_drvdata(runtime->card); + +@@ -200,6 +273,26 @@ static int cht_codec_init(struct snd_soc_pcm_runtime *runtime) + RT5645_AD_STEREO_FILTER, + RT5645_CLK_SEL_I2S1_ASRC); + ++ if (cht_rt5645_quirk & CHT_RT5645_SSP2_AIF2) { ++ ret = snd_soc_dapm_add_routes(&card->dapm, ++ cht_rt5645_ssp2_aif2_map, ++ ARRAY_SIZE(cht_rt5645_ssp2_aif2_map)); ++ } else if (cht_rt5645_quirk & CHT_RT5645_SSP0_AIF1) { ++ ret = snd_soc_dapm_add_routes(&card->dapm, ++ cht_rt5645_ssp0_aif1_map, ++ ARRAY_SIZE(cht_rt5645_ssp0_aif1_map)); ++ } else if (cht_rt5645_quirk & CHT_RT5645_SSP0_AIF2) { ++ ret = snd_soc_dapm_add_routes(&card->dapm, ++ cht_rt5645_ssp0_aif2_map, ++ ARRAY_SIZE(cht_rt5645_ssp0_aif2_map)); ++ } else { ++ ret = snd_soc_dapm_add_routes(&card->dapm, ++ cht_rt5645_ssp2_aif1_map, ++ ARRAY_SIZE(cht_rt5645_ssp2_aif1_map)); ++ } ++ if (ret) ++ return ret; ++ + /* TDM 4 slots 24 bit, set Rx & Tx bitmask to 4 active slots */ + ret = snd_soc_dai_set_tdm_slot(codec_dai, 0xF, 0xF, 4, 24); + if (ret < 0) { +@@ -224,12 +317,33 @@ static int cht_codec_init(struct snd_soc_pcm_runtime *runtime) + + rt5645_set_jack_detect(codec, &ctx->jack, &ctx->jack, &ctx->jack); + ++ if (ctx->mclk) { ++ /* ++ * The firmware might enable the clock at ++ * boot (this information may or may not ++ * be reflected in the enable clock register). ++ * To change the rate we must disable the clock ++ * first to cover these cases. Due to common ++ * clock framework restrictions that do not allow ++ * to disable a clock that has not been enabled, ++ * we need to enable the clock first. ++ */ ++ ret = clk_prepare_enable(ctx->mclk); ++ if (!ret) ++ clk_disable_unprepare(ctx->mclk); ++ ++ ret = clk_set_rate(ctx->mclk, CHT_PLAT_CLK_3_HZ); ++ ++ if (ret) ++ dev_err(runtime->dev, "unable to set MCLK rate\n"); ++ } + return ret; + } + + static int cht_codec_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) + { ++ int ret; + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, +@@ -239,8 +353,39 @@ static int cht_codec_fixup(struct snd_soc_pcm_runtime *rtd, + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + +- /* set SSP2 to 24-bit */ +- params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); ++ if ((cht_rt5645_quirk & CHT_RT5645_SSP0_AIF1) || ++ (cht_rt5645_quirk & CHT_RT5645_SSP0_AIF2)) { ++ ++ /* set SSP0 to 16-bit */ ++ params_set_format(params, SNDRV_PCM_FORMAT_S16_LE); ++ ++ /* ++ * Default mode for SSP configuration is TDM 4 slot, override config ++ * with explicit setting to I2S 2ch 16-bit. The word length is set with ++ * dai_set_tdm_slot() since there is no other API exposed ++ */ ++ ret = snd_soc_dai_set_fmt(rtd->cpu_dai, ++ SND_SOC_DAIFMT_I2S | ++ SND_SOC_DAIFMT_NB_IF | ++ SND_SOC_DAIFMT_CBS_CFS ++ ); ++ if (ret < 0) { ++ dev_err(rtd->dev, "can't set format to I2S, err %d\n", ret); ++ return ret; ++ } ++ ++ ret = snd_soc_dai_set_tdm_slot(rtd->cpu_dai, 0x3, 0x3, 2, 16); ++ if (ret < 0) { ++ dev_err(rtd->dev, "can't set I2S config, err %d\n", ret); ++ return ret; ++ } ++ ++ } else { ++ ++ /* set SSP2 to 24-bit */ ++ params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); ++ ++ } + return 0; + } + +@@ -343,10 +488,31 @@ static struct snd_soc_card snd_soc_card_chtrt5650 = { + static struct cht_acpi_card snd_soc_cards[] = { + {"10EC5640", CODEC_TYPE_RT5645, &snd_soc_card_chtrt5645}, + {"10EC5645", CODEC_TYPE_RT5645, &snd_soc_card_chtrt5645}, ++ {"10EC5648", CODEC_TYPE_RT5645, &snd_soc_card_chtrt5645}, ++ {"10EC3270", CODEC_TYPE_RT5645, &snd_soc_card_chtrt5645}, + {"10EC5650", CODEC_TYPE_RT5650, &snd_soc_card_chtrt5650}, + }; + +-static char cht_rt5640_codec_name[16]; /* i2c-:00 with HID being 8 chars */ ++static char cht_rt5645_codec_name[16]; /* i2c-:00 with HID being 8 chars */ ++static char cht_rt5645_codec_aif_name[12]; /* = "rt5645-aif[1|2]" */ ++static char cht_rt5645_cpu_dai_name[10]; /* = "ssp[0|2]-port" */ ++ ++static bool is_valleyview(void) ++{ ++ static const struct x86_cpu_id cpu_ids[] = { ++ { X86_VENDOR_INTEL, 6, 55 }, /* Valleyview, Bay Trail */ ++ {} ++ }; ++ ++ if (!x86_match_cpu(cpu_ids)) ++ return false; ++ return true; ++} ++ ++struct acpi_chan_package { /* ACPICA seems to require 64 bit integers */ ++ u64 aif_value; /* 1: AIF1, 2: AIF2 */ ++ u64 mclock_value; /* usually 25MHz (0x17d7940), ignored */ ++}; + + static int snd_cht_mc_probe(struct platform_device *pdev) + { +@@ -354,41 +520,147 @@ static int snd_cht_mc_probe(struct platform_device *pdev) + int i; + struct cht_mc_private *drv; + struct snd_soc_card *card = snd_soc_cards[0].soc_card; +- char codec_name[16]; + struct sst_acpi_mach *mach; + const char *i2c_name = NULL; + int dai_index = 0; ++ bool found = false; ++ bool is_bytcr = false; + + drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_ATOMIC); + if (!drv) + return -ENOMEM; + ++ mach = (&pdev->dev)->platform_data; ++ + for (i = 0; i < ARRAY_SIZE(snd_soc_cards); i++) { +- if (acpi_dev_found(snd_soc_cards[i].codec_id)) { ++ if (acpi_dev_found(snd_soc_cards[i].codec_id) && ++ (!strncmp(snd_soc_cards[i].codec_id, mach->id, 8))) { + dev_dbg(&pdev->dev, + "found codec %s\n", snd_soc_cards[i].codec_id); + card = snd_soc_cards[i].soc_card; + drv->acpi_card = &snd_soc_cards[i]; ++ found = true; + break; + } + } ++ ++ if (!found) { ++ dev_err(&pdev->dev, "No matching HID found in supported list\n"); ++ return -ENODEV; ++ } ++ + card->dev = &pdev->dev; +- mach = card->dev->platform_data; +- sprintf(codec_name, "i2c-%s:00", drv->acpi_card->codec_id); ++ sprintf(drv->codec_name, "i2c-%s:00", drv->acpi_card->codec_id); + + /* set correct codec name */ + for (i = 0; i < ARRAY_SIZE(cht_dailink); i++) + if (!strcmp(card->dai_link[i].codec_name, "i2c-10EC5645:00")) { +- card->dai_link[i].codec_name = kstrdup(codec_name, GFP_KERNEL); ++ card->dai_link[i].codec_name = drv->codec_name; + dai_index = i; + } + + /* fixup codec name based on HID */ + i2c_name = sst_acpi_find_name_from_hid(mach->id); + if (i2c_name != NULL) { +- snprintf(cht_rt5640_codec_name, sizeof(cht_rt5640_codec_name), ++ snprintf(cht_rt5645_codec_name, sizeof(cht_rt5645_codec_name), + "%s%s", "i2c-", i2c_name); +- cht_dailink[dai_index].codec_name = cht_rt5640_codec_name; ++ cht_dailink[dai_index].codec_name = cht_rt5645_codec_name; ++ } ++ ++ /* ++ * swap SSP0 if bytcr is detected ++ * (will be overridden if DMI quirk is detected) ++ */ ++ if (is_valleyview()) { ++ struct sst_platform_info *p_info = mach->pdata; ++ const struct sst_res_info *res_info = p_info->res_info; ++ ++ if (res_info->acpi_ipc_irq_index == 0) ++ is_bytcr = true; ++ } ++ ++ if (is_bytcr) { ++ /* ++ * Baytrail CR platforms may have CHAN package in BIOS, try ++ * to find relevant routing quirk based as done on Windows ++ * platforms. We have to read the information directly from the ++ * BIOS, at this stage the card is not created and the links ++ * with the codec driver/pdata are non-existent ++ */ ++ ++ struct acpi_chan_package chan_package; ++ ++ /* format specified: 2 64-bit integers */ ++ struct acpi_buffer format = {sizeof("NN"), "NN"}; ++ struct acpi_buffer state = {0, NULL}; ++ struct sst_acpi_package_context pkg_ctx; ++ bool pkg_found = false; ++ ++ state.length = sizeof(chan_package); ++ state.pointer = &chan_package; ++ ++ pkg_ctx.name = "CHAN"; ++ pkg_ctx.length = 2; ++ pkg_ctx.format = &format; ++ pkg_ctx.state = &state; ++ pkg_ctx.data_valid = false; ++ ++ pkg_found = sst_acpi_find_package_from_hid(mach->id, &pkg_ctx); ++ if (pkg_found) { ++ if (chan_package.aif_value == 1) { ++ dev_info(&pdev->dev, "BIOS Routing: AIF1 connected\n"); ++ cht_rt5645_quirk |= CHT_RT5645_SSP0_AIF1; ++ } else if (chan_package.aif_value == 2) { ++ dev_info(&pdev->dev, "BIOS Routing: AIF2 connected\n"); ++ cht_rt5645_quirk |= CHT_RT5645_SSP0_AIF2; ++ } else { ++ dev_info(&pdev->dev, "BIOS Routing isn't valid, ignored\n"); ++ pkg_found = false; ++ } ++ } ++ ++ if (!pkg_found) { ++ /* no BIOS indications, assume SSP0-AIF2 connection */ ++ cht_rt5645_quirk |= CHT_RT5645_SSP0_AIF2; ++ } ++ } ++ ++ /* check quirks before creating card */ ++ dmi_check_system(cht_rt5645_quirk_table); ++ log_quirks(&pdev->dev); ++ ++ if ((cht_rt5645_quirk & CHT_RT5645_SSP2_AIF2) || ++ (cht_rt5645_quirk & CHT_RT5645_SSP0_AIF2)) { ++ ++ /* fixup codec aif name */ ++ snprintf(cht_rt5645_codec_aif_name, ++ sizeof(cht_rt5645_codec_aif_name), ++ "%s", "rt5645-aif2"); ++ ++ cht_dailink[dai_index].codec_dai_name = ++ cht_rt5645_codec_aif_name; ++ } ++ ++ if ((cht_rt5645_quirk & CHT_RT5645_SSP0_AIF1) || ++ (cht_rt5645_quirk & CHT_RT5645_SSP0_AIF2)) { ++ ++ /* fixup cpu dai name name */ ++ snprintf(cht_rt5645_cpu_dai_name, ++ sizeof(cht_rt5645_cpu_dai_name), ++ "%s", "ssp0-port"); ++ ++ cht_dailink[dai_index].cpu_dai_name = ++ cht_rt5645_cpu_dai_name; ++ } ++ ++ if (is_valleyview()) { ++ drv->mclk = devm_clk_get(&pdev->dev, "pmc_plt_clk_3"); ++ if (IS_ERR(drv->mclk)) { ++ dev_err(&pdev->dev, ++ "Failed to get MCLK from pmc_plt_clk_3: %ld\n", ++ PTR_ERR(drv->mclk)); ++ return PTR_ERR(drv->mclk); ++ } + } + + snd_soc_card_set_drvdata(card, drv); +diff --git a/sound/soc/intel/boards/cht_cx2072x.c b/sound/soc/intel/boards/cht_cx2072x.c +new file mode 100644 +index 000000000000..30a97b201c9a +--- /dev/null ++++ b/sound/soc/intel/boards/cht_cx2072x.c +@@ -0,0 +1,453 @@ ++/* ++ * cht_cx207x.c - ASoc DPCM Machine driver for CherryTrail w/ CX2072x ++ * ++ * Copyright (C) 2016 Intel Corp ++ * Author: Pierre-Louis Bossart ++ * ++ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; version 2 of the License. ++ * ++ * This program is distributed in the hope that it will be useful, but ++ * WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "../../codecs/cx2072x.h" ++#include "../atom/sst-atom-controls.h" ++#include "../common/sst-acpi.h" ++ ++ ++#define CHT_PLAT_CLK_3_HZ 19200000 ++ ++enum { ++ CHT_CX_DMIC_MAP, ++}; ++ ++#define CHT_CX_MAP(quirk) ((quirk) & 0xff) ++#define CHT_CX_MCLK_EN BIT(16) ++ ++ ++struct cht_mc_private { ++ struct clk *mclk; ++}; ++ ++static unsigned long cht_cx_quirk = CHT_CX_MCLK_EN; ++ ++#define BYT_CODEC_DAI1 "cx2072x-hifi" ++ ++static inline struct snd_soc_dai *byt_get_codec_dai(struct snd_soc_card *card) ++{ ++ struct snd_soc_pcm_runtime *rtd; ++ ++ list_for_each_entry(rtd, &card->rtd_list, list) { ++ if (!strncmp(rtd->codec_dai->name, BYT_CODEC_DAI1, ++ strlen(BYT_CODEC_DAI1))) ++ return rtd->codec_dai; ++ } ++ return NULL; ++} ++ ++static int platform_clock_control(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *k, int event) ++{ ++ struct snd_soc_dapm_context *dapm = w->dapm; ++ struct snd_soc_card *card = dapm->card; ++ struct snd_soc_dai *codec_dai; ++ struct cht_mc_private *priv = snd_soc_card_get_drvdata(card); ++ int ret; ++ ++ codec_dai = byt_get_codec_dai(card); ++ if (!codec_dai) { ++ dev_err(card->dev, ++ "Codec dai not found; Unable to set platform clock\n"); ++ return -EIO; ++ } ++ ++ if (SND_SOC_DAPM_EVENT_ON(event)) { ++ if ((cht_cx_quirk & CHT_CX_MCLK_EN) && priv->mclk) { ++ ret = clk_prepare_enable(priv->mclk); ++ if (ret < 0) { ++ dev_err(card->dev, ++ "could not configure MCLK state"); ++ return ret; ++ } ++ } ++ ret = snd_soc_dai_set_sysclk(codec_dai, CX2072X_MCLK_EXTERNAL_PLL, ++ 19200000, SND_SOC_CLOCK_IN); ++ } else { ++ /* ++ * Set codec clock source to internal clock before ++ * turning off the platform clock. Codec needs clock ++ * for Jack detection and button press ++ */ ++ ++ /* FIXME: what is the internal setting, if any? */ ++ ret = snd_soc_dai_set_sysclk(codec_dai, CX2072X_MCLK_EXTERNAL_PLL, ++ 19200000, SND_SOC_CLOCK_IN); ++ ++ if (!ret) { ++ if ((cht_cx_quirk & CHT_CX_MCLK_EN) && priv->mclk) ++ clk_disable_unprepare(priv->mclk); ++ } ++ } ++ ++ if (ret < 0) { ++ dev_err(card->dev, "can't set codec sysclk: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static const struct snd_soc_dapm_widget cht_dapm_widgets[] = { ++ SND_SOC_DAPM_HP("Headphone", NULL), ++ SND_SOC_DAPM_MIC("Headset Mic", NULL), ++ SND_SOC_DAPM_MIC("Int Mic", NULL), ++ SND_SOC_DAPM_SPK("Ext Spk", NULL), ++ SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, ++ platform_clock_control, SND_SOC_DAPM_PRE_PMU | ++ SND_SOC_DAPM_POST_PMD), ++}; ++ ++static const struct snd_soc_dapm_route cht_audio_map[] = { ++ /* External Speakers: HFL, HFR */ ++ {"Headphone", NULL, "PORTA"}, ++ {"Ext Spk", NULL, "PORTG"}, ++ {"PORTC", NULL, "Int Mic"}, ++ /* Headset Mic: Headset Mic with bias */ ++ {"PORTD", NULL, "Headset Mic"}, ++ /*{"Headset Bias", NULL, "Headset Mic"},*/ ++ /*{"PortD Mic Bias", NULL, "Headset Mic"},*/ ++ ++ /* Headset Stereophone(Headphone): HSOL, HSOR */ ++ //{"Headphone", NULL, "HPL"}, ++ //{"Headphone", NULL, "HPR"}, ++ ++ {"Playback", NULL, "ssp2 Tx"}, ++ {"ssp2 Tx", NULL, "codec_out0"}, ++ {"ssp2 Tx", NULL, "codec_out1"}, ++ {"codec_in0", NULL, "ssp2 Rx"}, ++ {"codec_in1", NULL, "ssp2 Rx"}, ++ {"ssp2 Rx", NULL, "Capture"}, ++ {"ssp0 Tx", NULL, "modem_out"}, ++ {"modem_in", NULL, "ssp0 Rx"}, ++ {"Playback", NULL, "Platform Clock"}, ++ {"Capture", NULL, "Platform Clock"}, ++}; ++ ++static const struct snd_kcontrol_new cht_mc_controls[] = { ++ SOC_DAPM_PIN_SWITCH("Headphone"), ++ SOC_DAPM_PIN_SWITCH("Headset Mic"), ++ SOC_DAPM_PIN_SWITCH("Int Mic"), ++ SOC_DAPM_PIN_SWITCH("Ext Spk"), ++}; ++ ++static int cht_aif1_hw_params(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params) ++{ ++ struct snd_soc_pcm_runtime *rtd = substream->private_data; ++ struct snd_soc_dai *codec_dai = rtd->codec_dai; ++ int ret; ++ ++ ret = snd_soc_dai_set_sysclk(codec_dai, CX2072X_MCLK_EXTERNAL_PLL, ++ 19200000, SND_SOC_CLOCK_IN); ++ if (ret < 0) { ++ dev_err(rtd->dev, "can't set codec sysclk: %d\n", ret); ++ return ret; ++ } ++ ++ /* FIXME: No need PLL for conexant codec */ ++#if 0 ++ ret = snd_soc_dai_set_pll(codec_dai, 0, RT5670_PLL1_S_MCLK, ++ CHT_PLAT_CLK_3_HZ, rate * 512); ++ if (ret < 0) { ++ dev_err(rtd->dev, "can't set codec pll: %d\n", ret); ++ return ret; ++ } ++#endif ++ ++ ++ return 0; ++} ++ ++static int cht_codec_init(struct snd_soc_pcm_runtime *runtime) ++{ ++ int ret=0; ++ struct snd_soc_card *card = runtime->card; ++ struct cht_mc_private *priv = snd_soc_card_get_drvdata(runtime->card); ++ ++ card->dapm.idle_bias_off = true; ++ ++ /* FIXME: is this necessary */ ++// snd_soc_dapm_ignore_suspend(&codec->dapm, "PortD"); ++// snd_soc_dapm_enable_pin(&card->dapm, "Headset Mic"); ++// snd_soc_dapm_enable_pin(&card->dapm, "Headphone"); ++// snd_soc_dapm_enable_pin(&card->dapm, "Ext Spk"); ++// snd_soc_dapm_enable_pin(&card->dapm, "Int Mic"); ++ ++// snd_soc_dapm_sync(&card->dapm); ++ ++ if ((cht_cx_quirk & CHT_CX_MCLK_EN) && priv->mclk) { ++ /* ++ * The firmware might enable the clock at ++ * boot (this information may or may not ++ * be reflected in the enable clock register). ++ * To change the rate we must disable the clock ++ * first to cover these cases. Due to common ++ * clock framework restrictions that do not allow ++ * to disable a clock that has not been enabled, ++ * we need to enable the clock first. ++ */ ++ ret = clk_prepare_enable(priv->mclk); ++ if (!ret) ++ clk_disable_unprepare(priv->mclk); ++ ++ ret = clk_set_rate(priv->mclk, 19200000); ++ ++ if (ret) ++ dev_err(card->dev, "unable to set MCLK rate\n"); ++ } ++ ++ return ret; ++} ++ ++static const struct snd_soc_pcm_stream cht_dai_params = { ++ .formats = SNDRV_PCM_FMTBIT_S24_LE, ++ .rate_min = 48000, ++ .rate_max = 48000, ++ .channels_min = 2, ++ .channels_max = 2, ++}; ++ ++static int cht_codec_fixup(struct snd_soc_pcm_runtime *rtd, ++ struct snd_pcm_hw_params *params) ++{ ++ struct snd_interval *rate = hw_param_interval(params, ++ SNDRV_PCM_HW_PARAM_RATE); ++ struct snd_interval *channels = hw_param_interval(params, ++ SNDRV_PCM_HW_PARAM_CHANNELS); ++ int ret; ++ ++ /* The DSP will covert the FE rate to 48k, stereo, 24bits */ ++ rate->min = rate->max = 48000; ++ channels->min = channels->max = 2; ++ ++ /* set SSP2 to 24-bit */ ++ params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); ++ ++ /* ++ * Default mode for SSP configuration is TDM 4 slot, override config ++ * with explicit setting to I2S 2ch 24-bit. The word length is set with ++ * dai_set_tdm_slot() since there is no other API exposed ++ */ ++ ret = snd_soc_dai_set_fmt(rtd->cpu_dai, ++ SND_SOC_DAIFMT_I2S | ++ SND_SOC_DAIFMT_NB_IF | ++ SND_SOC_DAIFMT_CBS_CFS); ++ if (ret < 0) { ++ dev_err(rtd->dev, "can't set format to I2S, err %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static const struct snd_soc_pcm_stream cht_cx_dai_params = { ++ .formats = SNDRV_PCM_FMTBIT_S24_LE, ++ .rate_min = 48000, ++ .rate_max = 48000, ++ .channels_min = 2, ++ .channels_max = 2, ++}; ++ ++static int cht_aif1_startup(struct snd_pcm_substream *substream) ++{ ++ return snd_pcm_hw_constraint_single(substream->runtime, ++ SNDRV_PCM_HW_PARAM_RATE, 48000); ++} ++ ++static struct snd_soc_ops cht_aif1_ops = { ++ .startup = cht_aif1_startup, ++}; ++ ++static struct snd_soc_ops cht_be_ssp2_ops = { ++ .hw_params = cht_aif1_hw_params, ++}; ++ ++static struct snd_soc_dai_link cht_dailink[] = { ++ [MERR_DPCM_AUDIO] = { ++ .name = "Audio Port", ++ .stream_name = "Audio", ++ .cpu_dai_name = "media-cpu-dai", ++ .codec_dai_name = "snd-soc-dummy-dai", ++ .codec_name = "snd-soc-dummy", ++ .platform_name = "sst-mfld-platform", ++ .nonatomic = true, ++ .dynamic = 1, ++ .dpcm_playback = 1, ++ .dpcm_capture = 1, ++ .ops = &cht_aif1_ops, ++ }, ++ [MERR_DPCM_DEEP_BUFFER] = { ++ .name = "Deep-Buffer Audio Port", ++ .stream_name = "Deep-Buffer Audio", ++ .cpu_dai_name = "deepbuffer-cpu-dai", ++ .codec_dai_name = "snd-soc-dummy-dai", ++ .codec_name = "snd-soc-dummy", ++ .platform_name = "sst-mfld-platform", ++ .nonatomic = true, ++ .dynamic = 1, ++ .dpcm_playback = 1, ++ .ops = &cht_aif1_ops, ++ }, ++ [MERR_DPCM_COMPR] = { ++ .name = "Compressed Port", ++ .stream_name = "Compress", ++ .cpu_dai_name = "compress-cpu-dai", ++ .codec_dai_name = "snd-soc-dummy-dai", ++ .codec_name = "snd-soc-dummy", ++ .platform_name = "sst-mfld-platform", ++ }, ++ /* CODEC<->CODEC link */ ++ /* back ends */ ++ { ++ .name = "SSP2-Codec", ++ .id = 1, ++ .cpu_dai_name = "ssp2-port", ++ .platform_name = "sst-mfld-platform", ++ .no_pcm = 1, ++ .codec_dai_name = "cx2072x-hifi", ++ .codec_name = "i2c-14F10720:00", ++ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_IF ++ | SND_SOC_DAIFMT_CBS_CFS, ++ .init = cht_codec_init, ++ .be_hw_params_fixup = cht_codec_fixup, ++ .nonatomic = true, ++ .dpcm_playback = 1, ++ .dpcm_capture = 1, ++ .ops = &cht_be_ssp2_ops, ++ }, ++}; ++ ++/* SoC card */ ++static struct snd_soc_card chtcx2072x_card = { ++ .name = "chtcx2072x", ++ .dai_link = cht_dailink, ++ .num_links = ARRAY_SIZE(cht_dailink), ++ .dapm_widgets = cht_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(cht_dapm_widgets), ++ .dapm_routes = cht_audio_map, ++ .num_dapm_routes = ARRAY_SIZE(cht_audio_map), ++ .controls = cht_mc_controls, ++ .num_controls = ARRAY_SIZE(cht_mc_controls), ++}; ++ ++static char cht_cx_codec_name[16]; /* i2c-:00 with HID being 8 chars */ ++ ++static bool is_valleyview(void) ++{ ++ static const struct x86_cpu_id cpu_ids[] = { ++ { X86_VENDOR_INTEL, 6, 55 }, /* Valleyview, Bay Trail */ ++ {} ++ }; ++ ++ if (!x86_match_cpu(cpu_ids)) ++ return false; ++ return true; ++} ++ ++static int snd_cht_mc_probe(struct platform_device *pdev) ++{ ++ int ret_val = 0; ++ int i; ++ int dai_index; ++ struct cht_mc_private *drv; ++ struct sst_acpi_mach *mach; ++ const char *i2c_name = NULL; ++ ++ drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_ATOMIC); ++ if (!drv) { ++ pr_err("allocation failed\n"); ++ return -ENOMEM; ++ } ++ ++ /* register the soc card */ ++ chtcx2072x_card.dev = &pdev->dev; ++ mach = chtcx2072x_card.dev->platform_data; ++ snd_soc_card_set_drvdata(&chtcx2072x_card, drv); ++ ++ /* fix index of codec dai */ ++ dai_index = MERR_DPCM_COMPR + 1; ++ for (i = 0; i < ARRAY_SIZE(cht_dailink); i++) { ++ if (!strcmp(cht_dailink[i].codec_name, "i2c-14F10720:00")) { ++ dai_index = i; ++ break; ++ } ++ } ++ ++ /* fixup codec name based on HID */ ++ i2c_name = sst_acpi_find_name_from_hid(mach->id); ++ if (i2c_name != NULL) { ++ snprintf(cht_cx_codec_name, sizeof(cht_cx_codec_name), ++ "%s%s", "i2c-", i2c_name); ++ ++ cht_dailink[dai_index].codec_name = cht_cx_codec_name; ++ } ++ ++ if ((cht_cx_quirk & CHT_CX_MCLK_EN) && (is_valleyview())) { ++ drv->mclk = devm_clk_get(&pdev->dev, "pmc_plt_clk_3"); ++ if (IS_ERR(drv->mclk)) { ++ dev_err(&pdev->dev, ++ "Failed to get MCLK from pmc_plt_clk_3: %ld\n", ++ PTR_ERR(drv->mclk)); ++ ++ return PTR_ERR(drv->mclk); ++ } ++ } ++ ++ ret_val = devm_snd_soc_register_card(&pdev->dev, &chtcx2072x_card); ++ ++ if (ret_val) { ++ dev_err(&pdev->dev, "devm_snd_soc_register_card failed %d\n", ++ ret_val); ++ return ret_val; ++ } ++ platform_set_drvdata(pdev, &chtcx2072x_card); ++ return ret_val; ++} ++ ++ ++ ++static struct platform_driver snd_cht_mc_driver = { ++ .driver = { ++ .name = "cht-cx2072x", ++ .pm = &snd_soc_pm_ops, ++ }, ++ .probe = snd_cht_mc_probe, ++}; ++module_platform_driver(snd_cht_mc_driver); ++ ++MODULE_DESCRIPTION("ASoC Intel(R) Cherrytrail Machine driver"); ++MODULE_AUTHOR("Pierre-Louis Bossart "); ++MODULE_LICENSE("GPL v2"); ++MODULE_ALIAS("platform:cht-cx2072x"); +diff --git a/sound/soc/intel/common/sst-acpi.h b/sound/soc/intel/common/sst-acpi.h +index 012742299dd5..214e000667ae 100644 +--- a/sound/soc/intel/common/sst-acpi.h ++++ b/sound/soc/intel/common/sst-acpi.h +@@ -15,14 +15,29 @@ + #include + #include + +-/* translation fron HID to I2C name, needed for DAI codec_name */ ++struct sst_acpi_package_context { ++ char *name; /* package name */ ++ int length; /* number of elements */ ++ struct acpi_buffer *format; ++ struct acpi_buffer *state; ++ bool data_valid; ++}; ++ + #if IS_ENABLED(CONFIG_ACPI) ++/* translation fron HID to I2C name, needed for DAI codec_name */ + const char *sst_acpi_find_name_from_hid(const u8 hid[ACPI_ID_LEN]); ++bool sst_acpi_find_package_from_hid(const u8 hid[ACPI_ID_LEN], ++ struct sst_acpi_package_context *ctx); + #else + static inline const char *sst_acpi_find_name_from_hid(const u8 hid[ACPI_ID_LEN]) + { + return NULL; + } ++static inline bool sst_acpi_find_package_from_hid(const u8 hid[ACPI_ID_LEN], ++ struct sst_acpi_package_context *ctx) ++{ ++ return false; ++} + #endif + + /* acpi match */ +diff --git a/sound/soc/intel/common/sst-match-acpi.c b/sound/soc/intel/common/sst-match-acpi.c +index 789843307a49..1070f3ad23e5 100644 +--- a/sound/soc/intel/common/sst-match-acpi.c ++++ b/sound/soc/intel/common/sst-match-acpi.c +@@ -77,5 +77,62 @@ struct sst_acpi_mach *sst_acpi_find_machine(struct sst_acpi_mach *machines) + } + EXPORT_SYMBOL_GPL(sst_acpi_find_machine); + ++static acpi_status sst_acpi_find_package(acpi_handle handle, u32 level, ++ void *context, void **ret) ++{ ++ struct acpi_device *adev; ++ acpi_status status = AE_OK; ++ struct sst_acpi_package_context *pkg_ctx = context; ++ ++ pkg_ctx->data_valid = false; ++ ++ if (acpi_bus_get_device(handle, &adev)) ++ return AE_OK; ++ ++ if (adev->status.present && adev->status.functional) { ++ struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL}; ++ union acpi_object *myobj = NULL; ++ ++ status = acpi_evaluate_object_typed(handle, pkg_ctx->name, ++ NULL, &buffer, ++ ACPI_TYPE_PACKAGE); ++ if (ACPI_FAILURE(status)) ++ return AE_OK; ++ ++ myobj = buffer.pointer; ++ if (!myobj || myobj->package.count != pkg_ctx->length) { ++ kfree(buffer.pointer); ++ return AE_OK; ++ } ++ ++ status = acpi_extract_package(myobj, ++ pkg_ctx->format, pkg_ctx->state); ++ if (ACPI_FAILURE(status)) { ++ kfree(buffer.pointer); ++ return AE_OK; ++ } ++ ++ kfree(buffer.pointer); ++ pkg_ctx->data_valid = true; ++ return AE_CTRL_TERMINATE; ++ } ++ ++ return AE_OK; ++} ++ ++bool sst_acpi_find_package_from_hid(const u8 hid[ACPI_ID_LEN], ++ struct sst_acpi_package_context *ctx) ++{ ++ acpi_status status; ++ ++ status = acpi_get_devices(hid, sst_acpi_find_package, ctx, NULL); ++ ++ if (ACPI_FAILURE(status) || !ctx->data_valid) ++ return false; ++ ++ return true; ++} ++EXPORT_SYMBOL_GPL(sst_acpi_find_package_from_hid); ++ + MODULE_LICENSE("GPL v2"); + MODULE_DESCRIPTION("Intel Common ACPI Match module"); -- cgit 1.4.1