From f59c8f9fe689790248ae7aa7426579982050638c Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 31 Aug 2012 10:36:37 -0700 Subject: [PATCH 01/32] regulator: core: Support bypass mode Many regulators support a bypass mode where they simply switch their input supply to the output. This is mainly used in low power retention states where power consumption is extremely low so higher voltage or less clean supplies can be used. Support this by providing ops for the drivers and a consumer API which allows the device to be put into bypass mode if all consumers enable it and the machine enables permission for this. This is not supported as a mode since the existing modes are rarely used due to fuzzy definition and mostly redundant with modern hardware which is able to respond promptly to load changes. Signed-off-by: Mark Brown Reviewed-by: Graeme Gregory --- .../ABI/testing/sysfs-class-regulator | 21 +++++ drivers/regulator/core.c | 85 +++++++++++++++++++ include/linux/regulator/consumer.h | 8 ++ include/linux/regulator/driver.h | 10 +++ include/linux/regulator/machine.h | 2 + 5 files changed, 126 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-class-regulator b/Documentation/ABI/testing/sysfs-class-regulator index e091fa873792..bc578bc60628 100644 --- a/Documentation/ABI/testing/sysfs-class-regulator +++ b/Documentation/ABI/testing/sysfs-class-regulator @@ -349,3 +349,24 @@ Description: This will be one of the same strings reported by the "state" attribute. + +What: /sys/class/regulator/.../bypass +Date: September 2012 +KernelVersion: 3.7 +Contact: Mark Brown +Description: + Some regulator directories will contain a field called + bypass. This indicates if the device is in bypass mode. + + This will be one of the following strings: + + 'enabled' + 'disabled' + 'unknown' + + 'enabled' means the regulator is in bypass mode. + + 'disabled' means that the regulator is regulating. + + 'unknown' means software cannot determine the state, or + the reported state is invalid. diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index 48385318175a..64e16053975e 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -77,6 +77,7 @@ struct regulator { struct device *dev; struct list_head list; unsigned int always_on:1; + unsigned int bypass:1; int uA_load; int min_uV; int max_uV; @@ -394,6 +395,9 @@ static ssize_t regulator_status_show(struct device *dev, case REGULATOR_STATUS_STANDBY: label = "standby"; break; + case REGULATOR_STATUS_BYPASS: + label = "bypass"; + break; case REGULATOR_STATUS_UNDEFINED: label = "undefined"; break; @@ -585,6 +589,27 @@ static ssize_t regulator_suspend_standby_state_show(struct device *dev, static DEVICE_ATTR(suspend_standby_state, 0444, regulator_suspend_standby_state_show, NULL); +static ssize_t regulator_bypass_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct regulator_dev *rdev = dev_get_drvdata(dev); + const char *report; + bool bypass; + int ret; + + ret = rdev->desc->ops->get_bypass(rdev, &bypass); + + if (ret != 0) + report = "unknown"; + else if (bypass) + report = "enabled"; + else + report = "disabled"; + + return sprintf(buf, "%s\n", report); +} +static DEVICE_ATTR(bypass, 0444, + regulator_bypass_show, NULL); /* * These are the only attributes are present for all regulators. @@ -2673,6 +2698,59 @@ out: } EXPORT_SYMBOL_GPL(regulator_set_optimum_mode); +/** + * regulator_allow_bypass - allow the regulator to go into bypass mode + * + * @regulator: Regulator to configure + * @allow: enable or disable bypass mode + * + * Allow the regulator to go into bypass mode if all other consumers + * for the regulator also enable bypass mode and the machine + * constraints allow this. Bypass mode means that the regulator is + * simply passing the input directly to the output with no regulation. + */ +int regulator_allow_bypass(struct regulator *regulator, bool enable) +{ + struct regulator_dev *rdev = regulator->rdev; + int ret = 0; + + if (!rdev->desc->ops->set_bypass) + return 0; + + if (rdev->constraints && + !(rdev->constraints->valid_ops_mask & REGULATOR_CHANGE_BYPASS)) + return 0; + + mutex_lock(&rdev->mutex); + + if (enable && !regulator->bypass) { + rdev->bypass_count++; + + if (rdev->bypass_count == rdev->open_count) { + ret = rdev->desc->ops->set_bypass(rdev, enable); + if (ret != 0) + rdev->bypass_count--; + } + + } else if (!enable && regulator->bypass) { + rdev->bypass_count--; + + if (rdev->bypass_count != rdev->open_count) { + ret = rdev->desc->ops->set_bypass(rdev, enable); + if (ret != 0) + rdev->bypass_count++; + } + } + + if (ret == 0) + regulator->bypass = enable; + + mutex_unlock(&rdev->mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(regulator_allow_bypass); + /** * regulator_register_notifier - register regulator event notifier * @regulator: regulator source @@ -3036,6 +3114,11 @@ static int add_regulator_attributes(struct regulator_dev *rdev) if (status < 0) return status; } + if (ops->get_bypass) { + status = device_create_file(dev, &dev_attr_bypass); + if (status < 0) + return status; + } /* some attributes are type-specific */ if (rdev->desc->type == REGULATOR_CURRENT) { @@ -3124,6 +3207,8 @@ static void rdev_init_debugfs(struct regulator_dev *rdev) &rdev->use_count); debugfs_create_u32("open_count", 0444, rdev->debugfs, &rdev->open_count); + debugfs_create_u32("bypass_count", 0444, rdev->debugfs, + &rdev->bypass_count); } /** diff --git a/include/linux/regulator/consumer.h b/include/linux/regulator/consumer.h index da339fd8c755..ea3e35816621 100644 --- a/include/linux/regulator/consumer.h +++ b/include/linux/regulator/consumer.h @@ -177,6 +177,8 @@ int regulator_set_mode(struct regulator *regulator, unsigned int mode); unsigned int regulator_get_mode(struct regulator *regulator); int regulator_set_optimum_mode(struct regulator *regulator, int load_uA); +int regulator_allow_bypass(struct regulator *regulator, bool allow); + /* regulator notifier block */ int regulator_register_notifier(struct regulator *regulator, struct notifier_block *nb); @@ -328,6 +330,12 @@ static inline int regulator_set_optimum_mode(struct regulator *regulator, return REGULATOR_MODE_NORMAL; } +static inline int regulator_allow_bypass(struct regulator *regulator, + bool allow) +{ + return 0; +} + static inline int regulator_register_notifier(struct regulator *regulator, struct notifier_block *nb) { diff --git a/include/linux/regulator/driver.h b/include/linux/regulator/driver.h index bac4c871f3bd..c9869cfbf261 100644 --- a/include/linux/regulator/driver.h +++ b/include/linux/regulator/driver.h @@ -32,6 +32,8 @@ enum regulator_status { REGULATOR_STATUS_NORMAL, REGULATOR_STATUS_IDLE, REGULATOR_STATUS_STANDBY, + /* The regulator is enabled but not regulating */ + REGULATOR_STATUS_BYPASS, /* in case that any other status doesn't apply */ REGULATOR_STATUS_UNDEFINED, }; @@ -67,6 +69,9 @@ enum regulator_status { * @get_optimum_mode: Get the most efficient operating mode for the regulator * when running with the specified parameters. * + * @set_bypass: Set the regulator in bypass mode. + * @get_bypass: Get the regulator bypass mode state. + * * @enable_time: Time taken for the regulator voltage output voltage to * stabilise after being enabled, in microseconds. * @set_ramp_delay: Set the ramp delay for the regulator. The driver should @@ -133,6 +138,10 @@ struct regulator_ops { unsigned int (*get_optimum_mode) (struct regulator_dev *, int input_uV, int output_uV, int load_uA); + /* control and report on bypass mode */ + int (*set_bypass)(struct regulator_dev *dev, bool enable); + int (*get_bypass)(struct regulator_dev *dev, bool *enable); + /* the operations below are for configuration of regulator state when * its parent PMIC enters a global STANDBY/HIBERNATE state */ @@ -253,6 +262,7 @@ struct regulator_dev { int exclusive; u32 use_count; u32 open_count; + u32 bypass_count; /* lists we belong to */ struct list_head list; /* list of all regulators */ diff --git a/include/linux/regulator/machine.h b/include/linux/regulator/machine.h index 40dd0a394cfa..36adbc82de6a 100644 --- a/include/linux/regulator/machine.h +++ b/include/linux/regulator/machine.h @@ -32,6 +32,7 @@ struct regulator; * board/machine. * STATUS: Regulator can be enabled and disabled. * DRMS: Dynamic Regulator Mode Switching is enabled for this regulator. + * BYPASS: Regulator can be put into bypass mode */ #define REGULATOR_CHANGE_VOLTAGE 0x1 @@ -39,6 +40,7 @@ struct regulator; #define REGULATOR_CHANGE_MODE 0x4 #define REGULATOR_CHANGE_STATUS 0x8 #define REGULATOR_CHANGE_DRMS 0x10 +#define REGULATOR_CHANGE_BYPASS 0x20 /** * struct regulator_state - regulator state during low power system states From df36793115b4f68181877a1c89bac54feadd965d Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 27 Aug 2012 16:04:23 -0700 Subject: [PATCH 02/32] regulator: core: Provide regmap get/set bypass operations Signed-off-by: Mark Brown --- drivers/regulator/core.c | 41 ++++++++++++++++++++++++++++++++ include/linux/regulator/driver.h | 4 ++++ 2 files changed, 45 insertions(+) diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index 64e16053975e..419805cdd9d7 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -2698,6 +2698,47 @@ out: } EXPORT_SYMBOL_GPL(regulator_set_optimum_mode); +/** + * regulator_set_bypass_regmap - Default set_bypass() using regmap + * + * @rdev: device to operate on. + * @enable: state to set. + */ +int regulator_set_bypass_regmap(struct regulator_dev *rdev, bool enable) +{ + unsigned int val; + + if (enable) + val = rdev->desc->bypass_mask; + else + val = 0; + + return regmap_update_bits(rdev->regmap, rdev->desc->bypass_reg, + rdev->desc->bypass_mask, val); +} +EXPORT_SYMBOL_GPL(regulator_set_bypass_regmap); + +/** + * regulator_get_bypass_regmap - Default get_bypass() using regmap + * + * @rdev: device to operate on. + * @enable: current state. + */ +int regulator_get_bypass_regmap(struct regulator_dev *rdev, bool *enable) +{ + unsigned int val; + int ret; + + ret = regmap_read(rdev->regmap, rdev->desc->bypass_reg, &val); + if (ret != 0) + return ret; + + *enable = val & rdev->desc->bypass_mask; + + return 0; +} +EXPORT_SYMBOL_GPL(regulator_get_bypass_regmap); + /** * regulator_allow_bypass - allow the regulator to go into bypass mode * diff --git a/include/linux/regulator/driver.h b/include/linux/regulator/driver.h index c9869cfbf261..7274a469e8d9 100644 --- a/include/linux/regulator/driver.h +++ b/include/linux/regulator/driver.h @@ -214,6 +214,8 @@ struct regulator_desc { unsigned int vsel_mask; unsigned int enable_reg; unsigned int enable_mask; + unsigned int bypass_reg; + unsigned int bypass_mask; unsigned int enable_time; }; @@ -320,6 +322,8 @@ int regulator_disable_regmap(struct regulator_dev *rdev); int regulator_set_voltage_time_sel(struct regulator_dev *rdev, unsigned int old_selector, unsigned int new_selector); +int regulator_set_bypass_regmap(struct regulator_dev *rdev, bool enable); +int regulator_get_bypass_regmap(struct regulator_dev *rdev, bool *enable); void *regulator_get_init_drvdata(struct regulator_init_data *reg_init_data); From 9a17de04ef847f674ffe64eb46b9b99e14e663fa Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 27 Aug 2012 16:04:35 -0700 Subject: [PATCH 03/32] regulator: arizona-ldo: Support get/set bypass Signed-off-by: Mark Brown --- drivers/regulator/arizona-ldo1.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/regulator/arizona-ldo1.c b/drivers/regulator/arizona-ldo1.c index c8f95c07adb6..80e012f14160 100644 --- a/drivers/regulator/arizona-ldo1.c +++ b/drivers/regulator/arizona-ldo1.c @@ -39,6 +39,8 @@ static struct regulator_ops arizona_ldo1_ops = { .map_voltage = regulator_map_voltage_linear, .get_voltage_sel = regulator_get_voltage_sel_regmap, .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_bypass = regulator_get_bypass_regmap, + .set_bypass = regulator_set_bypass_regmap, }; static const struct regulator_desc arizona_ldo1 = { @@ -49,6 +51,8 @@ static const struct regulator_desc arizona_ldo1 = { .vsel_reg = ARIZONA_LDO1_CONTROL_1, .vsel_mask = ARIZONA_LDO1_VSEL_MASK, + .bypass_reg = ARIZONA_LDO1_CONTROL_1, + .bypass_mask = ARIZONA_LDO1_BYPASS, .min_uV = 900000, .uV_step = 50000, .n_voltages = 7, From e477ce075d205f9eee3e28795eae7a0efd1e54f6 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 27 Aug 2012 16:04:47 -0700 Subject: [PATCH 04/32] regulator: arizona-micsupp: Support get/set bypass Signed-off-by: Mark Brown --- drivers/regulator/arizona-micsupp.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/regulator/arizona-micsupp.c b/drivers/regulator/arizona-micsupp.c index 450a069aa9b6..d9b1f82cc5bd 100644 --- a/drivers/regulator/arizona-micsupp.c +++ b/drivers/regulator/arizona-micsupp.c @@ -82,6 +82,9 @@ static struct regulator_ops arizona_micsupp_ops = { .get_voltage_sel = regulator_get_voltage_sel_regmap, .set_voltage_sel = regulator_set_voltage_sel_regmap, + + .get_bypass = regulator_get_bypass_regmap, + .set_bypass = regulator_set_bypass_regmap, }; static const struct regulator_desc arizona_micsupp = { @@ -95,6 +98,8 @@ static const struct regulator_desc arizona_micsupp = { .vsel_mask = ARIZONA_LDO2_VSEL_MASK, .enable_reg = ARIZONA_MIC_CHARGE_PUMP_1, .enable_mask = ARIZONA_CPMIC_ENA, + .bypass_reg = ARIZONA_MIC_CHARGE_PUMP_1, + .bypass_mask = ARIZONA_CPMIC_BYPASS, .owner = THIS_MODULE, }; From 22c5fb6a7dc8674424394122337c84eaf48efbdf Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 27 Aug 2012 21:52:29 -0700 Subject: [PATCH 05/32] regulator: wm831x-ldo: Add bypass support Signed-off-by: Mark Brown --- drivers/regulator/wm831x-ldo.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/regulator/wm831x-ldo.c b/drivers/regulator/wm831x-ldo.c index 5cb70ca1e98d..f203a972dedf 100644 --- a/drivers/regulator/wm831x-ldo.c +++ b/drivers/regulator/wm831x-ldo.c @@ -237,6 +237,8 @@ static struct regulator_ops wm831x_gp_ldo_ops = { .set_mode = wm831x_gp_ldo_set_mode, .get_status = wm831x_gp_ldo_get_status, .get_optimum_mode = wm831x_gp_ldo_get_optimum_mode, + .get_bypass = regulator_get_bypass_regmap, + .set_bypass = regulator_set_bypass_regmap, .is_enabled = regulator_is_enabled_regmap, .enable = regulator_enable_regmap, @@ -293,6 +295,8 @@ static __devinit int wm831x_gp_ldo_probe(struct platform_device *pdev) ldo->desc.vsel_mask = WM831X_LDO1_ON_VSEL_MASK; ldo->desc.enable_reg = WM831X_LDO_ENABLE; ldo->desc.enable_mask = 1 << id; + ldo->desc.bypass_reg = ldo->base; + ldo->desc.bypass_mask = WM831X_LDO1_SWI; config.dev = pdev->dev.parent; if (pdata) @@ -488,6 +492,8 @@ static struct regulator_ops wm831x_aldo_ops = { .get_mode = wm831x_aldo_get_mode, .set_mode = wm831x_aldo_set_mode, .get_status = wm831x_aldo_get_status, + .set_bypass = regulator_set_bypass_regmap, + .get_bypass = regulator_get_bypass_regmap, .is_enabled = regulator_is_enabled_regmap, .enable = regulator_enable_regmap, @@ -544,6 +550,8 @@ static __devinit int wm831x_aldo_probe(struct platform_device *pdev) ldo->desc.vsel_mask = WM831X_LDO7_ON_VSEL_MASK; ldo->desc.enable_reg = WM831X_LDO_ENABLE; ldo->desc.enable_mask = 1 << id; + ldo->desc.bypass_reg = ldo->base; + ldo->desc.bypass_mask = WM831X_LDO7_SWI; config.dev = pdev->dev.parent; if (pdata) From b8575a1143f6d57a791c946291ba4363e07e32a3 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 7 Sep 2012 17:01:15 +0800 Subject: [PATCH 06/32] extcon: arizona: Use bypass mode for MICVDD We can perform microphone detection with MICVDD in bypass mode so try to enable that during startup for minimal power - other users or machine constraints will prevent bypass mode being activated if it is unsuitable. Signed-off-by: Mark Brown Acked-by: Greg Kroah-Hartman --- drivers/extcon/extcon-arizona.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index 427a289f32a5..6c19833ed2d0 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -434,6 +434,11 @@ static int __devinit arizona_extcon_probe(struct platform_device *pdev) regmap_update_bits(arizona->regmap, ARIZONA_JACK_DETECT_ANALOGUE, ARIZONA_JD1_ENA, ARIZONA_JD1_ENA); + ret = regulator_allow_bypass(info->micvdd, true); + if (ret != 0) + dev_warn(arizona->dev, "Failed to set MICVDD to bypass: %d\n", + ret); + pm_runtime_put(&pdev->dev); return 0; From 5ca032ee21cdabd08fb368ce3f02fa8906b0ef5f Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Fri, 14 Sep 2012 16:16:08 +0100 Subject: [PATCH 07/32] ASoC: Ux500: Move MSP pinctrl setup into the MSP driver In the initial submission of the MSP driver msp1 and msp3's associated pinctrl mechanism was passed back to platform code using a plat_init() call-back routine, but it has no place in platform code. The MSP driver should set this up for the appropriate ports. Instead we use a use_pinctrl identifier which is passed from platform_data/Device Tree which indicates which ports should use pinctrl. Acked-by: Ola Lilja Acked-by: Linus Walleij Signed-off-by: Lee Jones --- arch/arm/mach-ux500/board-mop500-msp.c | 79 +------------------------- arch/arm/mach-ux500/include/mach/msp.h | 2 - sound/soc/ux500/ux500_msp_i2s.c | 67 ++++++++++++++++------ sound/soc/ux500/ux500_msp_i2s.h | 8 ++- 4 files changed, 58 insertions(+), 98 deletions(-) diff --git a/arch/arm/mach-ux500/board-mop500-msp.c b/arch/arm/mach-ux500/board-mop500-msp.c index df15646036aa..ace6c051bc1a 100644 --- a/arch/arm/mach-ux500/board-mop500-msp.c +++ b/arch/arm/mach-ux500/board-mop500-msp.c @@ -7,7 +7,6 @@ #include #include #include -#include #include #include @@ -23,53 +22,6 @@ #include "devices-db8500.h" #include "pins-db8500.h" -/* MSP1/3 Tx/Rx usage protection */ -static DEFINE_SPINLOCK(msp_rxtx_lock); - -/* Reference Count */ -static int msp_rxtx_ref; - -/* Pin modes */ -struct pinctrl *msp1_p; -struct pinctrl_state *msp1_def; -struct pinctrl_state *msp1_sleep; - -int msp13_i2s_init(void) -{ - int retval = 0; - unsigned long flags; - - spin_lock_irqsave(&msp_rxtx_lock, flags); - if (msp_rxtx_ref == 0 && !(IS_ERR(msp1_p) || IS_ERR(msp1_def))) { - retval = pinctrl_select_state(msp1_p, msp1_def); - if (retval) - pr_err("could not set MSP1 defstate\n"); - } - if (!retval) - msp_rxtx_ref++; - spin_unlock_irqrestore(&msp_rxtx_lock, flags); - - return retval; -} - -int msp13_i2s_exit(void) -{ - int retval = 0; - unsigned long flags; - - spin_lock_irqsave(&msp_rxtx_lock, flags); - WARN_ON(!msp_rxtx_ref); - msp_rxtx_ref--; - if (msp_rxtx_ref == 0 && !(IS_ERR(msp1_p) || IS_ERR(msp1_sleep))) { - retval = pinctrl_select_state(msp1_p, msp1_sleep); - if (retval) - pr_err("could not set MSP1 sleepstate\n"); - } - spin_unlock_irqrestore(&msp_rxtx_lock, flags); - - return retval; -} - static struct stedma40_chan_cfg msp0_dma_rx = { .high_priority = true, .dir = STEDMA40_PERIPH_TO_MEM, @@ -132,8 +84,6 @@ static struct msp_i2s_platform_data msp1_platform_data = { .id = MSP_I2S_1, .msp_i2s_dma_rx = NULL, .msp_i2s_dma_tx = &msp1_dma_tx, - .msp_i2s_init = msp13_i2s_init, - .msp_i2s_exit = msp13_i2s_exit, }; static struct stedma40_chan_cfg msp2_dma_rx = { @@ -219,49 +169,22 @@ static struct msp_i2s_platform_data msp3_platform_data = { .id = MSP_I2S_3, .msp_i2s_dma_rx = &msp1_dma_rx, .msp_i2s_dma_tx = NULL, - .msp_i2s_init = msp13_i2s_init, - .msp_i2s_exit = msp13_i2s_exit, }; int mop500_msp_init(struct device *parent) { - struct platform_device *msp1; - pr_info("%s: Register platform-device 'snd-soc-mop500'.\n", __func__); platform_device_register(&snd_soc_mop500); pr_info("Initialize MSP I2S-devices.\n"); db8500_add_msp_i2s(parent, 0, U8500_MSP0_BASE, IRQ_DB8500_MSP0, &msp0_platform_data); - msp1 = db8500_add_msp_i2s(parent, 1, U8500_MSP1_BASE, IRQ_DB8500_MSP1, + db8500_add_msp_i2s(parent, 1, U8500_MSP1_BASE, IRQ_DB8500_MSP1, &msp1_platform_data); db8500_add_msp_i2s(parent, 2, U8500_MSP2_BASE, IRQ_DB8500_MSP2, &msp2_platform_data); db8500_add_msp_i2s(parent, 3, U8500_MSP3_BASE, IRQ_DB8500_MSP1, &msp3_platform_data); - /* Get the pinctrl handle for MSP1 */ - if (msp1) { - msp1_p = pinctrl_get(&msp1->dev); - if (IS_ERR(msp1_p)) - dev_err(&msp1->dev, "could not get MSP1 pinctrl\n"); - else { - msp1_def = pinctrl_lookup_state(msp1_p, - PINCTRL_STATE_DEFAULT); - if (IS_ERR(msp1_def)) { - dev_err(&msp1->dev, - "could not get MSP1 defstate\n"); - } - msp1_sleep = pinctrl_lookup_state(msp1_p, - PINCTRL_STATE_SLEEP); - if (IS_ERR(msp1_sleep)) - dev_err(&msp1->dev, - "could not get MSP1 idlestate\n"); - } - } - - pr_info("%s: Register platform-device 'ux500-pcm'\n", __func__); - platform_device_register(&ux500_pcm); - return 0; } diff --git a/arch/arm/mach-ux500/include/mach/msp.h b/arch/arm/mach-ux500/include/mach/msp.h index 798be19129ef..3cc7142eee02 100644 --- a/arch/arm/mach-ux500/include/mach/msp.h +++ b/arch/arm/mach-ux500/include/mach/msp.h @@ -22,8 +22,6 @@ struct msp_i2s_platform_data { enum msp_i2s_id id; struct stedma40_chan_cfg *msp_i2s_dma_rx; struct stedma40_chan_cfg *msp_i2s_dma_tx; - int (*msp_i2s_init) (void); - int (*msp_i2s_exit) (void); }; #endif diff --git a/sound/soc/ux500/ux500_msp_i2s.c b/sound/soc/ux500/ux500_msp_i2s.c index eb85113d472a..12d7f567420d 100644 --- a/sound/soc/ux500/ux500_msp_i2s.c +++ b/sound/soc/ux500/ux500_msp_i2s.c @@ -15,6 +15,7 @@ #include #include +#include #include #include @@ -25,6 +26,9 @@ #include "ux500_msp_i2s.h" +/* MSP1/3 Tx/Rx usage protection */ +static DEFINE_SPINLOCK(msp_rxtx_lock); + /* Protocol desciptors */ static const struct msp_protdesc prot_descs[] = { { /* I2S */ @@ -352,17 +356,23 @@ static int configure_multichannel(struct ux500_msp *msp, static int enable_msp(struct ux500_msp *msp, struct ux500_msp_config *config) { - int status = 0; + int status = 0, retval = 0; u32 reg_val_DMACR, reg_val_GCR; + unsigned long flags; /* Check msp state whether in RUN or CONFIGURED Mode */ - if ((msp->msp_state == MSP_STATE_IDLE) && (msp->plat_init)) { - status = msp->plat_init(); - if (status) { - dev_err(msp->dev, "%s: ERROR: Failed to init MSP (%d)!\n", - __func__, status); - return status; + if (msp->msp_state == MSP_STATE_IDLE) { + spin_lock_irqsave(&msp_rxtx_lock, flags); + if (msp->pinctrl_rxtx_ref == 0 && + !(IS_ERR(msp->pinctrl_p) || IS_ERR(msp->pinctrl_def))) { + retval = pinctrl_select_state(msp->pinctrl_p, + msp->pinctrl_def); + if (retval) + pr_err("could not set MSP defstate\n"); } + if (!retval) + msp->pinctrl_rxtx_ref++; + spin_unlock_irqrestore(&msp_rxtx_lock, flags); } /* Configure msp with protocol dependent settings */ @@ -620,7 +630,8 @@ int ux500_msp_i2s_trigger(struct ux500_msp *msp, int cmd, int direction) int ux500_msp_i2s_close(struct ux500_msp *msp, unsigned int dir) { - int status = 0; + int status = 0, retval = 0; + unsigned long flags; dev_dbg(msp->dev, "%s: Enter (dir = 0x%01x).\n", __func__, dir); @@ -631,12 +642,19 @@ int ux500_msp_i2s_close(struct ux500_msp *msp, unsigned int dir) writel((readl(msp->registers + MSP_GCR) & (~(FRAME_GEN_ENABLE | SRG_ENABLE))), msp->registers + MSP_GCR); - if (msp->plat_exit) - status = msp->plat_exit(); - if (status) - dev_warn(msp->dev, - "%s: WARN: ux500_msp_i2s_exit failed (%d)!\n", - __func__, status); + + spin_lock_irqsave(&msp_rxtx_lock, flags); + WARN_ON(!msp->pinctrl_rxtx_ref); + msp->pinctrl_rxtx_ref--; + if (msp->pinctrl_rxtx_ref == 0 && + !(IS_ERR(msp->pinctrl_p) || IS_ERR(msp->pinctrl_sleep))) { + retval = pinctrl_select_state(msp->pinctrl_p, + msp->pinctrl_sleep); + if (retval) + pr_err("could not set MSP sleepstate\n"); + } + spin_unlock_irqrestore(&msp_rxtx_lock, flags); + writel(0, msp->registers + MSP_GCR); writel(0, msp->registers + MSP_TCF); writel(0, msp->registers + MSP_RCF); @@ -675,8 +693,6 @@ int ux500_msp_i2s_init_msp(struct platform_device *pdev, msp->id = platform_data->id; msp->dev = &pdev->dev; - msp->plat_init = platform_data->msp_i2s_init; - msp->plat_exit = platform_data->msp_i2s_exit; msp->dma_cfg_rx = platform_data->msp_i2s_dma_rx; msp->dma_cfg_tx = platform_data->msp_i2s_dma_tx; @@ -713,6 +729,25 @@ int ux500_msp_i2s_init_msp(struct platform_device *pdev, dev_dbg(&pdev->dev, "I2S device-name: '%s'\n", i2s_cont->name); msp->i2s_cont = i2s_cont; + msp->pinctrl_p = pinctrl_get(msp->dev); + if (IS_ERR(msp->pinctrl_p)) + dev_err(&pdev->dev, "could not get MSP pinctrl\n"); + else { + msp->pinctrl_def = pinctrl_lookup_state(msp->pinctrl_p, + PINCTRL_STATE_DEFAULT); + if (IS_ERR(msp->pinctrl_def)) { + dev_err(&pdev->dev, + "could not get MSP defstate (%li)\n", + PTR_ERR(msp->pinctrl_def)); + } + msp->pinctrl_sleep = pinctrl_lookup_state(msp->pinctrl_p, + PINCTRL_STATE_SLEEP); + if (IS_ERR(msp->pinctrl_sleep)) + dev_err(&pdev->dev, + "could not get MSP idlestate (%li)\n", + PTR_ERR(msp->pinctrl_def)); + } + return 0; } diff --git a/sound/soc/ux500/ux500_msp_i2s.h b/sound/soc/ux500/ux500_msp_i2s.h index 2d9136da9865..1311c0df7628 100644 --- a/sound/soc/ux500/ux500_msp_i2s.h +++ b/sound/soc/ux500/ux500_msp_i2s.h @@ -524,14 +524,18 @@ struct ux500_msp { struct dma_chan *rx_pipeid; enum msp_state msp_state; int (*transfer) (struct ux500_msp *msp, struct i2s_message *message); - int (*plat_init) (void); - int (*plat_exit) (void); struct timer_list notify_timer; int def_elem_len; unsigned int dir_busy; int loopback_enable; u32 backup_regs[MAX_MSP_BACKUP_REGS]; unsigned int f_bitclk; + /* Pin modes */ + struct pinctrl *pinctrl_p; + struct pinctrl_state *pinctrl_def; + struct pinctrl_state *pinctrl_sleep; + /* Reference Count */ + int pinctrl_rxtx_ref; }; struct ux500_msp_dma_params { From 0af541ce47ee4e80393dda12109a1efaf757fdcd Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Thu, 26 Jul 2012 16:48:34 +0100 Subject: [PATCH 08/32] ASoC: Ux500: Enable MOP500 driver for Device Tree Here we ensure that the MOP500 audio driver will be probed during a Device Tree boot. We also parse the sound node to link together the codec, dma and the CPU-side Digital Audio Interface. Acked-by: Ola Lilja Acked-by: Linus Walleij Signed-off-by: Lee Jones --- sound/soc/ux500/mop500.c | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/sound/soc/ux500/mop500.c b/sound/soc/ux500/mop500.c index 31c4d26d0359..6840df79d798 100644 --- a/sound/soc/ux500/mop500.c +++ b/sound/soc/ux500/mop500.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -56,8 +57,35 @@ static struct snd_soc_card mop500_card = { .num_links = ARRAY_SIZE(mop500_dai_links), }; +static int __devinit mop500_of_probe(struct platform_device *pdev, + struct device_node *np) +{ + struct device_node *codec_np, *msp_np[2]; + int i; + + msp_np[0] = of_parse_phandle(np, "stericsson,cpu-dai", 0); + msp_np[1] = of_parse_phandle(np, "stericsson,cpu-dai", 1); + codec_np = of_parse_phandle(np, "stericsson,audio-codec", 0); + + if (!(msp_np[0] && msp_np[1] && codec_np)) { + dev_err(&pdev->dev, "Phandle missing or invalid\n"); + return -EINVAL; + } + + for (i = 0; i < 2; i++) { + mop500_dai_links[i].cpu_of_node = msp_np[i]; + mop500_dai_links[i].cpu_dai_name = NULL; + mop500_dai_links[i].codec_of_node = codec_np; + mop500_dai_links[i].codec_name = NULL; + } + + snd_soc_of_parse_card_name(&mop500_card, "stericsson,card-name"); + + return 0; +} static int __devinit mop500_probe(struct platform_device *pdev) { + struct device_node *np = pdev->dev.of_node; int ret; pr_debug("%s: Enter.\n", __func__); @@ -66,6 +94,12 @@ static int __devinit mop500_probe(struct platform_device *pdev) mop500_card.dev = &pdev->dev; + if (np) { + ret = mop500_of_probe(pdev, np); + if (ret) + return ret; + } + dev_dbg(&pdev->dev, "%s: Card %s: Set platform drvdata.\n", __func__, mop500_card.name); platform_set_drvdata(pdev, &mop500_card); @@ -101,10 +135,16 @@ static int __devexit mop500_remove(struct platform_device *pdev) return 0; } +static const struct of_device_id snd_soc_mop500_match[] = { + { .compatible = "stericsson,snd-soc-mop500", }, + {}, +}; + static struct platform_driver snd_soc_mop500_driver = { .driver = { .owner = THIS_MODULE, .name = "snd-soc-mop500", + .of_match_table = snd_soc_mop500_match, }, .probe = mop500_probe, .remove = __devexit_p(mop500_remove), From 49731c23bee88fd76af8cd57b915547b2175a26a Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Thu, 26 Jul 2012 17:07:26 +0100 Subject: [PATCH 09/32] ASoC: Ux500: Enable ux500 MSP driver for Device Tree Register both parts of the MSP driver from Device Tree so that they are probed when Device Tree is enabled. Also, as there is platform data involved, we ensure that there is allocated memory to place the configuration into and that the correct information is extracted from the DT binary. Acked-by: Ola Lilja Acked-by: Linus Walleij Signed-off-by: Lee Jones --- sound/soc/ux500/ux500_msp_dai.c | 6 ++++++ sound/soc/ux500/ux500_msp_i2s.c | 22 +++++++++++++++++++--- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/sound/soc/ux500/ux500_msp_dai.c b/sound/soc/ux500/ux500_msp_dai.c index 057e28ef770e..45e43b4057b0 100644 --- a/sound/soc/ux500/ux500_msp_dai.c +++ b/sound/soc/ux500/ux500_msp_dai.c @@ -830,10 +830,16 @@ static int __devexit ux500_msp_drv_remove(struct platform_device *pdev) return 0; } +static const struct of_device_id ux500_msp_i2s_match[] = { + { .compatible = "stericsson,ux500-msp-i2s", }, + {}, +}; + static struct platform_driver msp_i2s_driver = { .driver = { .name = "ux500-msp-i2s", .owner = THIS_MODULE, + .of_match_table = ux500_msp_i2s_match, }, .probe = ux500_msp_drv_probe, .remove = ux500_msp_drv_remove, diff --git a/sound/soc/ux500/ux500_msp_i2s.c b/sound/soc/ux500/ux500_msp_i2s.c index 12d7f567420d..e5c79ca42518 100644 --- a/sound/soc/ux500/ux500_msp_i2s.c +++ b/sound/soc/ux500/ux500_msp_i2s.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -683,14 +684,29 @@ int ux500_msp_i2s_init_msp(struct platform_device *pdev, { struct resource *res = NULL; struct i2s_controller *i2s_cont; + struct device_node *np = pdev->dev.of_node; struct ux500_msp *msp; - dev_dbg(&pdev->dev, "%s: Enter (name: %s, id: %d).\n", __func__, - pdev->name, platform_data->id); - *msp_p = devm_kzalloc(&pdev->dev, sizeof(struct ux500_msp), GFP_KERNEL); msp = *msp_p; + if (np) { + if (!platform_data) { + platform_data = devm_kzalloc(&pdev->dev, + sizeof(struct msp_i2s_platform_data), GFP_KERNEL); + if (!platform_data) + ret = -ENOMEM; + } + } else + if (!platform_data) + ret = -EINVAL; + + if (ret) + goto err_res; + + dev_dbg(&pdev->dev, "%s: Enter (name: %s, id: %d).\n", __func__, + pdev->name, platform_data->id); + msp->id = platform_data->id; msp->dev = &pdev->dev; msp->dma_cfg_rx = platform_data->msp_i2s_dma_rx; From db5c811d4044b5bd2ef923c7466bd2720eee0887 Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Fri, 27 Jul 2012 08:50:05 +0100 Subject: [PATCH 10/32] ASoC: codecs: Enable AB8500 CODEC for Device Tree We continue to allow the AB8500 CODEC to be registered via the AB8500 Multi Functional Device API, only this time we extract its configuration from the Device Tree binary. Acked-by: Ola Lilja Acked-by: Linus Walleij Signed-off-by: Lee Jones --- include/linux/mfd/abx500/ab8500-codec.h | 6 +- sound/soc/codecs/ab8500-codec.c | 81 +++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 2 deletions(-) diff --git a/include/linux/mfd/abx500/ab8500-codec.h b/include/linux/mfd/abx500/ab8500-codec.h index dc6529202cdd..d7079413def0 100644 --- a/include/linux/mfd/abx500/ab8500-codec.h +++ b/include/linux/mfd/abx500/ab8500-codec.h @@ -23,7 +23,8 @@ enum amic_type { /* Mic-biases */ enum amic_micbias { AMIC_MICBIAS_VAMIC1, - AMIC_MICBIAS_VAMIC2 + AMIC_MICBIAS_VAMIC2, + AMIC_MICBIAS_UNKNOWN }; /* Bias-voltage */ @@ -31,7 +32,8 @@ enum ear_cm_voltage { EAR_CMV_0_95V, EAR_CMV_1_10V, EAR_CMV_1_27V, - EAR_CMV_1_58V + EAR_CMV_1_58V, + EAR_CMV_UNKNOWN }; /* Analog microphone settings */ diff --git a/sound/soc/codecs/ab8500-codec.c b/sound/soc/codecs/ab8500-codec.c index 23b40186f9b8..07abd09e0b1d 100644 --- a/sound/soc/codecs/ab8500-codec.c +++ b/sound/soc/codecs/ab8500-codec.c @@ -34,6 +34,7 @@ #include #include #include +#include #include #include @@ -2394,9 +2395,65 @@ struct snd_soc_dai_driver ab8500_codec_dai[] = { } }; +static void ab8500_codec_of_probe(struct device *dev, struct device_node *np, + struct ab8500_codec_platform_data *codec) +{ + u32 value; + + if (of_get_property(np, "stericsson,amic1-type-single-ended", NULL)) + codec->amics.mic1_type = AMIC_TYPE_SINGLE_ENDED; + else + codec->amics.mic1_type = AMIC_TYPE_DIFFERENTIAL; + + if (of_get_property(np, "stericsson,amic2-type-single-ended", NULL)) + codec->amics.mic2_type = AMIC_TYPE_SINGLE_ENDED; + else + codec->amics.mic2_type = AMIC_TYPE_DIFFERENTIAL; + + /* Has a non-standard Vamic been requested? */ + if (of_get_property(np, "stericsson,amic1a-bias-vamic2", NULL)) + codec->amics.mic1a_micbias = AMIC_MICBIAS_VAMIC2; + else + codec->amics.mic1a_micbias = AMIC_MICBIAS_VAMIC1; + + if (of_get_property(np, "stericsson,amic1b-bias-vamic2", NULL)) + codec->amics.mic1b_micbias = AMIC_MICBIAS_VAMIC2; + else + codec->amics.mic1b_micbias = AMIC_MICBIAS_VAMIC1; + + if (of_get_property(np, "stericsson,amic2-bias-vamic1", NULL)) + codec->amics.mic2_micbias = AMIC_MICBIAS_VAMIC1; + else + codec->amics.mic2_micbias = AMIC_MICBIAS_VAMIC2; + + if (!of_property_read_u32(np, "stericsson,earpeice-cmv", &value)) { + switch (value) { + case 950 : + codec->ear_cmv = EAR_CMV_0_95V; + break; + case 1100 : + codec->ear_cmv = EAR_CMV_1_10V; + break; + case 1270 : + codec->ear_cmv = EAR_CMV_1_27V; + break; + case 1580 : + codec->ear_cmv = EAR_CMV_1_58V; + break; + default : + codec->ear_cmv = EAR_CMV_UNKNOWN; + dev_err(dev, "Unsuitable earpiece voltage found in DT\n"); + } + } else { + dev_warn(dev, "No earpiece voltage found in DT - using default\n"); + codec->ear_cmv = EAR_CMV_0_95V; + } +} + static int ab8500_codec_probe(struct snd_soc_codec *codec) { struct device *dev = codec->dev; + struct device_node *np = dev->of_node; struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(dev); struct ab8500_platform_data *pdata; struct filter_control *fc; @@ -2410,6 +2467,30 @@ static int ab8500_codec_probe(struct snd_soc_codec *codec) /* Inform SoC Core that we have our own I/O arrangements. */ codec->control_data = (void *)true; + if (np) { + if (!pdata) + pdata = devm_kzalloc(dev, + sizeof(struct ab8500_platform_data), + GFP_KERNEL); + + if (pdata && !pdata->codec) + pdata->codec + = devm_kzalloc(dev, + sizeof(struct ab8500_codec_platform_data), + GFP_KERNEL); + + if (!(pdata && pdata->codec)) + return -ENOMEM; + + ab8500_codec_of_probe(dev, np, pdata->codec); + + } else { + if (!(pdata && pdata->codec)) { + dev_err(dev, "No codec platform data or DT found\n"); + return -EINVAL; + } + } + status = ab8500_audio_setup_mics(codec, &pdata->codec->amics); if (status < 0) { pr_err("%s: Failed to setup mics (%d)!\n", __func__, status); From 2087a692a54947ef5688d096e398111a25356692 Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Tue, 21 Aug 2012 10:06:44 +0100 Subject: [PATCH 11/32] ASoC: Ux500: Minor coding layout changes Includes removal of duplicate debug print affirming entry into the probe function, an unnecessary line break of a coding line <80 chars and a white space change (unintentional tab). Acked-by: Ola Lilja Signed-off-by: Lee Jones --- sound/soc/ux500/mop500.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/sound/soc/ux500/mop500.c b/sound/soc/ux500/mop500.c index 6840df79d798..356611d9654d 100644 --- a/sound/soc/ux500/mop500.c +++ b/sound/soc/ux500/mop500.c @@ -88,8 +88,6 @@ static int __devinit mop500_probe(struct platform_device *pdev) struct device_node *np = pdev->dev.of_node; int ret; - pr_debug("%s: Enter.\n", __func__); - dev_dbg(&pdev->dev, "%s: Enter.\n", __func__); mop500_card.dev = &pdev->dev; @@ -117,8 +115,7 @@ static int __devinit mop500_probe(struct platform_device *pdev) ret = snd_soc_register_card(&mop500_card); if (ret) dev_err(&pdev->dev, - "Error: snd_soc_register_card failed (%d)!\n", - ret); + "Error: snd_soc_register_card failed (%d)!\n", ret); return ret; } @@ -131,7 +128,7 @@ static int __devexit mop500_remove(struct platform_device *pdev) snd_soc_unregister_card(mop500_card); mop500_ab8500_remove(mop500_card); - + return 0; } From bfde5afa0804b690224b9be3e44b2ce78ce6e32f Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Tue, 31 Jul 2012 11:56:51 +0100 Subject: [PATCH 12/32] Documentation: Define the MOP500 Audio Machine Driver Device Tree bindings Here we add the required documentation for the new Device Tree bindings pertaining to the MOP500 Audio Machine driver. Acked-by: Ola Lilja Signed-off-by: Lee Jones --- .../bindings/sound/ux500-mop500.txt | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/ux500-mop500.txt diff --git a/Documentation/devicetree/bindings/sound/ux500-mop500.txt b/Documentation/devicetree/bindings/sound/ux500-mop500.txt new file mode 100644 index 000000000000..48e071c96b46 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/ux500-mop500.txt @@ -0,0 +1,39 @@ +* MOP500 Audio Machine Driver + +This node is responsible for linking together all ux500 Audio Driver components. + +Required properties: + - compatible : "stericsson,snd-soc-mop500" + +Non-standard properties: + - stericsson,cpu-dai : Phandle to the CPU-side DAI + - stericsson,audio-codec : Phandle to the Audio CODEC + - stericsson,card-name : Over-ride default card name + +Example: + + sound { + compatible = "stericsson,snd-soc-mop500"; + + stericsson,cpu-dai = <&msp1 &msp3>; + stericsson,audio-codec = <&codec>; + }; + + msp1: msp@80124000 { + compatible = "stericsson,ux500-msp-i2s"; + reg = <0x80124000 0x1000>; + interrupts = <0 62 0x4>; + v-ape-supply = <&db8500_vape_reg>; + }; + + msp3: msp@80125000 { + compatible = "stericsson,ux500-msp-i2s"; + reg = <0x80125000 0x1000>; + interrupts = <0 62 0x4>; + v-ape-supply = <&db8500_vape_reg>; + }; + + codec: ab8500-codec { + compatible = "stericsson,ab8500-codec"; + stericsson,earpeice-cmv = <950>; /* Units in mV. */ + }; From b4cad7af6665743647f28119b689e1552326d4da Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Tue, 31 Jul 2012 11:59:21 +0100 Subject: [PATCH 13/32] Documentation: Define the MSP Driver Device Tree bindings Here we add the required documentation for the new Device Tree bindings pertaining to the MSP CPU-side DAI Driver. Acked-by: Ola Lilja Signed-off-by: Lee Jones --- .../devicetree/bindings/sound/ux500-msp.txt | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/ux500-msp.txt diff --git a/Documentation/devicetree/bindings/sound/ux500-msp.txt b/Documentation/devicetree/bindings/sound/ux500-msp.txt new file mode 100644 index 000000000000..99acd9c774e1 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/ux500-msp.txt @@ -0,0 +1,43 @@ +* ux500 MSP (CPU-side Digital Audio Interface) + +Required properties: + - compatible :"stericsson,ux500-msp-i2s" + - reg : Physical base address and length of the device's registers. + +Optional properties: + - interrupts : The interrupt output from the device. + - interrupt-parent : The parent interrupt controller. + - -supply : Phandle to the regulator supply + +Example: + + sound { + compatible = "stericsson,snd-soc-mop500"; + + stericsson,platform-pcm-dma = <&pcm>; + stericsson,cpu-dai = <&msp1 &msp3>; + stericsson,audio-codec = <&codec>; + }; + + pcm: ux500-pcm { + compatible = "stericsson,ux500-pcm"; + }; + + msp1: msp@80124000 { + compatible = "stericsson,ux500-msp-i2s"; + reg = <0x80124000 0x1000>; + interrupts = <0 62 0x4>; + v-ape-supply = <&db8500_vape_reg>; + }; + + msp3: msp@80125000 { + compatible = "stericsson,ux500-msp-i2s"; + reg = <0x80125000 0x1000>; + interrupts = <0 62 0x4>; + v-ape-supply = <&db8500_vape_reg>; + }; + + codec: ab8500-codec { + compatible = "stericsson,ab8500-codec"; + stericsson,earpeice-cmv = <950>; /* Units in mV. */ + }; From 8fed54aec8fa5bc6ebfee95454a2cb33101ad917 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 22 Sep 2012 18:32:08 -0400 Subject: [PATCH 14/32] ASoC: wm2000: Convert to devm_regmap_init_i2c() Signed-off-by: Mark Brown --- sound/soc/codecs/wm2000.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/sound/soc/codecs/wm2000.c b/sound/soc/codecs/wm2000.c index 89cd6fcad015..6675477a63cb 100644 --- a/sound/soc/codecs/wm2000.c +++ b/sound/soc/codecs/wm2000.c @@ -760,7 +760,7 @@ static int __devinit wm2000_i2c_probe(struct i2c_client *i2c, dev_set_drvdata(&i2c->dev, wm2000); - wm2000->regmap = regmap_init_i2c(i2c, &wm2000_regmap); + wm2000->regmap = devm_regmap_init_i2c(i2c, &wm2000_regmap); if (IS_ERR(wm2000->regmap)) { ret = PTR_ERR(wm2000->regmap); dev_err(&i2c->dev, "Failed to allocate register map: %d\n", @@ -777,7 +777,7 @@ static int __devinit wm2000_i2c_probe(struct i2c_client *i2c, if (id != 0x2000) { dev_err(&i2c->dev, "Device is not a WM2000 - ID %x\n", id); ret = -ENODEV; - goto out_regmap_exit; + goto out; } reg = wm2000_read(i2c, WM2000_REG_REVISON); @@ -796,7 +796,7 @@ static int __devinit wm2000_i2c_probe(struct i2c_client *i2c, ret = request_firmware(&fw, filename, &i2c->dev); if (ret != 0) { dev_err(&i2c->dev, "Failed to acquire ANC data: %d\n", ret); - goto out_regmap_exit; + goto out; } /* Pre-cook the concatenation of the register address onto the image */ @@ -807,7 +807,7 @@ static int __devinit wm2000_i2c_probe(struct i2c_client *i2c, if (wm2000->anc_download == NULL) { dev_err(&i2c->dev, "Out of memory\n"); ret = -ENOMEM; - goto out_regmap_exit; + goto out; } wm2000->anc_download[0] = 0x80; @@ -825,8 +825,6 @@ static int __devinit wm2000_i2c_probe(struct i2c_client *i2c, if (!ret) goto out; -out_regmap_exit: - regmap_exit(wm2000->regmap); out: release_firmware(fw); return ret; @@ -834,10 +832,7 @@ out: static __devexit int wm2000_i2c_remove(struct i2c_client *i2c) { - struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev); - snd_soc_unregister_codec(&i2c->dev); - regmap_exit(wm2000->regmap); return 0; } From a89be93c28cd656d1c3c49fe627666b3bbecd45a Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 22 Sep 2012 18:33:23 -0400 Subject: [PATCH 15/32] ASoC: wm2000: Add regulator support Signed-off-by: Mark Brown --- sound/soc/codecs/wm2000.c | 53 ++++++++++++++++++++++++++++++++++----- 1 file changed, 47 insertions(+), 6 deletions(-) diff --git a/sound/soc/codecs/wm2000.c b/sound/soc/codecs/wm2000.c index 6675477a63cb..b723e910fcdc 100644 --- a/sound/soc/codecs/wm2000.c +++ b/sound/soc/codecs/wm2000.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -43,6 +44,14 @@ #include "wm2000.h" +#define WM2000_NUM_SUPPLIES 3 + +static const char *wm2000_supplies[WM2000_NUM_SUPPLIES] = { + "SPKVDD", + "DBVDD", + "DCVDD", +}; + enum wm2000_anc_mode { ANC_ACTIVE = 0, ANC_BYPASS = 1, @@ -54,6 +63,8 @@ struct wm2000_priv { struct i2c_client *i2c; struct regmap *regmap; + struct regulator_bulk_data supplies[WM2000_NUM_SUPPLIES]; + enum wm2000_anc_mode anc_mode; unsigned int anc_active:1; @@ -126,6 +137,12 @@ static int wm2000_power_up(struct i2c_client *i2c, int analogue) dev_dbg(&i2c->dev, "Beginning power up\n"); + ret = regulator_bulk_enable(WM2000_NUM_SUPPLIES, wm2000->supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + if (!wm2000->mclk_div) { dev_dbg(&i2c->dev, "Disabling MCLK divider\n"); wm2000_write(i2c, WM2000_REG_SYS_CTL2, @@ -143,12 +160,14 @@ static int wm2000_power_up(struct i2c_client *i2c, int analogue) if (!wm2000_poll_bit(i2c, WM2000_REG_ANC_STAT, WM2000_ANC_ENG_IDLE)) { dev_err(&i2c->dev, "ANC engine failed to reset\n"); + regulator_bulk_disable(WM2000_NUM_SUPPLIES, wm2000->supplies); return -ETIMEDOUT; } if (!wm2000_poll_bit(i2c, WM2000_REG_SYS_STATUS, WM2000_STATUS_BOOT_COMPLETE)) { dev_err(&i2c->dev, "ANC engine failed to initialise\n"); + regulator_bulk_disable(WM2000_NUM_SUPPLIES, wm2000->supplies); return -ETIMEDOUT; } @@ -163,11 +182,13 @@ static int wm2000_power_up(struct i2c_client *i2c, int analogue) wm2000->anc_download_size); if (ret < 0) { dev_err(&i2c->dev, "i2c_transfer() failed: %d\n", ret); + regulator_bulk_disable(WM2000_NUM_SUPPLIES, wm2000->supplies); return ret; } if (ret != wm2000->anc_download_size) { dev_err(&i2c->dev, "i2c_transfer() failed, %d != %d\n", ret, wm2000->anc_download_size); + regulator_bulk_disable(WM2000_NUM_SUPPLIES, wm2000->supplies); return -EIO; } @@ -201,6 +222,7 @@ static int wm2000_power_up(struct i2c_client *i2c, int analogue) if (!wm2000_poll_bit(i2c, WM2000_REG_SYS_STATUS, WM2000_STATUS_MOUSE_ACTIVE)) { dev_err(&i2c->dev, "Timed out waiting for device\n"); + regulator_bulk_disable(WM2000_NUM_SUPPLIES, wm2000->supplies); return -ETIMEDOUT; } @@ -238,6 +260,8 @@ static int wm2000_power_down(struct i2c_client *i2c, int analogue) return -ETIMEDOUT; } + regulator_bulk_disable(WM2000_NUM_SUPPLIES, wm2000->supplies); + dev_dbg(&i2c->dev, "powered off\n"); wm2000->anc_mode = ANC_OFF; @@ -747,7 +771,7 @@ static int __devinit wm2000_i2c_probe(struct i2c_client *i2c, struct wm2000_platform_data *pdata; const char *filename; const struct firmware *fw = NULL; - int ret; + int ret, i; int reg; u16 id; @@ -768,6 +792,22 @@ static int __devinit wm2000_i2c_probe(struct i2c_client *i2c, goto out; } + for (i = 0; i < WM2000_NUM_SUPPLIES; i++) + wm2000->supplies[i].supply = wm2000_supplies[i]; + + ret = devm_regulator_bulk_get(&i2c->dev, WM2000_NUM_SUPPLIES, + wm2000->supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to get supplies: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(WM2000_NUM_SUPPLIES, wm2000->supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + /* Verify that this is a WM2000 */ reg = wm2000_read(i2c, WM2000_REG_ID1); id = reg << 8; @@ -777,7 +817,7 @@ static int __devinit wm2000_i2c_probe(struct i2c_client *i2c, if (id != 0x2000) { dev_err(&i2c->dev, "Device is not a WM2000 - ID %x\n", id); ret = -ENODEV; - goto out; + goto err_supplies; } reg = wm2000_read(i2c, WM2000_REG_REVISON); @@ -796,7 +836,7 @@ static int __devinit wm2000_i2c_probe(struct i2c_client *i2c, ret = request_firmware(&fw, filename, &i2c->dev); if (ret != 0) { dev_err(&i2c->dev, "Failed to acquire ANC data: %d\n", ret); - goto out; + goto err_supplies; } /* Pre-cook the concatenation of the register address onto the image */ @@ -807,7 +847,7 @@ static int __devinit wm2000_i2c_probe(struct i2c_client *i2c, if (wm2000->anc_download == NULL) { dev_err(&i2c->dev, "Out of memory\n"); ret = -ENOMEM; - goto out; + goto err_supplies; } wm2000->anc_download[0] = 0x80; @@ -822,8 +862,9 @@ static int __devinit wm2000_i2c_probe(struct i2c_client *i2c, wm2000_reset(wm2000); ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_wm2000, NULL, 0); - if (!ret) - goto out; + +err_supplies: + regulator_bulk_disable(WM2000_NUM_SUPPLIES, wm2000->supplies); out: release_firmware(fw); From f76fe059dd5034c55c560d9e0005e19481843726 Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Tue, 18 Sep 2012 13:03:54 -0300 Subject: [PATCH 16/32] ASoC: cs4270: Remove mono support According to cs4270 datasheet, there is no reference to mono mode. Signed-off-by: Fabio Estevam Acked-by: Timur Tabi Signed-off-by: Mark Brown --- sound/soc/codecs/cs4270.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/cs4270.c b/sound/soc/codecs/cs4270.c index 815b53bc2d27..8e4779812b96 100644 --- a/sound/soc/codecs/cs4270.c +++ b/sound/soc/codecs/cs4270.c @@ -459,7 +459,7 @@ static struct snd_soc_dai_driver cs4270_dai = { .name = "cs4270-hifi", .playback = { .stream_name = "Playback", - .channels_min = 1, + .channels_min = 2, .channels_max = 2, .rates = SNDRV_PCM_RATE_CONTINUOUS, .rate_min = 4000, @@ -468,7 +468,7 @@ static struct snd_soc_dai_driver cs4270_dai = { }, .capture = { .stream_name = "Capture", - .channels_min = 1, + .channels_min = 2, .channels_max = 2, .rates = SNDRV_PCM_RATE_CONTINUOUS, .rate_min = 4000, From c05b84d14b230a96e3f782c9d87ab18d82df8bd2 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 7 Sep 2012 12:57:11 +0800 Subject: [PATCH 17/32] ASoC: dapm: Allow regulators to bypass as well as disable when idle Allow regulators managed via DAPM to make use of the bypass support that has recently been added to the regulator API by setting a flag SND_SOC_DAPM_REGULATOR_BYPASS. When this flag is set the regulator will be put into bypass mode before being disabled, allowing the regulator to fall into bypass mode if it can't be disabled due to other users. Signed-off-by: Mark Brown --- include/sound/soc-dapm.h | 3 +++ sound/soc/soc-dapm.c | 23 +++++++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h index c96bf5ae80a6..e1ef63d4a5c4 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -320,6 +320,9 @@ struct device; #define SND_SOC_DAPM_EVENT_OFF(e) \ (e & (SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD)) +/* regulator widget flags */ +#define SND_SOC_DAPM_REGULATOR_BYPASS 0x1 /* bypass when disabled */ + struct snd_soc_dapm_widget; enum snd_soc_dapm_type; struct snd_soc_dapm_path; diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 873e6e76ee87..d0a4be38dc0f 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -1017,10 +1017,29 @@ EXPORT_SYMBOL_GPL(dapm_reg_event); int dapm_regulator_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { - if (SND_SOC_DAPM_EVENT_ON(event)) + int ret; + + if (SND_SOC_DAPM_EVENT_ON(event)) { + if (w->invert & SND_SOC_DAPM_REGULATOR_BYPASS) { + ret = regulator_allow_bypass(w->regulator, true); + if (ret != 0) + dev_warn(w->dapm->dev, + "Failed to bypass %s: %d\n", + w->name, ret); + } + return regulator_enable(w->regulator); - else + } else { + if (w->invert & SND_SOC_DAPM_REGULATOR_BYPASS) { + ret = regulator_allow_bypass(w->regulator, false); + if (ret != 0) + dev_warn(w->dapm->dev, + "Failed to unbypass %s: %d\n", + w->name, ret); + } + return regulator_disable_deferred(w->regulator, w->shift); + } } EXPORT_SYMBOL_GPL(dapm_regulator_event); From bb09d97855b0dc906eb7e409f56f9e0fa2480b47 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 7 Sep 2012 12:58:43 +0800 Subject: [PATCH 18/32] ASoC: wm5102: Enable bypass mode for MICVDD Signed-off-by: Mark Brown --- sound/soc/codecs/wm5102.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/wm5102.c b/sound/soc/codecs/wm5102.c index 4a2db4e10885..1722b586bdba 100644 --- a/sound/soc/codecs/wm5102.c +++ b/sound/soc/codecs/wm5102.c @@ -308,7 +308,7 @@ SND_SOC_DAPM_SUPPLY("ASYNCOPCLK", ARIZONA_OUTPUT_ASYNC_CLOCK, SND_SOC_DAPM_REGULATOR_SUPPLY("DBVDD2", 0, 0), SND_SOC_DAPM_REGULATOR_SUPPLY("DBVDD3", 0, 0), SND_SOC_DAPM_REGULATOR_SUPPLY("CPVDD", 20, 0), -SND_SOC_DAPM_REGULATOR_SUPPLY("MICVDD", 0, 0), +SND_SOC_DAPM_REGULATOR_SUPPLY("MICVDD", 0, SND_SOC_DAPM_REGULATOR_BYPASS), SND_SOC_DAPM_REGULATOR_SUPPLY("SPKVDDL", 0, 0), SND_SOC_DAPM_REGULATOR_SUPPLY("SPKVDDR", 0, 0), From 76dbd2af787e6ceafab9e8618a5c67e78a2de4a7 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 7 Sep 2012 12:58:54 +0800 Subject: [PATCH 19/32] ASoC: wm5110: Enable bypass mode for MICVDD Signed-off-by: Mark Brown --- sound/soc/codecs/wm5110.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/wm5110.c b/sound/soc/codecs/wm5110.c index bf47914234b3..259f251400d5 100644 --- a/sound/soc/codecs/wm5110.c +++ b/sound/soc/codecs/wm5110.c @@ -313,7 +313,7 @@ SND_SOC_DAPM_SUPPLY("ASYNCOPCLK", ARIZONA_OUTPUT_ASYNC_CLOCK, SND_SOC_DAPM_REGULATOR_SUPPLY("DBVDD2", 0, 0), SND_SOC_DAPM_REGULATOR_SUPPLY("DBVDD3", 0, 0), SND_SOC_DAPM_REGULATOR_SUPPLY("CPVDD", 20, 0), -SND_SOC_DAPM_REGULATOR_SUPPLY("MICVDD", 0, 0), +SND_SOC_DAPM_REGULATOR_SUPPLY("MICVDD", 0, SND_SOC_DAPM_REGULATOR_BYPASS), SND_SOC_DAPM_REGULATOR_SUPPLY("SPKVDDL", 0, 0), SND_SOC_DAPM_REGULATOR_SUPPLY("SPKVDDR", 0, 0), From fff00cbca13ab303b3995353d22c47e6b0f68fd8 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 25 Sep 2012 16:35:26 +0100 Subject: [PATCH 20/32] ASoC: wm0010: Allow slow GPIO for reset We never set the GPIO from atomic context so there's no reason why we can't support a GPIO that needs to sleep when configuring. Signed-off-by: Mark Brown --- sound/soc/codecs/wm0010.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sound/soc/codecs/wm0010.c b/sound/soc/codecs/wm0010.c index f8d6c31db870..4722acfb82a8 100644 --- a/sound/soc/codecs/wm0010.c +++ b/sound/soc/codecs/wm0010.c @@ -168,7 +168,8 @@ static void wm0010_halt(struct snd_soc_codec *codec) case WM0010_STAGE2: case WM0010_FIRMWARE: /* Remember to put chip back into reset */ - gpio_set_value(wm0010->gpio_reset, wm0010->gpio_reset_value); + gpio_set_value_cansleep(wm0010->gpio_reset, + wm0010->gpio_reset_value); /* Disable the regulators */ regulator_disable(wm0010->dbvdd); regulator_bulk_disable(ARRAY_SIZE(wm0010->core_supplies), @@ -387,7 +388,7 @@ static int wm0010_boot(struct snd_soc_codec *codec) } /* Release reset */ - gpio_set_value(wm0010->gpio_reset, !wm0010->gpio_reset_value); + gpio_set_value_cansleep(wm0010->gpio_reset, !wm0010->gpio_reset_value); spin_lock_irqsave(&wm0010->irq_lock, flags); wm0010->state = WM0010_OUT_OF_RESET; spin_unlock_irqrestore(&wm0010->irq_lock, flags); @@ -918,7 +919,8 @@ static int __devexit wm0010_spi_remove(struct spi_device *spi) if (wm0010->gpio_reset) { /* Remember to put chip back into reset */ - gpio_set_value(wm0010->gpio_reset, wm0010->gpio_reset_value); + gpio_set_value_cansleep(wm0010->gpio_reset, + wm0010->gpio_reset_value); } if (wm0010->irq) From 5afe5bfe243d649aa5118c74317cbcbe85a04cb9 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 25 Sep 2012 16:36:56 +0100 Subject: [PATCH 21/32] ASoC: wm0010: Don't check if reset GPIO is defined when removing We will fail to probe without one. Signed-off-by: Mark Brown --- sound/soc/codecs/wm0010.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/sound/soc/codecs/wm0010.c b/sound/soc/codecs/wm0010.c index 4722acfb82a8..780110a15c97 100644 --- a/sound/soc/codecs/wm0010.c +++ b/sound/soc/codecs/wm0010.c @@ -917,11 +917,8 @@ static int __devexit wm0010_spi_remove(struct spi_device *spi) snd_soc_unregister_codec(&spi->dev); - if (wm0010->gpio_reset) { - /* Remember to put chip back into reset */ - gpio_set_value_cansleep(wm0010->gpio_reset, - wm0010->gpio_reset_value); - } + gpio_set_value_cansleep(wm0010->gpio_reset, + wm0010->gpio_reset_value); if (wm0010->irq) free_irq(wm0010->irq, wm0010); From 9bb684442cf48c0e0736f5902f112c4f39ee3677 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 25 Sep 2012 19:04:25 +0100 Subject: [PATCH 22/32] ASoC: wm0010: Initialise chip state before we register the interrupt The interrupt handler uses the chip state. Signed-off-by: Mark Brown --- sound/soc/codecs/wm0010.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/sound/soc/codecs/wm0010.c b/sound/soc/codecs/wm0010.c index 780110a15c97..99afc003a084 100644 --- a/sound/soc/codecs/wm0010.c +++ b/sound/soc/codecs/wm0010.c @@ -810,7 +810,6 @@ static int wm0010_probe(struct snd_soc_codec *codec) static int __devinit wm0010_spi_probe(struct spi_device *spi) { - unsigned long flags; unsigned long gpio_flags; int ret; int trigger; @@ -877,6 +876,8 @@ static int __devinit wm0010_spi_probe(struct spi_device *spi) return -EINVAL; } + wm0010->state = WM0010_POWER_OFF; + irq = spi->irq; if (wm0010->pdata.irq_flags) trigger = wm0010->pdata.irq_flags; @@ -898,10 +899,6 @@ static int __devinit wm0010_spi_probe(struct spi_device *spi) else wm0010->board_max_spi_speed = 0; - spin_lock_irqsave(&wm0010->irq_lock, flags); - wm0010->state = WM0010_POWER_OFF; - spin_unlock_irqrestore(&wm0010->irq_lock, flags); - ret = snd_soc_register_codec(&spi->dev, &soc_codec_dev_wm0010, wm0010_dai, ARRAY_SIZE(wm0010_dai)); From c922cc4c1cc3c0253adec36bb7088eab7c2269c5 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 26 Sep 2012 16:43:44 +0100 Subject: [PATCH 23/32] ASoC: arizona: Add more DSP options for mixer input muxes Signed-off-by: Mark Brown --- sound/soc/codecs/arizona.c | 36 ++++++++++++++++++++++++++++++++++++ sound/soc/codecs/arizona.h | 2 +- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/sound/soc/codecs/arizona.c b/sound/soc/codecs/arizona.c index c167c896eaee..5764960087bc 100644 --- a/sound/soc/codecs/arizona.c +++ b/sound/soc/codecs/arizona.c @@ -119,6 +119,24 @@ const char *arizona_mixer_texts[ARIZONA_NUM_MIXER_INPUTS] = { "DSP1.4", "DSP1.5", "DSP1.6", + "DSP2.1", + "DSP2.2", + "DSP2.3", + "DSP2.4", + "DSP2.5", + "DSP2.6", + "DSP3.1", + "DSP3.2", + "DSP3.3", + "DSP3.4", + "DSP3.5", + "DSP3.6", + "DSP4.1", + "DSP4.2", + "DSP4.3", + "DSP4.4", + "DSP4.5", + "DSP4.6", "ASRC1L", "ASRC1R", "ASRC2L", @@ -180,6 +198,24 @@ int arizona_mixer_values[ARIZONA_NUM_MIXER_INPUTS] = { 0x6b, 0x6c, 0x6d, + 0x70, /* DSP2.1 */ + 0x71, + 0x72, + 0x73, + 0x74, + 0x75, + 0x78, /* DSP3.1 */ + 0x79, + 0x7a, + 0x7b, + 0x7c, + 0x7d, + 0x80, /* DSP4.1 */ + 0x81, + 0x82, + 0x83, + 0x84, + 0x85, 0x90, /* ASRC1L */ 0x91, 0x92, diff --git a/sound/soc/codecs/arizona.h b/sound/soc/codecs/arizona.h index eb66b52777c9..36ec64946120 100644 --- a/sound/soc/codecs/arizona.h +++ b/sound/soc/codecs/arizona.h @@ -61,7 +61,7 @@ struct arizona_priv { struct arizona_dai_priv dai[ARIZONA_MAX_DAI]; }; -#define ARIZONA_NUM_MIXER_INPUTS 57 +#define ARIZONA_NUM_MIXER_INPUTS 75 extern const unsigned int arizona_mixer_tlv[]; extern const char *arizona_mixer_texts[ARIZONA_NUM_MIXER_INPUTS]; From aeaeee1a1f054610299e614749d2c5a31cec3c8d Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 26 Sep 2012 17:50:02 +0100 Subject: [PATCH 24/32] ASoC: arizona: Add more clock rates Some devices support additional clock rates. Signed-off-by: Mark Brown --- sound/soc/codecs/arizona.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sound/soc/codecs/arizona.c b/sound/soc/codecs/arizona.c index 5764960087bc..c03b65af3059 100644 --- a/sound/soc/codecs/arizona.c +++ b/sound/soc/codecs/arizona.c @@ -270,6 +270,9 @@ static unsigned int arizona_sysclk_48k_rates[] = { 12288000, 22579200, 49152000, + 73728000, + 98304000, + 147456000, }; static unsigned int arizona_sysclk_44k1_rates[] = { @@ -277,6 +280,9 @@ static unsigned int arizona_sysclk_44k1_rates[] = { 11289600, 24576000, 45158400, + 67737600, + 90316800, + 135475200, }; static int arizona_set_opclk(struct snd_soc_codec *codec, unsigned int clk, From 35d3889389f658dd30eefd650fc774c9b00871e5 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sun, 26 Aug 2012 13:50:48 -0700 Subject: [PATCH 25/32] ASoC: wm5110: Rename EPOUT to HPOUT3 The third output on WM5110 is a general purpose headphone output which can be used to drive an earpice rather than a dedicated earpiece driver. Signed-off-by: Mark Brown --- sound/soc/codecs/wm5110.c | 45 ++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/sound/soc/codecs/wm5110.c b/sound/soc/codecs/wm5110.c index 259f251400d5..9eabfb62fcfa 100644 --- a/sound/soc/codecs/wm5110.c +++ b/sound/soc/codecs/wm5110.c @@ -153,6 +153,15 @@ SOC_ENUM("LHPF2 Mode", arizona_lhpf2_mode), SOC_ENUM("LHPF3 Mode", arizona_lhpf3_mode), SOC_ENUM("LHPF4 Mode", arizona_lhpf4_mode), +ARIZONA_MIXER_CONTROLS("DSP1L", ARIZONA_DSP1LMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("DSP1R", ARIZONA_DSP1RMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("DSP2L", ARIZONA_DSP2LMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("DSP2R", ARIZONA_DSP2RMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("DSP3L", ARIZONA_DSP3LMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("DSP3R", ARIZONA_DSP3RMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("DSP4L", ARIZONA_DSP4LMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("DSP5R", ARIZONA_DSP4RMIX_INPUT_1_SOURCE), + ARIZONA_MIXER_CONTROLS("Mic", ARIZONA_MICMIX_INPUT_1_SOURCE), ARIZONA_MIXER_CONTROLS("Noise", ARIZONA_NOISEMIX_INPUT_1_SOURCE), @@ -163,7 +172,8 @@ ARIZONA_MIXER_CONTROLS("HPOUT1L", ARIZONA_OUT1LMIX_INPUT_1_SOURCE), ARIZONA_MIXER_CONTROLS("HPOUT1R", ARIZONA_OUT1RMIX_INPUT_1_SOURCE), ARIZONA_MIXER_CONTROLS("HPOUT2L", ARIZONA_OUT2LMIX_INPUT_1_SOURCE), ARIZONA_MIXER_CONTROLS("HPOUT2R", ARIZONA_OUT2RMIX_INPUT_1_SOURCE), -ARIZONA_MIXER_CONTROLS("EPOUT", ARIZONA_OUT3LMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("HPOUT3L", ARIZONA_OUT3LMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("HPOUT3R", ARIZONA_OUT3RMIX_INPUT_1_SOURCE), ARIZONA_MIXER_CONTROLS("SPKOUTL", ARIZONA_OUT4LMIX_INPUT_1_SOURCE), ARIZONA_MIXER_CONTROLS("SPKOUTR", ARIZONA_OUT4RMIX_INPUT_1_SOURCE), ARIZONA_MIXER_CONTROLS("SPKDAT1L", ARIZONA_OUT5LMIX_INPUT_1_SOURCE), @@ -175,7 +185,7 @@ SOC_SINGLE("HPOUT1 High Performance Switch", ARIZONA_OUTPUT_PATH_CONFIG_1L, ARIZONA_OUT1_OSR_SHIFT, 1, 0), SOC_SINGLE("OUT2 High Performance Switch", ARIZONA_OUTPUT_PATH_CONFIG_2L, ARIZONA_OUT2_OSR_SHIFT, 1, 0), -SOC_SINGLE("EPOUT High Performance Switch", ARIZONA_OUTPUT_PATH_CONFIG_3L, +SOC_SINGLE("OUT3 High Performance Switch", ARIZONA_OUTPUT_PATH_CONFIG_3L, ARIZONA_OUT3_OSR_SHIFT, 1, 0), SOC_SINGLE("Speaker High Performance Switch", ARIZONA_OUTPUT_PATH_CONFIG_4L, ARIZONA_OUT4_OSR_SHIFT, 1, 0), @@ -188,8 +198,8 @@ SOC_DOUBLE_R("HPOUT1 Digital Switch", ARIZONA_DAC_DIGITAL_VOLUME_1L, ARIZONA_DAC_DIGITAL_VOLUME_1R, ARIZONA_OUT1L_MUTE_SHIFT, 1, 1), SOC_DOUBLE_R("OUT2 Digital Switch", ARIZONA_DAC_DIGITAL_VOLUME_2L, ARIZONA_DAC_DIGITAL_VOLUME_2R, ARIZONA_OUT2L_MUTE_SHIFT, 1, 1), -SOC_SINGLE("EPOUT Digital Switch", ARIZONA_DAC_DIGITAL_VOLUME_3L, - ARIZONA_OUT3L_MUTE_SHIFT, 1, 1), +SOC_DOUBLE_R("OUT3 Digital Switch", ARIZONA_DAC_DIGITAL_VOLUME_3L, + ARIZONA_DAC_DIGITAL_VOLUME_3R, ARIZONA_OUT3L_MUTE_SHIFT, 1, 1), SOC_DOUBLE_R("Speaker Digital Switch", ARIZONA_DAC_DIGITAL_VOLUME_4L, ARIZONA_DAC_DIGITAL_VOLUME_4R, ARIZONA_OUT4L_MUTE_SHIFT, 1, 1), SOC_DOUBLE_R("SPKDAT1 Digital Switch", ARIZONA_DAC_DIGITAL_VOLUME_5L, @@ -203,8 +213,9 @@ SOC_DOUBLE_R_TLV("HPOUT1 Digital Volume", ARIZONA_DAC_DIGITAL_VOLUME_1L, SOC_DOUBLE_R_TLV("OUT2 Digital Volume", ARIZONA_DAC_DIGITAL_VOLUME_2L, ARIZONA_DAC_DIGITAL_VOLUME_2R, ARIZONA_OUT2L_VOL_SHIFT, 0xbf, 0, digital_tlv), -SOC_SINGLE_TLV("EPOUT Digital Volume", ARIZONA_DAC_DIGITAL_VOLUME_3L, - ARIZONA_OUT3L_VOL_SHIFT, 0xbf, 0, digital_tlv), +SOC_DOUBLE_R_TLV("OUT3 Digital Volume", ARIZONA_DAC_DIGITAL_VOLUME_3L, + ARIZONA_DAC_DIGITAL_VOLUME_3R, ARIZONA_OUT3L_VOL_SHIFT, + 0xbf, 0, digital_tlv), SOC_DOUBLE_R_TLV("Speaker Digital Volume", ARIZONA_DAC_DIGITAL_VOLUME_4L, ARIZONA_DAC_DIGITAL_VOLUME_4R, ARIZONA_OUT4L_VOL_SHIFT, 0xbf, 0, digital_tlv), @@ -223,8 +234,9 @@ SOC_DOUBLE_R_RANGE_TLV("OUT2 Volume", ARIZONA_OUTPUT_PATH_CONFIG_2L, ARIZONA_OUTPUT_PATH_CONFIG_2R, ARIZONA_OUT2L_PGA_VOL_SHIFT, 0x34, 0x40, 0, ana_tlv), -SOC_SINGLE_RANGE_TLV("EPOUT Volume", ARIZONA_OUTPUT_PATH_CONFIG_3L, - ARIZONA_OUT3L_PGA_VOL_SHIFT, 0x34, 0x40, 0, ana_tlv), +SOC_DOUBLE_R_RANGE_TLV("OUT3 Volume", ARIZONA_OUTPUT_PATH_CONFIG_3L, + ARIZONA_OUTPUT_PATH_CONFIG_3R, + ARIZONA_OUT3L_PGA_VOL_SHIFT, 0x34, 0x40, 0, ana_tlv), SOC_DOUBLE("SPKDAT1 Switch", ARIZONA_PDM_SPK1_CTRL_1, ARIZONA_SPK1L_MUTE_SHIFT, ARIZONA_SPK1R_MUTE_SHIFT, 1, 1), @@ -272,7 +284,8 @@ ARIZONA_MIXER_ENUMS(OUT1L, ARIZONA_OUT1LMIX_INPUT_1_SOURCE); ARIZONA_MIXER_ENUMS(OUT1R, ARIZONA_OUT1RMIX_INPUT_1_SOURCE); ARIZONA_MIXER_ENUMS(OUT2L, ARIZONA_OUT2LMIX_INPUT_1_SOURCE); ARIZONA_MIXER_ENUMS(OUT2R, ARIZONA_OUT2RMIX_INPUT_1_SOURCE); -ARIZONA_MIXER_ENUMS(OUT3, ARIZONA_OUT3LMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(OUT3L, ARIZONA_OUT3LMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(OUT3R, ARIZONA_OUT3RMIX_INPUT_1_SOURCE); ARIZONA_MIXER_ENUMS(SPKOUTL, ARIZONA_OUT4LMIX_INPUT_1_SOURCE); ARIZONA_MIXER_ENUMS(SPKOUTR, ARIZONA_OUT4RMIX_INPUT_1_SOURCE); ARIZONA_MIXER_ENUMS(SPKDAT1L, ARIZONA_OUT5LMIX_INPUT_1_SOURCE); @@ -522,7 +535,8 @@ ARIZONA_MIXER_WIDGETS(OUT1L, "HPOUT1L"), ARIZONA_MIXER_WIDGETS(OUT1R, "HPOUT1R"), ARIZONA_MIXER_WIDGETS(OUT2L, "HPOUT2L"), ARIZONA_MIXER_WIDGETS(OUT2R, "HPOUT2R"), -ARIZONA_MIXER_WIDGETS(OUT3, "EPOUT"), +ARIZONA_MIXER_WIDGETS(OUT3L, "HPOUT3L"), +ARIZONA_MIXER_WIDGETS(OUT3R, "HPOUT3R"), ARIZONA_MIXER_WIDGETS(SPKOUTL, "SPKOUTL"), ARIZONA_MIXER_WIDGETS(SPKOUTR, "SPKOUTR"), ARIZONA_MIXER_WIDGETS(SPKDAT1L, "SPKDAT1L"), @@ -554,8 +568,8 @@ SND_SOC_DAPM_OUTPUT("HPOUT1L"), SND_SOC_DAPM_OUTPUT("HPOUT1R"), SND_SOC_DAPM_OUTPUT("HPOUT2L"), SND_SOC_DAPM_OUTPUT("HPOUT2R"), -SND_SOC_DAPM_OUTPUT("EPOUTN"), -SND_SOC_DAPM_OUTPUT("EPOUTP"), +SND_SOC_DAPM_OUTPUT("HPOUT3L"), +SND_SOC_DAPM_OUTPUT("HPOUT3R"), SND_SOC_DAPM_OUTPUT("SPKOUTLN"), SND_SOC_DAPM_OUTPUT("SPKOUTLP"), SND_SOC_DAPM_OUTPUT("SPKOUTRN"), @@ -701,7 +715,8 @@ static const struct snd_soc_dapm_route wm5110_dapm_routes[] = { ARIZONA_MIXER_ROUTES("OUT1R", "HPOUT1R"), ARIZONA_MIXER_ROUTES("OUT2L", "HPOUT2L"), ARIZONA_MIXER_ROUTES("OUT2R", "HPOUT2R"), - ARIZONA_MIXER_ROUTES("OUT3L", "EPOUT"), + ARIZONA_MIXER_ROUTES("OUT3L", "HPOUT3L"), + ARIZONA_MIXER_ROUTES("OUT3R", "HPOUT3R"), ARIZONA_MIXER_ROUTES("OUT4L", "SPKOUTL"), ARIZONA_MIXER_ROUTES("OUT4R", "SPKOUTR"), @@ -754,8 +769,8 @@ static const struct snd_soc_dapm_route wm5110_dapm_routes[] = { { "HPOUT2L", NULL, "OUT2L" }, { "HPOUT2R", NULL, "OUT2R" }, - { "EPOUTN", NULL, "OUT3L" }, - { "EPOUTP", NULL, "OUT3L" }, + { "HPOUT3L", NULL, "OUT3L" }, + { "HPOUT3R", NULL, "OUT3L" }, { "SPKOUTLN", NULL, "OUT4L" }, { "SPKOUTLP", NULL, "OUT4L" }, From dac8f1c422a77ce4809433c18e359fff1e0df39e Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sun, 26 Aug 2012 13:51:27 -0700 Subject: [PATCH 26/32] ASoC: wm5110: Add AEC loopback support Signed-off-by: Mark Brown --- sound/soc/codecs/wm5110.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/sound/soc/codecs/wm5110.c b/sound/soc/codecs/wm5110.c index 9eabfb62fcfa..83177aa83625 100644 --- a/sound/soc/codecs/wm5110.c +++ b/sound/soc/codecs/wm5110.c @@ -313,6 +313,26 @@ ARIZONA_MIXER_ENUMS(ASRC1R, ARIZONA_ASRC1RMIX_INPUT_1_SOURCE); ARIZONA_MIXER_ENUMS(ASRC2L, ARIZONA_ASRC2LMIX_INPUT_1_SOURCE); ARIZONA_MIXER_ENUMS(ASRC2R, ARIZONA_ASRC2RMIX_INPUT_1_SOURCE); +static const char *wm5110_aec_loopback_texts[] = { + "HPOUT1L", "HPOUT1R", "HPOUT2L", "HPOUT2R", "HPOUT3L", "HPOUT3R", + "SPKOUTL", "SPKOUTR", "SPKDAT1L", "SPKDAT1R", "SPKDAT2L", "SPKDAT2R", +}; + +static const unsigned int wm5110_aec_loopback_values[] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, +}; + +static const struct soc_enum wm5110_aec_loopback = + SOC_VALUE_ENUM_SINGLE(ARIZONA_DAC_AEC_CONTROL_1, + ARIZONA_AEC_LOOPBACK_SRC_SHIFT, + ARIZONA_AEC_LOOPBACK_SRC_MASK, + ARRAY_SIZE(wm5110_aec_loopback_texts), + wm5110_aec_loopback_texts, + wm5110_aec_loopback_values); + +static const struct snd_kcontrol_new wm5110_aec_loopback_mux = + SOC_DAPM_VALUE_ENUM("AEC Loopback", wm5110_aec_loopback); + static const struct snd_soc_dapm_widget wm5110_dapm_widgets[] = { SND_SOC_DAPM_SUPPLY("SYSCLK", ARIZONA_SYSTEM_CLOCK_1, ARIZONA_SYSCLK_ENA_SHIFT, 0, NULL, 0), @@ -422,6 +442,9 @@ SND_SOC_DAPM_PGA("ASRC2L", ARIZONA_ASRC_ENABLE, ARIZONA_ASRC2L_ENA_SHIFT, 0, SND_SOC_DAPM_PGA("ASRC2R", ARIZONA_ASRC_ENABLE, ARIZONA_ASRC2R_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_VALUE_MUX("AEC Loopback", ARIZONA_DAC_AEC_CONTROL_1, + ARIZONA_AEC_LOOPBACK_ENA, 0, &wm5110_aec_loopback_mux), + SND_SOC_DAPM_AIF_OUT("AIF1TX1", NULL, 0, ARIZONA_AIF1_TX_ENABLES, ARIZONA_AIF1TX1_ENA_SHIFT, 0), SND_SOC_DAPM_AIF_OUT("AIF1TX2", NULL, 0, @@ -584,6 +607,7 @@ SND_SOC_DAPM_OUTPUT("SPKDAT2R"), { name, "Noise Generator", "Noise Generator" }, \ { name, "Tone Generator 1", "Tone Generator 1" }, \ { name, "Tone Generator 2", "Tone Generator 2" }, \ + { name, "AEC", "AEC Loopback" }, \ { name, "IN1L", "IN1L PGA" }, \ { name, "IN1R", "IN1R PGA" }, \ { name, "IN2L", "IN2L PGA" }, \ From 20bf691f4e028a4d65dc7888bc59a717ec0577b8 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 26 Sep 2012 17:52:36 +0100 Subject: [PATCH 27/32] ASoC: wm5110: Add OUT3R support Signed-off-by: Mark Brown --- sound/soc/codecs/wm5110.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sound/soc/codecs/wm5110.c b/sound/soc/codecs/wm5110.c index 83177aa83625..b99451ef0347 100644 --- a/sound/soc/codecs/wm5110.c +++ b/sound/soc/codecs/wm5110.c @@ -514,6 +514,9 @@ SND_SOC_DAPM_PGA_E("OUT2R", ARIZONA_OUTPUT_ENABLES_1, SND_SOC_DAPM_PGA_E("OUT3L", ARIZONA_OUTPUT_ENABLES_1, ARIZONA_OUT3L_ENA_SHIFT, 0, NULL, 0, arizona_out_ev, SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("OUT3R", ARIZONA_OUTPUT_ENABLES_1, + ARIZONA_OUT3R_ENA_SHIFT, 0, NULL, 0, arizona_out_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), SND_SOC_DAPM_PGA_E("OUT4L", ARIZONA_OUTPUT_ENABLES_1, ARIZONA_OUT4L_ENA_SHIFT, 0, NULL, 0, arizona_out_ev, SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), @@ -658,6 +661,7 @@ static const struct snd_soc_dapm_route wm5110_dapm_routes[] = { { "OUT2L", NULL, "CPVDD" }, { "OUT2R", NULL, "CPVDD" }, { "OUT3L", NULL, "CPVDD" }, + { "OUT3R", NULL, "CPVDD" }, { "OUT4L", NULL, "SPKVDDL" }, { "OUT4R", NULL, "SPKVDDR" }, From ae60503741991a36ed6b2a8f53b249b2a72af52b Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Thu, 27 Sep 2012 13:21:48 +0100 Subject: [PATCH 28/32] ASoC: wm5110: Adding missing volume update bits The volume update bits were being set on all but one input and one output. Signed-off-by: Charles Keepax Signed-off-by: Mark Brown Cc: stable@vger.kernel.org --- sound/soc/codecs/wm5110.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sound/soc/codecs/wm5110.c b/sound/soc/codecs/wm5110.c index b99451ef0347..9211e4192f71 100644 --- a/sound/soc/codecs/wm5110.c +++ b/sound/soc/codecs/wm5110.c @@ -916,6 +916,8 @@ static unsigned int wm5110_digital_vu[] = { ARIZONA_ADC_DIGITAL_VOLUME_2R, ARIZONA_ADC_DIGITAL_VOLUME_3L, ARIZONA_ADC_DIGITAL_VOLUME_3R, + ARIZONA_ADC_DIGITAL_VOLUME_4L, + ARIZONA_ADC_DIGITAL_VOLUME_4R, ARIZONA_DAC_DIGITAL_VOLUME_1L, ARIZONA_DAC_DIGITAL_VOLUME_1R, @@ -927,6 +929,8 @@ static unsigned int wm5110_digital_vu[] = { ARIZONA_DAC_DIGITAL_VOLUME_4R, ARIZONA_DAC_DIGITAL_VOLUME_5L, ARIZONA_DAC_DIGITAL_VOLUME_5R, + ARIZONA_DAC_DIGITAL_VOLUME_6L, + ARIZONA_DAC_DIGITAL_VOLUME_6R, }; static struct snd_soc_codec_driver soc_codec_dev_wm5110 = { From eb4d5fc1f0ce89e3d5b072c594a1e213a0e05881 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 27 Sep 2012 18:35:24 +0100 Subject: [PATCH 29/32] ASoC: wm_hubs: Ensure volume updates are handled during class W startup In some circumstances we may need to flush volume updates to the device after switching to class W mode. Do this unconditionally to ensure that these situations are handled. Signed-off-by: Mark Brown Cc: stable@vger.kernel.org --- sound/soc/codecs/wm_hubs.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sound/soc/codecs/wm_hubs.c b/sound/soc/codecs/wm_hubs.c index 7a773a835b8e..867ae97ddcec 100644 --- a/sound/soc/codecs/wm_hubs.c +++ b/sound/soc/codecs/wm_hubs.c @@ -681,6 +681,11 @@ void wm_hubs_update_class_w(struct snd_soc_codec *codec) snd_soc_update_bits(codec, WM8993_CLASS_W_0, WM8993_CP_DYN_V | WM8993_CP_DYN_FREQ, enable); + + snd_soc_write(codec, WM8993_LEFT_OUTPUT_VOLUME, + snd_soc_read(codec, WM8993_LEFT_OUTPUT_VOLUME)); + snd_soc_write(codec, WM8993_RIGHT_OUTPUT_VOLUME, + snd_soc_read(codec, WM8993_RIGHT_OUTPUT_VOLUME)); } EXPORT_SYMBOL_GPL(wm_hubs_update_class_w); From a31ebc349dade4e6a7a27e88669f20dbc6f8a3b8 Mon Sep 17 00:00:00 2001 From: Daniel Mack Date: Fri, 28 Sep 2012 01:36:44 +0200 Subject: [PATCH 30/32] ALSA: ASoC: add DT bindings for CS4271 Apart from pure matching, the bindings also support setting the the reset gpio line. Signed-off-by: Daniel Mack Cc: Alexander Sverdlin Signed-off-by: Mark Brown --- .../devicetree/bindings/sound/cs4271.txt | 36 +++++++++++++++++++ sound/soc/codecs/cs4271.c | 24 +++++++++++-- 2 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 Documentation/devicetree/bindings/sound/cs4271.txt diff --git a/Documentation/devicetree/bindings/sound/cs4271.txt b/Documentation/devicetree/bindings/sound/cs4271.txt new file mode 100644 index 000000000000..c81b5fd5a5bc --- /dev/null +++ b/Documentation/devicetree/bindings/sound/cs4271.txt @@ -0,0 +1,36 @@ +Cirrus Logic CS4271 DT bindings + +This driver supports both the I2C and the SPI bus. + +Required properties: + + - compatible: "cirrus,cs4271" + +For required properties on SPI, please consult +Documentation/devicetree/bindings/spi/spi-bus.txt + +Required properties on I2C: + + - reg: the i2c address + + +Optional properties: + + - reset-gpio: a GPIO spec to define which pin is connected to the chip's + !RESET pin + +Examples: + + codec_i2c: cs4271@10 { + compatible = "cirrus,cs4271"; + reg = <0x10>; + reset-gpio = <&gpio 23 0>; + }; + + codec_spi: cs4271@0 { + compatible = "cirrus,cs4271"; + reg = <0x0>; + reset-gpio = <&gpio 23 0>; + spi-max-frequency = <6000000>; + }; + diff --git a/sound/soc/codecs/cs4271.c b/sound/soc/codecs/cs4271.c index 9eb01d7d58a3..f994af34f552 100644 --- a/sound/soc/codecs/cs4271.c +++ b/sound/soc/codecs/cs4271.c @@ -22,12 +22,14 @@ #include #include #include -#include -#include -#include #include #include #include +#include +#include +#include +#include +#include #include #define CS4271_PCM_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ @@ -458,6 +460,14 @@ static int cs4271_soc_resume(struct snd_soc_codec *codec) #define cs4271_soc_resume NULL #endif /* CONFIG_PM */ +#ifdef CONFIG_OF +static const struct of_device_id cs4271_dt_ids[] = { + { .compatible = "cirrus,cs4271", }, + { } +}; +MODULE_DEVICE_TABLE(of, cs4271_dt_ids); +#endif + static int cs4271_probe(struct snd_soc_codec *codec) { struct cs4271_private *cs4271 = snd_soc_codec_get_drvdata(codec); @@ -465,6 +475,12 @@ static int cs4271_probe(struct snd_soc_codec *codec) int ret; int gpio_nreset = -EINVAL; +#ifdef CONFIG_OF + if (of_match_device(cs4271_dt_ids, codec->dev)) + gpio_nreset = of_get_named_gpio(codec->dev->of_node, + "reset-gpio", 0); +#endif + if (cs4271plat && gpio_is_valid(cs4271plat->gpio_nreset)) gpio_nreset = cs4271plat->gpio_nreset; @@ -569,6 +585,7 @@ static struct spi_driver cs4271_spi_driver = { .driver = { .name = "cs4271", .owner = THIS_MODULE, + .of_match_table = of_match_ptr(cs4271_dt_ids), }, .probe = cs4271_spi_probe, .remove = __devexit_p(cs4271_spi_remove), @@ -608,6 +625,7 @@ static struct i2c_driver cs4271_i2c_driver = { .driver = { .name = "cs4271", .owner = THIS_MODULE, + .of_match_table = of_match_ptr(cs4271_dt_ids), }, .id_table = cs4271_i2c_id, .probe = cs4271_i2c_probe, From da75c924878c48b3ee6ce21579bbf679f93ce40c Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Sat, 22 Sep 2012 12:27:31 -0300 Subject: [PATCH 31/32] ASoC: eukrea-tlv320: Convert it to platform driver Convert eukrea-tlv320 to platform driver. Signed-off-by: Fabio Estevam Signed-off-by: Mark Brown --- arch/arm/mach-imx/eukrea_mbimx27-baseboard.c | 1 + .../arm/mach-imx/eukrea_mbimxsd25-baseboard.c | 1 + .../arm/mach-imx/eukrea_mbimxsd35-baseboard.c | 1 + .../arm/mach-imx/eukrea_mbimxsd51-baseboard.c | 1 + sound/soc/fsl/eukrea-tlv320.c | 37 ++++++++++--------- 5 files changed, 23 insertions(+), 18 deletions(-) diff --git a/arch/arm/mach-imx/eukrea_mbimx27-baseboard.c b/arch/arm/mach-imx/eukrea_mbimx27-baseboard.c index fd3177f9e79a..98aef571b9f8 100644 --- a/arch/arm/mach-imx/eukrea_mbimx27-baseboard.c +++ b/arch/arm/mach-imx/eukrea_mbimx27-baseboard.c @@ -348,4 +348,5 @@ void __init eukrea_mbimx27_baseboard_init(void) imx27_add_imx_keypad(&eukrea_mbimx27_keymap_data); gpio_led_register_device(-1, &eukrea_mbimx27_gpio_led_info); + imx_add_platform_device("eukrea_tlv320", 0, NULL, 0, NULL, 0); } diff --git a/arch/arm/mach-imx/eukrea_mbimxsd25-baseboard.c b/arch/arm/mach-imx/eukrea_mbimxsd25-baseboard.c index dfd2da87c2df..0b84666792f0 100644 --- a/arch/arm/mach-imx/eukrea_mbimxsd25-baseboard.c +++ b/arch/arm/mach-imx/eukrea_mbimxsd25-baseboard.c @@ -306,4 +306,5 @@ void __init eukrea_mbimxsd25_baseboard_init(void) platform_add_devices(platform_devices, ARRAY_SIZE(platform_devices)); gpio_led_register_device(-1, &eukrea_mbimxsd_led_info); imx_add_gpio_keys(&eukrea_mbimxsd_button_data); + imx_add_platform_device("eukrea_tlv320", 0, NULL, 0, NULL, 0); } diff --git a/arch/arm/mach-imx/eukrea_mbimxsd35-baseboard.c b/arch/arm/mach-imx/eukrea_mbimxsd35-baseboard.c index 6e9dd12a6961..c6532a007d46 100644 --- a/arch/arm/mach-imx/eukrea_mbimxsd35-baseboard.c +++ b/arch/arm/mach-imx/eukrea_mbimxsd35-baseboard.c @@ -315,4 +315,5 @@ void __init eukrea_mbimxsd35_baseboard_init(void) platform_add_devices(platform_devices, ARRAY_SIZE(platform_devices)); gpio_led_register_device(-1, &eukrea_mbimxsd_led_info); imx_add_gpio_keys(&eukrea_mbimxsd_button_data); + imx_add_platform_device("eukrea_tlv320", 0, NULL, 0, NULL, 0); } diff --git a/arch/arm/mach-imx/eukrea_mbimxsd51-baseboard.c b/arch/arm/mach-imx/eukrea_mbimxsd51-baseboard.c index 96a24b73dc23..8b0de30d7a3f 100644 --- a/arch/arm/mach-imx/eukrea_mbimxsd51-baseboard.c +++ b/arch/arm/mach-imx/eukrea_mbimxsd51-baseboard.c @@ -228,4 +228,5 @@ void __init eukrea_mbimxsd51_baseboard_init(void) gpio_led_register_device(-1, &eukrea_mbimxsd51_led_info); imx_add_gpio_keys(&eukrea_mbimxsd51_button_data); + imx_add_platform_device("eukrea_tlv320", 0, NULL, 0, NULL, 0); } diff --git a/sound/soc/fsl/eukrea-tlv320.c b/sound/soc/fsl/eukrea-tlv320.c index efb9ede01208..267d5b4b63ce 100644 --- a/sound/soc/fsl/eukrea-tlv320.c +++ b/sound/soc/fsl/eukrea-tlv320.c @@ -93,9 +93,7 @@ static struct snd_soc_card eukrea_tlv320 = { .num_links = 1, }; -static struct platform_device *eukrea_tlv320_snd_device; - -static int __init eukrea_tlv320_init(void) +static int __devinit eukrea_tlv320_probe(struct platform_device *pdev) { int ret; int int_port = 0, ext_port; @@ -136,29 +134,32 @@ static int __init eukrea_tlv320_init(void) return 0; } - eukrea_tlv320_snd_device = platform_device_alloc("soc-audio", -1); - if (!eukrea_tlv320_snd_device) - return -ENOMEM; - - platform_set_drvdata(eukrea_tlv320_snd_device, &eukrea_tlv320); - ret = platform_device_add(eukrea_tlv320_snd_device); - - if (ret) { - printk(KERN_ERR "ASoC: Platform device allocation failed\n"); - platform_device_put(eukrea_tlv320_snd_device); - } + eukrea_tlv320.dev = &pdev->dev; + ret = snd_soc_register_card(&eukrea_tlv320); + if (ret) + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); return ret; } -static void __exit eukrea_tlv320_exit(void) +static int __devexit eukrea_tlv320_remove(struct platform_device *pdev) { - platform_device_unregister(eukrea_tlv320_snd_device); + snd_soc_unregister_card(&eukrea_tlv320); + + return 0; } -module_init(eukrea_tlv320_init); -module_exit(eukrea_tlv320_exit); +static struct platform_driver eukrea_tlv320_driver = { + .driver = { + .name = "eukrea_tlv320", + .owner = THIS_MODULE, + }, + .probe = eukrea_tlv320_probe, + .remove = __devexit_p(eukrea_tlv320_remove),}; + +module_platform_driver(eukrea_tlv320_driver); MODULE_AUTHOR("Eric BĂ©nard "); MODULE_DESCRIPTION("CPUIMX ALSA SoC driver"); MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:eukrea_tlv320"); From 9911f7f7562a25381eff93fdc660a4a3b4c0f6e0 Mon Sep 17 00:00:00 2001 From: Ashish Chavan Date: Fri, 21 Sep 2012 20:16:17 +0530 Subject: [PATCH 32/32] ASoC: codecs: Add DA9055 codec driver This patch adds support for Dialog semiconductor's DA9055 audio codec. This has been tested on DA9055 EVB with Samsung SMDK6410 board. Signed-off-by: Ashish Chavan Signed-off-by: David Dajun Chen Signed-off-by: Mark Brown --- include/sound/da9055.h | 33 + sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/da9055.c | 1510 +++++++++++++++++++++++++++++++++++++ 4 files changed, 1549 insertions(+) create mode 100644 include/sound/da9055.h create mode 100644 sound/soc/codecs/da9055.c diff --git a/include/sound/da9055.h b/include/sound/da9055.h new file mode 100644 index 000000000000..cf1241b64d89 --- /dev/null +++ b/include/sound/da9055.h @@ -0,0 +1,33 @@ +/* + * DA9055 ALSA Soc codec driver + * + * Copyright (c) 2012 Dialog Semiconductor + * + * Tested on (Samsung SMDK6410 board + DA9055 EVB) using I2S and I2C + * Written by David Chen and + * Ashish Chavan + * + * 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; either version 2 of the License, or (at your + * option) any later version. + */ + +#ifndef __SOUND_DA9055_H__ +#define __SOUND_DA9055_H__ + +enum da9055_micbias_voltage { + DA9055_MICBIAS_1_6V = 0, + DA9055_MICBIAS_1_8V = 1, + DA9055_MICBIAS_2_1V = 2, + DA9055_MICBIAS_2_2V = 3, +}; + +struct da9055_platform_data { + /* Selects which of the two MicBias pins acts as the bias source */ + bool micbias_source; + /* Selects the micbias voltage */ + enum da9055_micbias_voltage micbias; +}; + +#endif diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 3684255e5fba..b92759a39361 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -37,6 +37,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_CX20442 select SND_SOC_DA7210 if I2C select SND_SOC_DA732X if I2C + select SND_SOC_DA9055 if I2C select SND_SOC_DFBMCS320 select SND_SOC_ISABELLE if I2C select SND_SOC_JZ4740_CODEC @@ -239,6 +240,9 @@ config SND_SOC_DA7210 config SND_SOC_DA732X tristate +config SND_SOC_DA9055 + tristate + config SND_SOC_DFBMCS320 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index ca508b251df7..9bd4d95aab4f 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -24,6 +24,7 @@ snd-soc-cs4271-objs := cs4271.o snd-soc-cx20442-objs := cx20442.o snd-soc-da7210-objs := da7210.o snd-soc-da732x-objs := da732x.o +snd-soc-da9055-objs := da9055.o snd-soc-dfbmcs320-objs := dfbmcs320.o snd-soc-dmic-objs := dmic.o snd-soc-isabelle-objs := isabelle.o @@ -144,6 +145,7 @@ obj-$(CONFIG_SND_SOC_CS4271) += snd-soc-cs4271.o obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o obj-$(CONFIG_SND_SOC_DA7210) += snd-soc-da7210.o obj-$(CONFIG_SND_SOC_DA732X) += snd-soc-da732x.o +obj-$(CONFIG_SND_SOC_DA9055) += snd-soc-da9055.o obj-$(CONFIG_SND_SOC_DFBMCS320) += snd-soc-dfbmcs320.o obj-$(CONFIG_SND_SOC_DMIC) += snd-soc-dmic.o obj-$(CONFIG_SND_SOC_ISABELLE) += snd-soc-isabelle.o diff --git a/sound/soc/codecs/da9055.c b/sound/soc/codecs/da9055.c new file mode 100644 index 000000000000..185d8dd36399 --- /dev/null +++ b/sound/soc/codecs/da9055.c @@ -0,0 +1,1510 @@ +/* + * DA9055 ALSA Soc codec driver + * + * Copyright (c) 2012 Dialog Semiconductor + * + * Tested on (Samsung SMDK6410 board + DA9055 EVB) using I2S and I2C + * Written by David Chen and + * Ashish Chavan + * + * 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; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* DA9055 register space */ + +/* Status Registers */ +#define DA9055_STATUS1 0x02 +#define DA9055_PLL_STATUS 0x03 +#define DA9055_AUX_L_GAIN_STATUS 0x04 +#define DA9055_AUX_R_GAIN_STATUS 0x05 +#define DA9055_MIC_L_GAIN_STATUS 0x06 +#define DA9055_MIC_R_GAIN_STATUS 0x07 +#define DA9055_MIXIN_L_GAIN_STATUS 0x08 +#define DA9055_MIXIN_R_GAIN_STATUS 0x09 +#define DA9055_ADC_L_GAIN_STATUS 0x0A +#define DA9055_ADC_R_GAIN_STATUS 0x0B +#define DA9055_DAC_L_GAIN_STATUS 0x0C +#define DA9055_DAC_R_GAIN_STATUS 0x0D +#define DA9055_HP_L_GAIN_STATUS 0x0E +#define DA9055_HP_R_GAIN_STATUS 0x0F +#define DA9055_LINE_GAIN_STATUS 0x10 + +/* System Initialisation Registers */ +#define DA9055_CIF_CTRL 0x20 +#define DA9055_DIG_ROUTING_AIF 0X21 +#define DA9055_SR 0x22 +#define DA9055_REFERENCES 0x23 +#define DA9055_PLL_FRAC_TOP 0x24 +#define DA9055_PLL_FRAC_BOT 0x25 +#define DA9055_PLL_INTEGER 0x26 +#define DA9055_PLL_CTRL 0x27 +#define DA9055_AIF_CLK_MODE 0x28 +#define DA9055_AIF_CTRL 0x29 +#define DA9055_DIG_ROUTING_DAC 0x2A +#define DA9055_ALC_CTRL1 0x2B + +/* Input - Gain, Select and Filter Registers */ +#define DA9055_AUX_L_GAIN 0x30 +#define DA9055_AUX_R_GAIN 0x31 +#define DA9055_MIXIN_L_SELECT 0x32 +#define DA9055_MIXIN_R_SELECT 0x33 +#define DA9055_MIXIN_L_GAIN 0x34 +#define DA9055_MIXIN_R_GAIN 0x35 +#define DA9055_ADC_L_GAIN 0x36 +#define DA9055_ADC_R_GAIN 0x37 +#define DA9055_ADC_FILTERS1 0x38 +#define DA9055_MIC_L_GAIN 0x39 +#define DA9055_MIC_R_GAIN 0x3A + +/* Output - Gain, Select and Filter Registers */ +#define DA9055_DAC_FILTERS5 0x40 +#define DA9055_DAC_FILTERS2 0x41 +#define DA9055_DAC_FILTERS3 0x42 +#define DA9055_DAC_FILTERS4 0x43 +#define DA9055_DAC_FILTERS1 0x44 +#define DA9055_DAC_L_GAIN 0x45 +#define DA9055_DAC_R_GAIN 0x46 +#define DA9055_CP_CTRL 0x47 +#define DA9055_HP_L_GAIN 0x48 +#define DA9055_HP_R_GAIN 0x49 +#define DA9055_LINE_GAIN 0x4A +#define DA9055_MIXOUT_L_SELECT 0x4B +#define DA9055_MIXOUT_R_SELECT 0x4C + +/* System Controller Registers */ +#define DA9055_SYSTEM_MODES_INPUT 0x50 +#define DA9055_SYSTEM_MODES_OUTPUT 0x51 + +/* Control Registers */ +#define DA9055_AUX_L_CTRL 0x60 +#define DA9055_AUX_R_CTRL 0x61 +#define DA9055_MIC_BIAS_CTRL 0x62 +#define DA9055_MIC_L_CTRL 0x63 +#define DA9055_MIC_R_CTRL 0x64 +#define DA9055_MIXIN_L_CTRL 0x65 +#define DA9055_MIXIN_R_CTRL 0x66 +#define DA9055_ADC_L_CTRL 0x67 +#define DA9055_ADC_R_CTRL 0x68 +#define DA9055_DAC_L_CTRL 0x69 +#define DA9055_DAC_R_CTRL 0x6A +#define DA9055_HP_L_CTRL 0x6B +#define DA9055_HP_R_CTRL 0x6C +#define DA9055_LINE_CTRL 0x6D +#define DA9055_MIXOUT_L_CTRL 0x6E +#define DA9055_MIXOUT_R_CTRL 0x6F + +/* Configuration Registers */ +#define DA9055_LDO_CTRL 0x90 +#define DA9055_IO_CTRL 0x91 +#define DA9055_GAIN_RAMP_CTRL 0x92 +#define DA9055_MIC_CONFIG 0x93 +#define DA9055_PC_COUNT 0x94 +#define DA9055_CP_VOL_THRESHOLD1 0x95 +#define DA9055_CP_DELAY 0x96 +#define DA9055_CP_DETECTOR 0x97 +#define DA9055_AIF_OFFSET 0x98 +#define DA9055_DIG_CTRL 0x99 +#define DA9055_ALC_CTRL2 0x9A +#define DA9055_ALC_CTRL3 0x9B +#define DA9055_ALC_NOISE 0x9C +#define DA9055_ALC_TARGET_MIN 0x9D +#define DA9055_ALC_TARGET_MAX 0x9E +#define DA9055_ALC_GAIN_LIMITS 0x9F +#define DA9055_ALC_ANA_GAIN_LIMITS 0xA0 +#define DA9055_ALC_ANTICLIP_CTRL 0xA1 +#define DA9055_ALC_ANTICLIP_LEVEL 0xA2 +#define DA9055_ALC_OFFSET_OP2M_L 0xA6 +#define DA9055_ALC_OFFSET_OP2U_L 0xA7 +#define DA9055_ALC_OFFSET_OP2M_R 0xAB +#define DA9055_ALC_OFFSET_OP2U_R 0xAC +#define DA9055_ALC_CIC_OP_LVL_CTRL 0xAD +#define DA9055_ALC_CIC_OP_LVL_DATA 0xAE +#define DA9055_DAC_NG_SETUP_TIME 0xAF +#define DA9055_DAC_NG_OFF_THRESHOLD 0xB0 +#define DA9055_DAC_NG_ON_THRESHOLD 0xB1 +#define DA9055_DAC_NG_CTRL 0xB2 + +/* SR bit fields */ +#define DA9055_SR_8000 (0x1 << 0) +#define DA9055_SR_11025 (0x2 << 0) +#define DA9055_SR_12000 (0x3 << 0) +#define DA9055_SR_16000 (0x5 << 0) +#define DA9055_SR_22050 (0x6 << 0) +#define DA9055_SR_24000 (0x7 << 0) +#define DA9055_SR_32000 (0x9 << 0) +#define DA9055_SR_44100 (0xA << 0) +#define DA9055_SR_48000 (0xB << 0) +#define DA9055_SR_88200 (0xE << 0) +#define DA9055_SR_96000 (0xF << 0) + +/* REFERENCES bit fields */ +#define DA9055_BIAS_EN (1 << 3) +#define DA9055_VMID_EN (1 << 7) + +/* PLL_CTRL bit fields */ +#define DA9055_PLL_INDIV_10_20_MHZ (1 << 2) +#define DA9055_PLL_SRM_EN (1 << 6) +#define DA9055_PLL_EN (1 << 7) + +/* AIF_CLK_MODE bit fields */ +#define DA9055_AIF_BCLKS_PER_WCLK_32 (0 << 0) +#define DA9055_AIF_BCLKS_PER_WCLK_64 (1 << 0) +#define DA9055_AIF_BCLKS_PER_WCLK_128 (2 << 0) +#define DA9055_AIF_BCLKS_PER_WCLK_256 (3 << 0) +#define DA9055_AIF_CLK_EN_SLAVE_MODE (0 << 7) +#define DA9055_AIF_CLK_EN_MASTER_MODE (1 << 7) + +/* AIF_CTRL bit fields */ +#define DA9055_AIF_FORMAT_I2S_MODE (0 << 0) +#define DA9055_AIF_FORMAT_LEFT_J (1 << 0) +#define DA9055_AIF_FORMAT_RIGHT_J (2 << 0) +#define DA9055_AIF_WORD_S16_LE (0 << 2) +#define DA9055_AIF_WORD_S20_3LE (1 << 2) +#define DA9055_AIF_WORD_S24_LE (2 << 2) +#define DA9055_AIF_WORD_S32_LE (3 << 2) + +/* MIXIN_L_CTRL bit fields */ +#define DA9055_MIXIN_L_MIX_EN (1 << 3) + +/* MIXIN_R_CTRL bit fields */ +#define DA9055_MIXIN_R_MIX_EN (1 << 3) + +/* ADC_L_CTRL bit fields */ +#define DA9055_ADC_L_EN (1 << 7) + +/* ADC_R_CTRL bit fields */ +#define DA9055_ADC_R_EN (1 << 7) + +/* DAC_L_CTRL bit fields */ +#define DA9055_DAC_L_MUTE_EN (1 << 6) + +/* DAC_R_CTRL bit fields */ +#define DA9055_DAC_R_MUTE_EN (1 << 6) + +/* HP_L_CTRL bit fields */ +#define DA9055_HP_L_AMP_OE (1 << 3) + +/* HP_R_CTRL bit fields */ +#define DA9055_HP_R_AMP_OE (1 << 3) + +/* LINE_CTRL bit fields */ +#define DA9055_LINE_AMP_OE (1 << 3) + +/* MIXOUT_L_CTRL bit fields */ +#define DA9055_MIXOUT_L_MIX_EN (1 << 3) + +/* MIXOUT_R_CTRL bit fields */ +#define DA9055_MIXOUT_R_MIX_EN (1 << 3) + +/* MIC bias select bit fields */ +#define DA9055_MICBIAS2_EN (1 << 6) + +/* ALC_CIC_OP_LEVEL_CTRL bit fields */ +#define DA9055_ALC_DATA_MIDDLE (2 << 0) +#define DA9055_ALC_DATA_TOP (3 << 0) +#define DA9055_ALC_CIC_OP_CHANNEL_LEFT (0 << 7) +#define DA9055_ALC_CIC_OP_CHANNEL_RIGHT (1 << 7) + +#define DA9055_AIF_BCLK_MASK (3 << 0) +#define DA9055_AIF_CLK_MODE_MASK (1 << 7) +#define DA9055_AIF_FORMAT_MASK (3 << 0) +#define DA9055_AIF_WORD_LENGTH_MASK (3 << 2) +#define DA9055_GAIN_RAMPING_EN (1 << 5) +#define DA9055_MICBIAS_LEVEL_MASK (3 << 4) + +#define DA9055_ALC_OFFSET_15_8 0x00FF00 +#define DA9055_ALC_OFFSET_17_16 0x030000 +#define DA9055_ALC_AVG_ITERATIONS 5 + +struct pll_div { + int fref; + int fout; + u8 frac_top; + u8 frac_bot; + u8 integer; + u8 mode; /* 0 = slave, 1 = master */ +}; + +/* PLL divisor table */ +static const struct pll_div da9055_pll_div[] = { + /* for MASTER mode, fs = 44.1Khz and its harmonics */ + {11289600, 2822400, 0x00, 0x00, 0x20, 1}, /* MCLK=11.2896Mhz */ + {12000000, 2822400, 0x03, 0x61, 0x1E, 1}, /* MCLK=12Mhz */ + {12288000, 2822400, 0x0C, 0xCC, 0x1D, 1}, /* MCLK=12.288Mhz */ + {13000000, 2822400, 0x19, 0x45, 0x1B, 1}, /* MCLK=13Mhz */ + {13500000, 2822400, 0x18, 0x56, 0x1A, 1}, /* MCLK=13.5Mhz */ + {14400000, 2822400, 0x02, 0xD0, 0x19, 1}, /* MCLK=14.4Mhz */ + {19200000, 2822400, 0x1A, 0x1C, 0x12, 1}, /* MCLK=19.2Mhz */ + {19680000, 2822400, 0x0B, 0x6D, 0x12, 1}, /* MCLK=19.68Mhz */ + {19800000, 2822400, 0x07, 0xDD, 0x12, 1}, /* MCLK=19.8Mhz */ + /* for MASTER mode, fs = 48Khz and its harmonics */ + {11289600, 3072000, 0x1A, 0x8E, 0x22, 1}, /* MCLK=11.2896Mhz */ + {12000000, 3072000, 0x18, 0x93, 0x20, 1}, /* MCLK=12Mhz */ + {12288000, 3072000, 0x00, 0x00, 0x20, 1}, /* MCLK=12.288Mhz */ + {13000000, 3072000, 0x07, 0xEA, 0x1E, 1}, /* MCLK=13Mhz */ + {13500000, 3072000, 0x04, 0x11, 0x1D, 1}, /* MCLK=13.5Mhz */ + {14400000, 3072000, 0x09, 0xD0, 0x1B, 1}, /* MCLK=14.4Mhz */ + {19200000, 3072000, 0x0F, 0x5C, 0x14, 1}, /* MCLK=19.2Mhz */ + {19680000, 3072000, 0x1F, 0x60, 0x13, 1}, /* MCLK=19.68Mhz */ + {19800000, 3072000, 0x1B, 0x80, 0x13, 1}, /* MCLK=19.8Mhz */ + /* for SLAVE mode with SRM */ + {11289600, 2822400, 0x0D, 0x47, 0x21, 0}, /* MCLK=11.2896Mhz */ + {12000000, 2822400, 0x0D, 0xFA, 0x1F, 0}, /* MCLK=12Mhz */ + {12288000, 2822400, 0x16, 0x66, 0x1E, 0}, /* MCLK=12.288Mhz */ + {13000000, 2822400, 0x00, 0x98, 0x1D, 0}, /* MCLK=13Mhz */ + {13500000, 2822400, 0x1E, 0x33, 0x1B, 0}, /* MCLK=13.5Mhz */ + {14400000, 2822400, 0x06, 0x50, 0x1A, 0}, /* MCLK=14.4Mhz */ + {19200000, 2822400, 0x14, 0xBC, 0x13, 0}, /* MCLK=19.2Mhz */ + {19680000, 2822400, 0x05, 0x66, 0x13, 0}, /* MCLK=19.68Mhz */ + {19800000, 2822400, 0x01, 0xAE, 0x13, 0}, /* MCLK=19.8Mhz */ +}; + +enum clk_src { + DA9055_CLKSRC_MCLK +}; + +/* Gain and Volume */ + +static const unsigned int aux_vol_tlv[] = { + TLV_DB_RANGE_HEAD(2), + 0x0, 0x10, TLV_DB_SCALE_ITEM(-5400, 0, 0), + /* -54dB to 15dB */ + 0x11, 0x3f, TLV_DB_SCALE_ITEM(-5400, 150, 0) +}; + +static const unsigned int digital_gain_tlv[] = { + TLV_DB_RANGE_HEAD(2), + 0x0, 0x07, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 1), + /* -78dB to 12dB */ + 0x08, 0x7f, TLV_DB_SCALE_ITEM(-7800, 75, 0) +}; + +static const unsigned int alc_analog_gain_tlv[] = { + TLV_DB_RANGE_HEAD(2), + 0x0, 0x0, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 1), + /* 0dB to 36dB */ + 0x01, 0x07, TLV_DB_SCALE_ITEM(0, 600, 0) +}; + +static const DECLARE_TLV_DB_SCALE(mic_vol_tlv, -600, 600, 0); +static const DECLARE_TLV_DB_SCALE(mixin_gain_tlv, -450, 150, 0); +static const DECLARE_TLV_DB_SCALE(eq_gain_tlv, -1050, 150, 0); +static const DECLARE_TLV_DB_SCALE(hp_vol_tlv, -5700, 100, 0); +static const DECLARE_TLV_DB_SCALE(lineout_vol_tlv, -4800, 100, 0); +static const DECLARE_TLV_DB_SCALE(alc_threshold_tlv, -9450, 150, 0); +static const DECLARE_TLV_DB_SCALE(alc_gain_tlv, 0, 600, 0); + +/* ADC and DAC high pass filter cutoff value */ +static const char * const da9055_hpf_cutoff_txt[] = { + "Fs/24000", "Fs/12000", "Fs/6000", "Fs/3000" +}; + +static const struct soc_enum da9055_dac_hpf_cutoff = + SOC_ENUM_SINGLE(DA9055_DAC_FILTERS1, 4, 4, da9055_hpf_cutoff_txt); + +static const struct soc_enum da9055_adc_hpf_cutoff = + SOC_ENUM_SINGLE(DA9055_ADC_FILTERS1, 4, 4, da9055_hpf_cutoff_txt); + +/* ADC and DAC voice mode (8kHz) high pass cutoff value */ +static const char * const da9055_vf_cutoff_txt[] = { + "2.5Hz", "25Hz", "50Hz", "100Hz", "150Hz", "200Hz", "300Hz", "400Hz" +}; + +static const struct soc_enum da9055_dac_vf_cutoff = + SOC_ENUM_SINGLE(DA9055_DAC_FILTERS1, 0, 8, da9055_vf_cutoff_txt); + +static const struct soc_enum da9055_adc_vf_cutoff = + SOC_ENUM_SINGLE(DA9055_ADC_FILTERS1, 0, 8, da9055_vf_cutoff_txt); + +/* Gain ramping rate value */ +static const char * const da9055_gain_ramping_txt[] = { + "nominal rate", "nominal rate * 4", "nominal rate * 8", + "nominal rate / 8" +}; + +static const struct soc_enum da9055_gain_ramping_rate = + SOC_ENUM_SINGLE(DA9055_GAIN_RAMP_CTRL, 0, 4, da9055_gain_ramping_txt); + +/* DAC noise gate setup time value */ +static const char * const da9055_dac_ng_setup_time_txt[] = { + "256 samples", "512 samples", "1024 samples", "2048 samples" +}; + +static const struct soc_enum da9055_dac_ng_setup_time = + SOC_ENUM_SINGLE(DA9055_DAC_NG_SETUP_TIME, 0, 4, + da9055_dac_ng_setup_time_txt); + +/* DAC noise gate rampup rate value */ +static const char * const da9055_dac_ng_rampup_txt[] = { + "0.02 ms/dB", "0.16 ms/dB" +}; + +static const struct soc_enum da9055_dac_ng_rampup_rate = + SOC_ENUM_SINGLE(DA9055_DAC_NG_SETUP_TIME, 2, 2, + da9055_dac_ng_rampup_txt); + +/* DAC noise gate rampdown rate value */ +static const char * const da9055_dac_ng_rampdown_txt[] = { + "0.64 ms/dB", "20.48 ms/dB" +}; + +static const struct soc_enum da9055_dac_ng_rampdown_rate = + SOC_ENUM_SINGLE(DA9055_DAC_NG_SETUP_TIME, 3, 2, + da9055_dac_ng_rampdown_txt); + +/* DAC soft mute rate value */ +static const char * const da9055_dac_soft_mute_rate_txt[] = { + "1", "2", "4", "8", "16", "32", "64" +}; + +static const struct soc_enum da9055_dac_soft_mute_rate = + SOC_ENUM_SINGLE(DA9055_DAC_FILTERS5, 4, 7, + da9055_dac_soft_mute_rate_txt); + +/* DAC routing select */ +static const char * const da9055_dac_src_txt[] = { + "ADC output left", "ADC output right", "AIF input left", + "AIF input right" +}; + +static const struct soc_enum da9055_dac_l_src = + SOC_ENUM_SINGLE(DA9055_DIG_ROUTING_DAC, 0, 4, da9055_dac_src_txt); + +static const struct soc_enum da9055_dac_r_src = + SOC_ENUM_SINGLE(DA9055_DIG_ROUTING_DAC, 4, 4, da9055_dac_src_txt); + +/* MIC PGA Left source select */ +static const char * const da9055_mic_l_src_txt[] = { + "MIC1_P_N", "MIC1_P", "MIC1_N", "MIC2_L" +}; + +static const struct soc_enum da9055_mic_l_src = + SOC_ENUM_SINGLE(DA9055_MIXIN_L_SELECT, 4, 4, da9055_mic_l_src_txt); + +/* MIC PGA Right source select */ +static const char * const da9055_mic_r_src_txt[] = { + "MIC2_R_L", "MIC2_R", "MIC2_L" +}; + +static const struct soc_enum da9055_mic_r_src = + SOC_ENUM_SINGLE(DA9055_MIXIN_R_SELECT, 4, 3, da9055_mic_r_src_txt); + +/* ALC Input Signal Tracking rate select */ +static const char * const da9055_signal_tracking_rate_txt[] = { + "1/4", "1/16", "1/256", "1/65536" +}; + +static const struct soc_enum da9055_integ_attack_rate = + SOC_ENUM_SINGLE(DA9055_ALC_CTRL3, 4, 4, + da9055_signal_tracking_rate_txt); + +static const struct soc_enum da9055_integ_release_rate = + SOC_ENUM_SINGLE(DA9055_ALC_CTRL3, 6, 4, + da9055_signal_tracking_rate_txt); + +/* ALC Attack Rate select */ +static const char * const da9055_attack_rate_txt[] = { + "44/fs", "88/fs", "176/fs", "352/fs", "704/fs", "1408/fs", "2816/fs", + "5632/fs", "11264/fs", "22528/fs", "45056/fs", "90112/fs", "180224/fs" +}; + +static const struct soc_enum da9055_attack_rate = + SOC_ENUM_SINGLE(DA9055_ALC_CTRL2, 0, 13, da9055_attack_rate_txt); + +/* ALC Release Rate select */ +static const char * const da9055_release_rate_txt[] = { + "176/fs", "352/fs", "704/fs", "1408/fs", "2816/fs", "5632/fs", + "11264/fs", "22528/fs", "45056/fs", "90112/fs", "180224/fs" +}; + +static const struct soc_enum da9055_release_rate = + SOC_ENUM_SINGLE(DA9055_ALC_CTRL2, 4, 11, da9055_release_rate_txt); + +/* ALC Hold Time select */ +static const char * const da9055_hold_time_txt[] = { + "62/fs", "124/fs", "248/fs", "496/fs", "992/fs", "1984/fs", "3968/fs", + "7936/fs", "15872/fs", "31744/fs", "63488/fs", "126976/fs", + "253952/fs", "507904/fs", "1015808/fs", "2031616/fs" +}; + +static const struct soc_enum da9055_hold_time = + SOC_ENUM_SINGLE(DA9055_ALC_CTRL3, 0, 16, da9055_hold_time_txt); + +static int da9055_get_alc_data(struct snd_soc_codec *codec, u8 reg_val) +{ + int mid_data, top_data; + int sum = 0; + u8 iteration; + + for (iteration = 0; iteration < DA9055_ALC_AVG_ITERATIONS; + iteration++) { + /* Select the left or right channel and capture data */ + snd_soc_write(codec, DA9055_ALC_CIC_OP_LVL_CTRL, reg_val); + + /* Select middle 8 bits for read back from data register */ + snd_soc_write(codec, DA9055_ALC_CIC_OP_LVL_CTRL, + reg_val | DA9055_ALC_DATA_MIDDLE); + mid_data = snd_soc_read(codec, DA9055_ALC_CIC_OP_LVL_DATA); + + /* Select top 8 bits for read back from data register */ + snd_soc_write(codec, DA9055_ALC_CIC_OP_LVL_CTRL, + reg_val | DA9055_ALC_DATA_TOP); + top_data = snd_soc_read(codec, DA9055_ALC_CIC_OP_LVL_DATA); + + sum += ((mid_data << 8) | (top_data << 16)); + } + + return sum / DA9055_ALC_AVG_ITERATIONS; +} + +static int da9055_put_alc_sw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + u8 reg_val, adc_left, adc_right; + int avg_left_data, avg_right_data, offset_l, offset_r; + + if (ucontrol->value.integer.value[0]) { + /* + * While enabling ALC (or ALC sync mode), calibration of the DC + * offsets must be done first + */ + + /* Save current values from ADC control registers */ + adc_left = snd_soc_read(codec, DA9055_ADC_L_CTRL); + adc_right = snd_soc_read(codec, DA9055_ADC_R_CTRL); + + /* Enable ADC Left and Right */ + snd_soc_update_bits(codec, DA9055_ADC_L_CTRL, + DA9055_ADC_L_EN, DA9055_ADC_L_EN); + snd_soc_update_bits(codec, DA9055_ADC_R_CTRL, + DA9055_ADC_R_EN, DA9055_ADC_R_EN); + + /* Calculate average for Left and Right data */ + /* Left Data */ + avg_left_data = da9055_get_alc_data(codec, + DA9055_ALC_CIC_OP_CHANNEL_LEFT); + /* Right Data */ + avg_right_data = da9055_get_alc_data(codec, + DA9055_ALC_CIC_OP_CHANNEL_RIGHT); + + /* Calculate DC offset */ + offset_l = -avg_left_data; + offset_r = -avg_right_data; + + reg_val = (offset_l & DA9055_ALC_OFFSET_15_8) >> 8; + snd_soc_write(codec, DA9055_ALC_OFFSET_OP2M_L, reg_val); + reg_val = (offset_l & DA9055_ALC_OFFSET_17_16) >> 16; + snd_soc_write(codec, DA9055_ALC_OFFSET_OP2U_L, reg_val); + + reg_val = (offset_r & DA9055_ALC_OFFSET_15_8) >> 8; + snd_soc_write(codec, DA9055_ALC_OFFSET_OP2M_R, reg_val); + reg_val = (offset_r & DA9055_ALC_OFFSET_17_16) >> 16; + snd_soc_write(codec, DA9055_ALC_OFFSET_OP2U_R, reg_val); + + /* Restore original values of ADC control registers */ + snd_soc_write(codec, DA9055_ADC_L_CTRL, adc_left); + snd_soc_write(codec, DA9055_ADC_R_CTRL, adc_right); + } + + return snd_soc_put_volsw(kcontrol, ucontrol); +} + +static const struct snd_kcontrol_new da9055_snd_controls[] = { + + /* Volume controls */ + SOC_DOUBLE_R_TLV("Mic Volume", + DA9055_MIC_L_GAIN, DA9055_MIC_R_GAIN, + 0, 0x7, 0, mic_vol_tlv), + SOC_DOUBLE_R_TLV("Aux Volume", + DA9055_AUX_L_GAIN, DA9055_AUX_R_GAIN, + 0, 0x3f, 0, aux_vol_tlv), + SOC_DOUBLE_R_TLV("Mixin PGA Volume", + DA9055_MIXIN_L_GAIN, DA9055_MIXIN_R_GAIN, + 0, 0xf, 0, mixin_gain_tlv), + SOC_DOUBLE_R_TLV("ADC Volume", + DA9055_ADC_L_GAIN, DA9055_ADC_R_GAIN, + 0, 0x7f, 0, digital_gain_tlv), + + SOC_DOUBLE_R_TLV("DAC Volume", + DA9055_DAC_L_GAIN, DA9055_DAC_R_GAIN, + 0, 0x7f, 0, digital_gain_tlv), + SOC_DOUBLE_R_TLV("Headphone Volume", + DA9055_HP_L_GAIN, DA9055_HP_R_GAIN, + 0, 0x3f, 0, hp_vol_tlv), + SOC_SINGLE_TLV("Lineout Volume", DA9055_LINE_GAIN, 0, 0x3f, 0, + lineout_vol_tlv), + + /* DAC Equalizer controls */ + SOC_SINGLE("DAC EQ Switch", DA9055_DAC_FILTERS4, 7, 1, 0), + SOC_SINGLE_TLV("DAC EQ1 Volume", DA9055_DAC_FILTERS2, 0, 0xf, 0, + eq_gain_tlv), + SOC_SINGLE_TLV("DAC EQ2 Volume", DA9055_DAC_FILTERS2, 4, 0xf, 0, + eq_gain_tlv), + SOC_SINGLE_TLV("DAC EQ3 Volume", DA9055_DAC_FILTERS3, 0, 0xf, 0, + eq_gain_tlv), + SOC_SINGLE_TLV("DAC EQ4 Volume", DA9055_DAC_FILTERS3, 4, 0xf, 0, + eq_gain_tlv), + SOC_SINGLE_TLV("DAC EQ5 Volume", DA9055_DAC_FILTERS4, 0, 0xf, 0, + eq_gain_tlv), + + /* High Pass Filter and Voice Mode controls */ + SOC_SINGLE("ADC HPF Switch", DA9055_ADC_FILTERS1, 7, 1, 0), + SOC_ENUM("ADC HPF Cutoff", da9055_adc_hpf_cutoff), + SOC_SINGLE("ADC Voice Mode Switch", DA9055_ADC_FILTERS1, 3, 1, 0), + SOC_ENUM("ADC Voice Cutoff", da9055_adc_vf_cutoff), + + SOC_SINGLE("DAC HPF Switch", DA9055_DAC_FILTERS1, 7, 1, 0), + SOC_ENUM("DAC HPF Cutoff", da9055_dac_hpf_cutoff), + SOC_SINGLE("DAC Voice Mode Switch", DA9055_DAC_FILTERS1, 3, 1, 0), + SOC_ENUM("DAC Voice Cutoff", da9055_dac_vf_cutoff), + + /* Mute controls */ + SOC_DOUBLE_R("Mic Switch", DA9055_MIC_L_CTRL, + DA9055_MIC_R_CTRL, 6, 1, 0), + SOC_DOUBLE_R("Aux Switch", DA9055_AUX_L_CTRL, + DA9055_AUX_R_CTRL, 6, 1, 0), + SOC_DOUBLE_R("Mixin PGA Switch", DA9055_MIXIN_L_CTRL, + DA9055_MIXIN_R_CTRL, 6, 1, 0), + SOC_DOUBLE_R("ADC Switch", DA9055_ADC_L_CTRL, + DA9055_ADC_R_CTRL, 6, 1, 0), + SOC_DOUBLE_R("Headphone Switch", DA9055_HP_L_CTRL, + DA9055_HP_R_CTRL, 6, 1, 0), + SOC_SINGLE("Lineout Switch", DA9055_LINE_CTRL, 6, 1, 0), + SOC_SINGLE("DAC Soft Mute Switch", DA9055_DAC_FILTERS5, 7, 1, 0), + SOC_ENUM("DAC Soft Mute Rate", da9055_dac_soft_mute_rate), + + /* Zero Cross controls */ + SOC_DOUBLE_R("Aux ZC Switch", DA9055_AUX_L_CTRL, + DA9055_AUX_R_CTRL, 4, 1, 0), + SOC_DOUBLE_R("Mixin PGA ZC Switch", DA9055_MIXIN_L_CTRL, + DA9055_MIXIN_R_CTRL, 4, 1, 0), + SOC_DOUBLE_R("Headphone ZC Switch", DA9055_HP_L_CTRL, + DA9055_HP_R_CTRL, 4, 1, 0), + SOC_SINGLE("Lineout ZC Switch", DA9055_LINE_CTRL, 4, 1, 0), + + /* Gain Ramping controls */ + SOC_DOUBLE_R("Aux Gain Ramping Switch", DA9055_AUX_L_CTRL, + DA9055_AUX_R_CTRL, 5, 1, 0), + SOC_DOUBLE_R("Mixin Gain Ramping Switch", DA9055_MIXIN_L_CTRL, + DA9055_MIXIN_R_CTRL, 5, 1, 0), + SOC_DOUBLE_R("ADC Gain Ramping Switch", DA9055_ADC_L_CTRL, + DA9055_ADC_R_CTRL, 5, 1, 0), + SOC_DOUBLE_R("DAC Gain Ramping Switch", DA9055_DAC_L_CTRL, + DA9055_DAC_R_CTRL, 5, 1, 0), + SOC_DOUBLE_R("Headphone Gain Ramping Switch", DA9055_HP_L_CTRL, + DA9055_HP_R_CTRL, 5, 1, 0), + SOC_SINGLE("Lineout Gain Ramping Switch", DA9055_LINE_CTRL, 5, 1, 0), + SOC_ENUM("Gain Ramping Rate", da9055_gain_ramping_rate), + + /* DAC Noise Gate controls */ + SOC_SINGLE("DAC NG Switch", DA9055_DAC_NG_CTRL, 7, 1, 0), + SOC_SINGLE("DAC NG ON Threshold", DA9055_DAC_NG_ON_THRESHOLD, + 0, 0x7, 0), + SOC_SINGLE("DAC NG OFF Threshold", DA9055_DAC_NG_OFF_THRESHOLD, + 0, 0x7, 0), + SOC_ENUM("DAC NG Setup Time", da9055_dac_ng_setup_time), + SOC_ENUM("DAC NG Rampup Rate", da9055_dac_ng_rampup_rate), + SOC_ENUM("DAC NG Rampdown Rate", da9055_dac_ng_rampdown_rate), + + /* DAC Invertion control */ + SOC_SINGLE("DAC Left Invert", DA9055_DIG_CTRL, 3, 1, 0), + SOC_SINGLE("DAC Right Invert", DA9055_DIG_CTRL, 7, 1, 0), + + /* DMIC controls */ + SOC_DOUBLE_R("DMIC Switch", DA9055_MIXIN_L_SELECT, + DA9055_MIXIN_R_SELECT, 7, 1, 0), + + /* ALC Controls */ + SOC_DOUBLE_EXT("ALC Switch", DA9055_ALC_CTRL1, 3, 7, 1, 0, + snd_soc_get_volsw, da9055_put_alc_sw), + SOC_SINGLE_EXT("ALC Sync Mode Switch", DA9055_ALC_CTRL1, 1, 1, 0, + snd_soc_get_volsw, da9055_put_alc_sw), + SOC_SINGLE("ALC Offset Switch", DA9055_ALC_CTRL1, 0, 1, 0), + SOC_SINGLE("ALC Anticlip Mode Switch", DA9055_ALC_ANTICLIP_CTRL, + 7, 1, 0), + SOC_SINGLE("ALC Anticlip Level", DA9055_ALC_ANTICLIP_LEVEL, + 0, 0x7f, 0), + SOC_SINGLE_TLV("ALC Min Threshold Volume", DA9055_ALC_TARGET_MIN, + 0, 0x3f, 1, alc_threshold_tlv), + SOC_SINGLE_TLV("ALC Max Threshold Volume", DA9055_ALC_TARGET_MAX, + 0, 0x3f, 1, alc_threshold_tlv), + SOC_SINGLE_TLV("ALC Noise Threshold Volume", DA9055_ALC_NOISE, + 0, 0x3f, 1, alc_threshold_tlv), + SOC_SINGLE_TLV("ALC Max Gain Volume", DA9055_ALC_GAIN_LIMITS, + 4, 0xf, 0, alc_gain_tlv), + SOC_SINGLE_TLV("ALC Max Attenuation Volume", DA9055_ALC_GAIN_LIMITS, + 0, 0xf, 0, alc_gain_tlv), + SOC_SINGLE_TLV("ALC Min Analog Gain Volume", + DA9055_ALC_ANA_GAIN_LIMITS, + 0, 0x7, 0, alc_analog_gain_tlv), + SOC_SINGLE_TLV("ALC Max Analog Gain Volume", + DA9055_ALC_ANA_GAIN_LIMITS, + 4, 0x7, 0, alc_analog_gain_tlv), + SOC_ENUM("ALC Attack Rate", da9055_attack_rate), + SOC_ENUM("ALC Release Rate", da9055_release_rate), + SOC_ENUM("ALC Hold Time", da9055_hold_time), + /* + * Rate at which input signal envelope is tracked as the signal gets + * larger + */ + SOC_ENUM("ALC Integ Attack Rate", da9055_integ_attack_rate), + /* + * Rate at which input signal envelope is tracked as the signal gets + * smaller + */ + SOC_ENUM("ALC Integ Release Rate", da9055_integ_release_rate), +}; + +/* DAPM Controls */ + +/* Mic PGA Left Source */ +static const struct snd_kcontrol_new da9055_mic_l_mux_controls = +SOC_DAPM_ENUM("Route", da9055_mic_l_src); + +/* Mic PGA Right Source */ +static const struct snd_kcontrol_new da9055_mic_r_mux_controls = +SOC_DAPM_ENUM("Route", da9055_mic_r_src); + +/* In Mixer Left */ +static const struct snd_kcontrol_new da9055_dapm_mixinl_controls[] = { + SOC_DAPM_SINGLE("Aux Left Switch", DA9055_MIXIN_L_SELECT, 0, 1, 0), + SOC_DAPM_SINGLE("Mic Left Switch", DA9055_MIXIN_L_SELECT, 1, 1, 0), + SOC_DAPM_SINGLE("Mic Right Switch", DA9055_MIXIN_L_SELECT, 2, 1, 0), +}; + +/* In Mixer Right */ +static const struct snd_kcontrol_new da9055_dapm_mixinr_controls[] = { + SOC_DAPM_SINGLE("Aux Right Switch", DA9055_MIXIN_R_SELECT, 0, 1, 0), + SOC_DAPM_SINGLE("Mic Right Switch", DA9055_MIXIN_R_SELECT, 1, 1, 0), + SOC_DAPM_SINGLE("Mic Left Switch", DA9055_MIXIN_R_SELECT, 2, 1, 0), + SOC_DAPM_SINGLE("Mixin Left Switch", DA9055_MIXIN_R_SELECT, 3, 1, 0), +}; + +/* DAC Left Source */ +static const struct snd_kcontrol_new da9055_dac_l_mux_controls = +SOC_DAPM_ENUM("Route", da9055_dac_l_src); + +/* DAC Right Source */ +static const struct snd_kcontrol_new da9055_dac_r_mux_controls = +SOC_DAPM_ENUM("Route", da9055_dac_r_src); + +/* Out Mixer Left */ +static const struct snd_kcontrol_new da9055_dapm_mixoutl_controls[] = { + SOC_DAPM_SINGLE("Aux Left Switch", DA9055_MIXOUT_L_SELECT, 0, 1, 0), + SOC_DAPM_SINGLE("Mixin Left Switch", DA9055_MIXOUT_L_SELECT, 1, 1, 0), + SOC_DAPM_SINGLE("Mixin Right Switch", DA9055_MIXOUT_L_SELECT, 2, 1, 0), + SOC_DAPM_SINGLE("DAC Left Switch", DA9055_MIXOUT_L_SELECT, 3, 1, 0), + SOC_DAPM_SINGLE("Aux Left Invert Switch", DA9055_MIXOUT_L_SELECT, + 4, 1, 0), + SOC_DAPM_SINGLE("Mixin Left Invert Switch", DA9055_MIXOUT_L_SELECT, + 5, 1, 0), + SOC_DAPM_SINGLE("Mixin Right Invert Switch", DA9055_MIXOUT_L_SELECT, + 6, 1, 0), +}; + +/* Out Mixer Right */ +static const struct snd_kcontrol_new da9055_dapm_mixoutr_controls[] = { + SOC_DAPM_SINGLE("Aux Right Switch", DA9055_MIXOUT_R_SELECT, 0, 1, 0), + SOC_DAPM_SINGLE("Mixin Right Switch", DA9055_MIXOUT_R_SELECT, 1, 1, 0), + SOC_DAPM_SINGLE("Mixin Left Switch", DA9055_MIXOUT_R_SELECT, 2, 1, 0), + SOC_DAPM_SINGLE("DAC Right Switch", DA9055_MIXOUT_R_SELECT, 3, 1, 0), + SOC_DAPM_SINGLE("Aux Right Invert Switch", DA9055_MIXOUT_R_SELECT, + 4, 1, 0), + SOC_DAPM_SINGLE("Mixin Right Invert Switch", DA9055_MIXOUT_R_SELECT, + 5, 1, 0), + SOC_DAPM_SINGLE("Mixin Left Invert Switch", DA9055_MIXOUT_R_SELECT, + 6, 1, 0), +}; + +/* DAPM widgets */ +static const struct snd_soc_dapm_widget da9055_dapm_widgets[] = { + /* Input Side */ + + /* Input Lines */ + SND_SOC_DAPM_INPUT("MIC1"), + SND_SOC_DAPM_INPUT("MIC2"), + SND_SOC_DAPM_INPUT("AUXL"), + SND_SOC_DAPM_INPUT("AUXR"), + + /* MUXs for Mic PGA source selection */ + SND_SOC_DAPM_MUX("Mic Left Source", SND_SOC_NOPM, 0, 0, + &da9055_mic_l_mux_controls), + SND_SOC_DAPM_MUX("Mic Right Source", SND_SOC_NOPM, 0, 0, + &da9055_mic_r_mux_controls), + + /* Input PGAs */ + SND_SOC_DAPM_PGA("Mic Left", DA9055_MIC_L_CTRL, 7, 0, NULL, 0), + SND_SOC_DAPM_PGA("Mic Right", DA9055_MIC_R_CTRL, 7, 0, NULL, 0), + SND_SOC_DAPM_PGA("Aux Left", DA9055_AUX_L_CTRL, 7, 0, NULL, 0), + SND_SOC_DAPM_PGA("Aux Right", DA9055_AUX_R_CTRL, 7, 0, NULL, 0), + SND_SOC_DAPM_PGA("MIXIN Left", DA9055_MIXIN_L_CTRL, 7, 0, NULL, 0), + SND_SOC_DAPM_PGA("MIXIN Right", DA9055_MIXIN_R_CTRL, 7, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("Mic Bias", DA9055_MIC_BIAS_CTRL, 7, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("AIF", DA9055_AIF_CTRL, 7, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Charge Pump", DA9055_CP_CTRL, 7, 0, NULL, 0), + + /* Input Mixers */ + SND_SOC_DAPM_MIXER("In Mixer Left", SND_SOC_NOPM, 0, 0, + &da9055_dapm_mixinl_controls[0], + ARRAY_SIZE(da9055_dapm_mixinl_controls)), + SND_SOC_DAPM_MIXER("In Mixer Right", SND_SOC_NOPM, 0, 0, + &da9055_dapm_mixinr_controls[0], + ARRAY_SIZE(da9055_dapm_mixinr_controls)), + + /* ADCs */ + SND_SOC_DAPM_ADC("ADC Left", "Capture", DA9055_ADC_L_CTRL, 7, 0), + SND_SOC_DAPM_ADC("ADC Right", "Capture", DA9055_ADC_R_CTRL, 7, 0), + + /* Output Side */ + + /* MUXs for DAC source selection */ + SND_SOC_DAPM_MUX("DAC Left Source", SND_SOC_NOPM, 0, 0, + &da9055_dac_l_mux_controls), + SND_SOC_DAPM_MUX("DAC Right Source", SND_SOC_NOPM, 0, 0, + &da9055_dac_r_mux_controls), + + /* AIF input */ + SND_SOC_DAPM_AIF_IN("AIFIN Left", "Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("AIFIN Right", "Playback", 0, SND_SOC_NOPM, 0, 0), + + /* DACs */ + SND_SOC_DAPM_DAC("DAC Left", "Playback", DA9055_DAC_L_CTRL, 7, 0), + SND_SOC_DAPM_DAC("DAC Right", "Playback", DA9055_DAC_R_CTRL, 7, 0), + + /* Output Mixers */ + SND_SOC_DAPM_MIXER("Out Mixer Left", SND_SOC_NOPM, 0, 0, + &da9055_dapm_mixoutl_controls[0], + ARRAY_SIZE(da9055_dapm_mixoutl_controls)), + SND_SOC_DAPM_MIXER("Out Mixer Right", SND_SOC_NOPM, 0, 0, + &da9055_dapm_mixoutr_controls[0], + ARRAY_SIZE(da9055_dapm_mixoutr_controls)), + + /* Output PGAs */ + SND_SOC_DAPM_PGA("MIXOUT Left", DA9055_MIXOUT_L_CTRL, 7, 0, NULL, 0), + SND_SOC_DAPM_PGA("MIXOUT Right", DA9055_MIXOUT_R_CTRL, 7, 0, NULL, 0), + SND_SOC_DAPM_PGA("Lineout", DA9055_LINE_CTRL, 7, 0, NULL, 0), + SND_SOC_DAPM_PGA("Headphone Left", DA9055_HP_L_CTRL, 7, 0, NULL, 0), + SND_SOC_DAPM_PGA("Headphone Right", DA9055_HP_R_CTRL, 7, 0, NULL, 0), + + /* Output Lines */ + SND_SOC_DAPM_OUTPUT("HPL"), + SND_SOC_DAPM_OUTPUT("HPR"), + SND_SOC_DAPM_OUTPUT("LINE"), +}; + +/* DAPM audio route definition */ +static const struct snd_soc_dapm_route da9055_audio_map[] = { + /* Dest Connecting Widget source */ + + /* Input path */ + {"Mic Left Source", "MIC1_P_N", "MIC1"}, + {"Mic Left Source", "MIC1_P", "MIC1"}, + {"Mic Left Source", "MIC1_N", "MIC1"}, + {"Mic Left Source", "MIC2_L", "MIC2"}, + + {"Mic Right Source", "MIC2_R_L", "MIC2"}, + {"Mic Right Source", "MIC2_R", "MIC2"}, + {"Mic Right Source", "MIC2_L", "MIC2"}, + + {"Mic Left", NULL, "Mic Left Source"}, + {"Mic Right", NULL, "Mic Right Source"}, + + {"Aux Left", NULL, "AUXL"}, + {"Aux Right", NULL, "AUXR"}, + + {"In Mixer Left", "Mic Left Switch", "Mic Left"}, + {"In Mixer Left", "Mic Right Switch", "Mic Right"}, + {"In Mixer Left", "Aux Left Switch", "Aux Left"}, + + {"In Mixer Right", "Mic Right Switch", "Mic Right"}, + {"In Mixer Right", "Mic Left Switch", "Mic Left"}, + {"In Mixer Right", "Aux Right Switch", "Aux Right"}, + {"In Mixer Right", "Mixin Left Switch", "MIXIN Left"}, + + {"MIXIN Left", NULL, "In Mixer Left"}, + {"ADC Left", NULL, "MIXIN Left"}, + + {"MIXIN Right", NULL, "In Mixer Right"}, + {"ADC Right", NULL, "MIXIN Right"}, + + {"ADC Left", NULL, "AIF"}, + {"ADC Right", NULL, "AIF"}, + + /* Output path */ + {"AIFIN Left", NULL, "AIF"}, + {"AIFIN Right", NULL, "AIF"}, + + {"DAC Left Source", "ADC output left", "ADC Left"}, + {"DAC Left Source", "ADC output right", "ADC Right"}, + {"DAC Left Source", "AIF input left", "AIFIN Left"}, + {"DAC Left Source", "AIF input right", "AIFIN Right"}, + + {"DAC Right Source", "ADC output left", "ADC Left"}, + {"DAC Right Source", "ADC output right", "ADC Right"}, + {"DAC Right Source", "AIF input left", "AIFIN Left"}, + {"DAC Right Source", "AIF input right", "AIFIN Right"}, + + {"DAC Left", NULL, "DAC Left Source"}, + {"DAC Right", NULL, "DAC Right Source"}, + + {"Out Mixer Left", "Aux Left Switch", "Aux Left"}, + {"Out Mixer Left", "Mixin Left Switch", "MIXIN Left"}, + {"Out Mixer Left", "Mixin Right Switch", "MIXIN Right"}, + {"Out Mixer Left", "Aux Left Invert Switch", "Aux Left"}, + {"Out Mixer Left", "Mixin Left Invert Switch", "MIXIN Left"}, + {"Out Mixer Left", "Mixin Right Invert Switch", "MIXIN Right"}, + {"Out Mixer Left", "DAC Left Switch", "DAC Left"}, + + {"Out Mixer Right", "Aux Right Switch", "Aux Right"}, + {"Out Mixer Right", "Mixin Right Switch", "MIXIN Right"}, + {"Out Mixer Right", "Mixin Left Switch", "MIXIN Left"}, + {"Out Mixer Right", "Aux Right Invert Switch", "Aux Right"}, + {"Out Mixer Right", "Mixin Right Invert Switch", "MIXIN Right"}, + {"Out Mixer Right", "Mixin Left Invert Switch", "MIXIN Left"}, + {"Out Mixer Right", "DAC Right Switch", "DAC Right"}, + + {"MIXOUT Left", NULL, "Out Mixer Left"}, + {"Headphone Left", NULL, "MIXOUT Left"}, + {"Headphone Left", NULL, "Charge Pump"}, + {"HPL", NULL, "Headphone Left"}, + + {"MIXOUT Right", NULL, "Out Mixer Right"}, + {"Headphone Right", NULL, "MIXOUT Right"}, + {"Headphone Right", NULL, "Charge Pump"}, + {"HPR", NULL, "Headphone Right"}, + + {"MIXOUT Right", NULL, "Out Mixer Right"}, + {"Lineout", NULL, "MIXOUT Right"}, + {"LINE", NULL, "Lineout"}, +}; + +/* Codec private data */ +struct da9055_priv { + struct regmap *regmap; + unsigned int mclk_rate; + int master; + struct da9055_platform_data *pdata; +}; + +static struct reg_default da9055_reg_defaults[] = { + { 0x21, 0x10 }, + { 0x22, 0x0A }, + { 0x23, 0x00 }, + { 0x24, 0x00 }, + { 0x25, 0x00 }, + { 0x26, 0x00 }, + { 0x27, 0x0C }, + { 0x28, 0x01 }, + { 0x29, 0x08 }, + { 0x2A, 0x32 }, + { 0x2B, 0x00 }, + { 0x30, 0x35 }, + { 0x31, 0x35 }, + { 0x32, 0x00 }, + { 0x33, 0x00 }, + { 0x34, 0x03 }, + { 0x35, 0x03 }, + { 0x36, 0x6F }, + { 0x37, 0x6F }, + { 0x38, 0x80 }, + { 0x39, 0x01 }, + { 0x3A, 0x01 }, + { 0x40, 0x00 }, + { 0x41, 0x88 }, + { 0x42, 0x88 }, + { 0x43, 0x08 }, + { 0x44, 0x80 }, + { 0x45, 0x6F }, + { 0x46, 0x6F }, + { 0x47, 0x61 }, + { 0x48, 0x35 }, + { 0x49, 0x35 }, + { 0x4A, 0x35 }, + { 0x4B, 0x00 }, + { 0x4C, 0x00 }, + { 0x60, 0x44 }, + { 0x61, 0x44 }, + { 0x62, 0x00 }, + { 0x63, 0x40 }, + { 0x64, 0x40 }, + { 0x65, 0x40 }, + { 0x66, 0x40 }, + { 0x67, 0x40 }, + { 0x68, 0x40 }, + { 0x69, 0x48 }, + { 0x6A, 0x40 }, + { 0x6B, 0x41 }, + { 0x6C, 0x40 }, + { 0x6D, 0x40 }, + { 0x6E, 0x10 }, + { 0x6F, 0x10 }, + { 0x90, 0x80 }, + { 0x92, 0x02 }, + { 0x93, 0x00 }, + { 0x99, 0x00 }, + { 0x9A, 0x00 }, + { 0x9B, 0x00 }, + { 0x9C, 0x3F }, + { 0x9D, 0x00 }, + { 0x9E, 0x3F }, + { 0x9F, 0xFF }, + { 0xA0, 0x71 }, + { 0xA1, 0x00 }, + { 0xA2, 0x00 }, + { 0xA6, 0x00 }, + { 0xA7, 0x00 }, + { 0xAB, 0x00 }, + { 0xAC, 0x00 }, + { 0xAD, 0x00 }, + { 0xAF, 0x08 }, + { 0xB0, 0x00 }, + { 0xB1, 0x00 }, + { 0xB2, 0x00 }, +}; + +static bool da9055_volatile_register(struct device *dev, + unsigned int reg) +{ + switch (reg) { + case DA9055_STATUS1: + case DA9055_PLL_STATUS: + case DA9055_AUX_L_GAIN_STATUS: + case DA9055_AUX_R_GAIN_STATUS: + case DA9055_MIC_L_GAIN_STATUS: + case DA9055_MIC_R_GAIN_STATUS: + case DA9055_MIXIN_L_GAIN_STATUS: + case DA9055_MIXIN_R_GAIN_STATUS: + case DA9055_ADC_L_GAIN_STATUS: + case DA9055_ADC_R_GAIN_STATUS: + case DA9055_DAC_L_GAIN_STATUS: + case DA9055_DAC_R_GAIN_STATUS: + case DA9055_HP_L_GAIN_STATUS: + case DA9055_HP_R_GAIN_STATUS: + case DA9055_LINE_GAIN_STATUS: + case DA9055_ALC_CIC_OP_LVL_DATA: + return 1; + default: + return 0; + } +} + +/* Set DAI word length */ +static int da9055_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 da9055_priv *da9055 = snd_soc_codec_get_drvdata(codec); + u8 aif_ctrl, fs; + u32 sysclk; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + aif_ctrl = DA9055_AIF_WORD_S16_LE; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + aif_ctrl = DA9055_AIF_WORD_S20_3LE; + break; + case SNDRV_PCM_FORMAT_S24_LE: + aif_ctrl = DA9055_AIF_WORD_S24_LE; + break; + case SNDRV_PCM_FORMAT_S32_LE: + aif_ctrl = DA9055_AIF_WORD_S32_LE; + break; + default: + return -EINVAL; + } + + /* Set AIF format */ + snd_soc_update_bits(codec, DA9055_AIF_CTRL, DA9055_AIF_WORD_LENGTH_MASK, + aif_ctrl); + + switch (params_rate(params)) { + case 8000: + fs = DA9055_SR_8000; + sysclk = 3072000; + break; + case 11025: + fs = DA9055_SR_11025; + sysclk = 2822400; + break; + case 12000: + fs = DA9055_SR_12000; + sysclk = 3072000; + break; + case 16000: + fs = DA9055_SR_16000; + sysclk = 3072000; + break; + case 22050: + fs = DA9055_SR_22050; + sysclk = 2822400; + break; + case 32000: + fs = DA9055_SR_32000; + sysclk = 3072000; + break; + case 44100: + fs = DA9055_SR_44100; + sysclk = 2822400; + break; + case 48000: + fs = DA9055_SR_48000; + sysclk = 3072000; + break; + case 88200: + fs = DA9055_SR_88200; + sysclk = 2822400; + break; + case 96000: + fs = DA9055_SR_96000; + sysclk = 3072000; + break; + default: + return -EINVAL; + } + + if (da9055->mclk_rate) { + /* PLL Mode, Write actual FS */ + snd_soc_write(codec, DA9055_SR, fs); + } else { + /* + * Non-PLL Mode + * When PLL is bypassed, chip assumes constant MCLK of + * 12.288MHz and uses sample rate value to divide this MCLK + * to derive its sys clk. As sys clk has to be 256 * Fs, we + * need to write constant sample rate i.e. 48KHz. + */ + snd_soc_write(codec, DA9055_SR, DA9055_SR_48000); + } + + if (da9055->mclk_rate && (da9055->mclk_rate != sysclk)) { + /* PLL Mode */ + if (!da9055->master) { + /* PLL slave mode, enable PLL and also SRM */ + snd_soc_update_bits(codec, DA9055_PLL_CTRL, + DA9055_PLL_EN | DA9055_PLL_SRM_EN, + DA9055_PLL_EN | DA9055_PLL_SRM_EN); + } else { + /* PLL master mode, only enable PLL */ + snd_soc_update_bits(codec, DA9055_PLL_CTRL, + DA9055_PLL_EN, DA9055_PLL_EN); + } + } else { + /* Non PLL Mode, disable PLL */ + snd_soc_update_bits(codec, DA9055_PLL_CTRL, DA9055_PLL_EN, 0); + } + + return 0; +} + +/* Set DAI mode and Format */ +static int da9055_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct da9055_priv *da9055 = snd_soc_codec_get_drvdata(codec); + u8 aif_clk_mode, aif_ctrl, mode; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + /* DA9055 in I2S Master Mode */ + mode = 1; + aif_clk_mode = DA9055_AIF_CLK_EN_MASTER_MODE; + break; + case SND_SOC_DAIFMT_CBS_CFS: + /* DA9055 in I2S Slave Mode */ + mode = 0; + aif_clk_mode = DA9055_AIF_CLK_EN_SLAVE_MODE; + break; + default: + return -EINVAL; + } + + /* Don't allow change of mode if PLL is enabled */ + if ((snd_soc_read(codec, DA9055_PLL_CTRL) & DA9055_PLL_EN) && + (da9055->master != mode)) + return -EINVAL; + + da9055->master = mode; + + /* Only I2S is supported */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + aif_ctrl = DA9055_AIF_FORMAT_I2S_MODE; + break; + case SND_SOC_DAIFMT_LEFT_J: + aif_ctrl = DA9055_AIF_FORMAT_LEFT_J; + break; + case SND_SOC_DAIFMT_RIGHT_J: + aif_ctrl = DA9055_AIF_FORMAT_RIGHT_J; + break; + default: + return -EINVAL; + } + + /* By default only 32 BCLK per WCLK is supported */ + aif_clk_mode |= DA9055_AIF_BCLKS_PER_WCLK_32; + + snd_soc_update_bits(codec, DA9055_AIF_CLK_MODE, + (DA9055_AIF_CLK_MODE_MASK | DA9055_AIF_BCLK_MASK), + aif_clk_mode); + snd_soc_update_bits(codec, DA9055_AIF_CTRL, DA9055_AIF_FORMAT_MASK, + aif_ctrl); + return 0; +} + +static int da9055_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + + if (mute) { + snd_soc_update_bits(codec, DA9055_DAC_L_CTRL, + DA9055_DAC_L_MUTE_EN, DA9055_DAC_L_MUTE_EN); + snd_soc_update_bits(codec, DA9055_DAC_R_CTRL, + DA9055_DAC_R_MUTE_EN, DA9055_DAC_R_MUTE_EN); + } else { + snd_soc_update_bits(codec, DA9055_DAC_L_CTRL, + DA9055_DAC_L_MUTE_EN, 0); + snd_soc_update_bits(codec, DA9055_DAC_R_CTRL, + DA9055_DAC_R_MUTE_EN, 0); + } + + return 0; +} + +#define DA9055_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static int da9055_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct da9055_priv *da9055 = snd_soc_codec_get_drvdata(codec); + + switch (clk_id) { + case DA9055_CLKSRC_MCLK: + switch (freq) { + case 11289600: + case 12000000: + case 12288000: + case 13000000: + case 13500000: + case 14400000: + case 19200000: + case 19680000: + case 19800000: + da9055->mclk_rate = freq; + return 0; + default: + dev_err(codec_dai->dev, "Unsupported MCLK value %d\n", + freq); + return -EINVAL; + } + break; + default: + dev_err(codec_dai->dev, "Unknown clock source %d\n", clk_id); + return -EINVAL; + } +} + +/* + * da9055_set_dai_pll : Configure the codec PLL + * @param codec_dai : Pointer to codec DAI + * @param pll_id : da9055 has only one pll, so pll_id is always zero + * @param fref : Input MCLK frequency + * @param fout : FsDM value + * @return int : Zero for success, negative error code for error + * + * Note: Supported PLL input frequencies are 11.2896MHz, 12MHz, 12.288MHz, + * 13MHz, 13.5MHz, 14.4MHz, 19.2MHz, 19.6MHz and 19.8MHz + */ +static int da9055_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + int source, unsigned int fref, unsigned int fout) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct da9055_priv *da9055 = snd_soc_codec_get_drvdata(codec); + + u8 pll_frac_top, pll_frac_bot, pll_integer, cnt; + + /* Disable PLL before setting the divisors */ + snd_soc_update_bits(codec, DA9055_PLL_CTRL, DA9055_PLL_EN, 0); + + /* In slave mode, there is only one set of divisors */ + if (!da9055->master && (fout != 2822400)) + goto pll_err; + + /* Search pll div array for correct divisors */ + for (cnt = 0; cnt < ARRAY_SIZE(da9055_pll_div); cnt++) { + /* Check fref, mode and fout */ + if ((fref == da9055_pll_div[cnt].fref) && + (da9055->master == da9055_pll_div[cnt].mode) && + (fout == da9055_pll_div[cnt].fout)) { + /* All match, pick up divisors */ + pll_frac_top = da9055_pll_div[cnt].frac_top; + pll_frac_bot = da9055_pll_div[cnt].frac_bot; + pll_integer = da9055_pll_div[cnt].integer; + break; + } + } + if (cnt >= ARRAY_SIZE(da9055_pll_div)) + goto pll_err; + + /* Write PLL dividers */ + snd_soc_write(codec, DA9055_PLL_FRAC_TOP, pll_frac_top); + snd_soc_write(codec, DA9055_PLL_FRAC_BOT, pll_frac_bot); + snd_soc_write(codec, DA9055_PLL_INTEGER, pll_integer); + + return 0; +pll_err: + dev_err(codec_dai->dev, "Error in setting up PLL\n"); + return -EINVAL; +} + +/* DAI operations */ +static const struct snd_soc_dai_ops da9055_dai_ops = { + .hw_params = da9055_hw_params, + .set_fmt = da9055_set_dai_fmt, + .set_sysclk = da9055_set_dai_sysclk, + .set_pll = da9055_set_dai_pll, + .digital_mute = da9055_mute, +}; + +static struct snd_soc_dai_driver da9055_dai = { + .name = "da9055-hifi", + /* Playback Capabilities */ + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = DA9055_FORMATS, + }, + /* Capture Capabilities */ + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = DA9055_FORMATS, + }, + .ops = &da9055_dai_ops, + .symmetric_rates = 1, +}; + +static int da9055_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) { + /* Enable VMID reference & master bias */ + snd_soc_update_bits(codec, DA9055_REFERENCES, + DA9055_VMID_EN | DA9055_BIAS_EN, + DA9055_VMID_EN | DA9055_BIAS_EN); + } + break; + case SND_SOC_BIAS_OFF: + /* Disable VMID reference & master bias */ + snd_soc_update_bits(codec, DA9055_REFERENCES, + DA9055_VMID_EN | DA9055_BIAS_EN, 0); + break; + } + codec->dapm.bias_level = level; + return 0; +} + +static int da9055_probe(struct snd_soc_codec *codec) +{ + int ret; + struct da9055_priv *da9055 = snd_soc_codec_get_drvdata(codec); + + codec->control_data = da9055->regmap; + ret = snd_soc_codec_set_cache_io(codec, 8, 8, SND_SOC_REGMAP); + if (ret < 0) { + dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); + return ret; + } + + /* Enable all Gain Ramps */ + snd_soc_update_bits(codec, DA9055_AUX_L_CTRL, + DA9055_GAIN_RAMPING_EN, DA9055_GAIN_RAMPING_EN); + snd_soc_update_bits(codec, DA9055_AUX_R_CTRL, + DA9055_GAIN_RAMPING_EN, DA9055_GAIN_RAMPING_EN); + snd_soc_update_bits(codec, DA9055_MIXIN_L_CTRL, + DA9055_GAIN_RAMPING_EN, DA9055_GAIN_RAMPING_EN); + snd_soc_update_bits(codec, DA9055_MIXIN_R_CTRL, + DA9055_GAIN_RAMPING_EN, DA9055_GAIN_RAMPING_EN); + snd_soc_update_bits(codec, DA9055_ADC_L_CTRL, + DA9055_GAIN_RAMPING_EN, DA9055_GAIN_RAMPING_EN); + snd_soc_update_bits(codec, DA9055_ADC_R_CTRL, + DA9055_GAIN_RAMPING_EN, DA9055_GAIN_RAMPING_EN); + snd_soc_update_bits(codec, DA9055_DAC_L_CTRL, + DA9055_GAIN_RAMPING_EN, DA9055_GAIN_RAMPING_EN); + snd_soc_update_bits(codec, DA9055_DAC_R_CTRL, + DA9055_GAIN_RAMPING_EN, DA9055_GAIN_RAMPING_EN); + snd_soc_update_bits(codec, DA9055_HP_L_CTRL, + DA9055_GAIN_RAMPING_EN, DA9055_GAIN_RAMPING_EN); + snd_soc_update_bits(codec, DA9055_HP_R_CTRL, + DA9055_GAIN_RAMPING_EN, DA9055_GAIN_RAMPING_EN); + snd_soc_update_bits(codec, DA9055_LINE_CTRL, + DA9055_GAIN_RAMPING_EN, DA9055_GAIN_RAMPING_EN); + + /* + * There are two separate control bits for input and output mixers as + * well as headphone and line outs. + * One to enable corresponding amplifier and other to enable its + * output. As amplifier bits are related to power control, they are + * being managed by DAPM while other (non power related) bits are + * enabled here + */ + snd_soc_update_bits(codec, DA9055_MIXIN_L_CTRL, + DA9055_MIXIN_L_MIX_EN, DA9055_MIXIN_L_MIX_EN); + snd_soc_update_bits(codec, DA9055_MIXIN_R_CTRL, + DA9055_MIXIN_R_MIX_EN, DA9055_MIXIN_R_MIX_EN); + + snd_soc_update_bits(codec, DA9055_MIXOUT_L_CTRL, + DA9055_MIXOUT_L_MIX_EN, DA9055_MIXOUT_L_MIX_EN); + snd_soc_update_bits(codec, DA9055_MIXOUT_R_CTRL, + DA9055_MIXOUT_R_MIX_EN, DA9055_MIXOUT_R_MIX_EN); + + snd_soc_update_bits(codec, DA9055_HP_L_CTRL, + DA9055_HP_L_AMP_OE, DA9055_HP_L_AMP_OE); + snd_soc_update_bits(codec, DA9055_HP_R_CTRL, + DA9055_HP_R_AMP_OE, DA9055_HP_R_AMP_OE); + + snd_soc_update_bits(codec, DA9055_LINE_CTRL, + DA9055_LINE_AMP_OE, DA9055_LINE_AMP_OE); + + /* Set this as per your system configuration */ + snd_soc_write(codec, DA9055_PLL_CTRL, DA9055_PLL_INDIV_10_20_MHZ); + + /* Set platform data values */ + if (da9055->pdata) { + /* set mic bias source */ + if (da9055->pdata->micbias_source) { + snd_soc_update_bits(codec, DA9055_MIXIN_R_SELECT, + DA9055_MICBIAS2_EN, + DA9055_MICBIAS2_EN); + } else { + snd_soc_update_bits(codec, DA9055_MIXIN_R_SELECT, + DA9055_MICBIAS2_EN, 0); + } + /* set mic bias voltage */ + switch (da9055->pdata->micbias) { + case DA9055_MICBIAS_2_2V: + case DA9055_MICBIAS_2_1V: + case DA9055_MICBIAS_1_8V: + case DA9055_MICBIAS_1_6V: + snd_soc_update_bits(codec, DA9055_MIC_CONFIG, + DA9055_MICBIAS_LEVEL_MASK, + (da9055->pdata->micbias) << 4); + break; + } + } + return 0; +} + +static struct snd_soc_codec_driver soc_codec_dev_da9055 = { + .probe = da9055_probe, + .set_bias_level = da9055_set_bias_level, + + .controls = da9055_snd_controls, + .num_controls = ARRAY_SIZE(da9055_snd_controls), + + .dapm_widgets = da9055_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(da9055_dapm_widgets), + .dapm_routes = da9055_audio_map, + .num_dapm_routes = ARRAY_SIZE(da9055_audio_map), +}; + +static const struct regmap_config da9055_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .reg_defaults = da9055_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(da9055_reg_defaults), + .volatile_reg = da9055_volatile_register, + .cache_type = REGCACHE_RBTREE, +}; + +static int __devinit da9055_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct da9055_priv *da9055; + struct da9055_platform_data *pdata = dev_get_platdata(&i2c->dev); + int ret; + + da9055 = devm_kzalloc(&i2c->dev, sizeof(struct da9055_priv), + GFP_KERNEL); + if (!da9055) + return -ENOMEM; + + if (pdata) + da9055->pdata = pdata; + + i2c_set_clientdata(i2c, da9055); + + da9055->regmap = devm_regmap_init_i2c(i2c, &da9055_regmap_config); + if (IS_ERR(da9055->regmap)) { + ret = PTR_ERR(da9055->regmap); + dev_err(&i2c->dev, "regmap_init() failed: %d\n", ret); + return ret; + } + + ret = snd_soc_register_codec(&i2c->dev, + &soc_codec_dev_da9055, &da9055_dai, 1); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to register da9055 codec: %d\n", + ret); + } + return ret; +} + +static int __devexit da9055_remove(struct i2c_client *client) +{ + snd_soc_unregister_codec(&client->dev); + return 0; +} + +static const struct i2c_device_id da9055_i2c_id[] = { + { "da9055", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, da9055_i2c_id); + +/* I2C codec control layer */ +static struct i2c_driver da9055_i2c_driver = { + .driver = { + .name = "da9055", + .owner = THIS_MODULE, + }, + .probe = da9055_i2c_probe, + .remove = __devexit_p(da9055_remove), + .id_table = da9055_i2c_id, +}; + +module_i2c_driver(da9055_i2c_driver); + +MODULE_DESCRIPTION("ASoC DA9055 Codec driver"); +MODULE_AUTHOR("David Chen, Ashish Chavan"); +MODULE_LICENSE("GPL");