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