diff options
| -rw-r--r-- | Documentation/devicetree/bindings/sound/cs35l32.txt | 62 | ||||
| -rw-r--r-- | include/dt-bindings/sound/cs35l32.h | 26 | ||||
| -rw-r--r-- | sound/soc/codecs/Kconfig | 5 | ||||
| -rw-r--r-- | sound/soc/codecs/Makefile | 2 | ||||
| -rw-r--r-- | sound/soc/codecs/ab8500-codec.c | 73 | ||||
| -rw-r--r-- | sound/soc/codecs/ac97.c | 15 | ||||
| -rw-r--r-- | sound/soc/codecs/cs35l32.c | 631 | ||||
| -rw-r--r-- | sound/soc/codecs/cs35l32.h | 93 | 
8 files changed, 852 insertions, 55 deletions
| diff --git a/Documentation/devicetree/bindings/sound/cs35l32.txt b/Documentation/devicetree/bindings/sound/cs35l32.txt new file mode 100644 index 000000000000..1417d3f5cc22 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/cs35l32.txt @@ -0,0 +1,62 @@ +CS35L32 audio CODEC + +Required properties: + +  - compatible : "cirrus,cs35l32" + +  - reg : the I2C address of the device for I2C. Address is determined by the level +  of the AD0 pin. Level 0 is 0x40 while Level 1 is 0x41. + +  - VA-supply, VP-supply : power supplies for the device, +  as covered in Documentation/devicetree/bindings/regulator/regulator.txt. + +Optional properties: + +  - reset-gpios : a GPIO spec for the reset pin. If specified, it will be +  deasserted before communication to the codec starts. + +  - cirrus,boost-manager : Boost voltage control. +  0 = Automatically managed.  Boost-converter output voltage is the higher +  of the two: Class G or adaptive LED voltage. +  1 = Automatically managed irrespective of audio, adapting for low-power +  dissipation when LEDs are ON, and operating in Fixed-Boost Bypass Mode +  if LEDs are OFF (VBST = VP). +  2 = (Default) Boost voltage fixed in Bypass Mode (VBST = VP). +  3 = Boost voltage fixed at 5 V. + +  - cirrus,sdout-datacfg : Data configuration for dual CS35L32 applications only. +  Determines the data packed in a two-CS35L32 configuration. +  0 = Left/right channels VMON[11:0], IMON[11:0], VPMON[7:0]. +  1 = Left/right channels VMON[11:0], IMON[11:0], STATUS. +  2 = (Default) left/right channels VMON[15:0], IMON [15:0]. +  3 = Left/right channels VPMON[7:0], STATUS. + +  - cirrus,sdout-share : SDOUT sharing. Determines whether one or two CS35L32 +  devices are on board sharing SDOUT. +  0 = (Default) One IC. +  1 = Two IC's. + +  - cirrus,battery-recovery : Low battery nominal recovery threshold, rising VP. +  0 = 3.1V +  1 = 3.2V +  2 = 3.3V (Default) +  3 = 3.4V + +  - cirrus,battery-threshold : Low battery nominal threshold, falling VP. +  0 = 3.1V +  1 = 3.2V +  2 = 3.3V +  3 = 3.4V (Default) +  4 = 3.5V +  5 = 3.6V + +Example: + +codec: codec@40 { +	compatible = "cirrus,cs35l32"; +	reg = <0x40>; +	reset-gpios = <&gpio 10 0>; +	cirrus,boost-manager = <0x03>; +	cirrus,sdout-datacfg = <0x02>; +	VA-supply = <®_audio>; +}; diff --git a/include/dt-bindings/sound/cs35l32.h b/include/dt-bindings/sound/cs35l32.h new file mode 100644 index 000000000000..0c6d6a3c15a2 --- /dev/null +++ b/include/dt-bindings/sound/cs35l32.h @@ -0,0 +1,26 @@ +#ifndef __DT_CS35L32_H +#define __DT_CS35L32_H + +#define CS35L32_BOOST_MGR_AUTO		0 +#define CS35L32_BOOST_MGR_AUTO_AUDIO	1 +#define CS35L32_BOOST_MGR_BYPASS	2 +#define CS35L32_BOOST_MGR_FIXED		3 + +#define CS35L32_DATA_CFG_LR_VP		0 +#define CS35L32_DATA_CFG_LR_STAT	1 +#define CS35L32_DATA_CFG_LR		2 +#define CS35L32_DATA_CFG_LR_VPSTAT	3 + +#define CS35L32_BATT_THRESH_3_1V	0 +#define CS35L32_BATT_THRESH_3_2V	1 +#define CS35L32_BATT_THRESH_3_3V	2 +#define CS35L32_BATT_THRESH_3_4V	3 + +#define CS35L32_BATT_RECOV_3_1V		0 +#define CS35L32_BATT_RECOV_3_2V		1 +#define CS35L32_BATT_RECOV_3_3V		2 +#define CS35L32_BATT_RECOV_3_4V		3 +#define CS35L32_BATT_RECOV_3_5V		4 +#define CS35L32_BATT_RECOV_3_6V		5 + +#endif /* __DT_CS35L32_H */ diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 8bca6343d8a3..184ee7e2a3da 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -43,6 +43,7 @@ config SND_SOC_ALL_CODECS  	select SND_SOC_ALC5623 if I2C  	select SND_SOC_ALC5632 if I2C  	select SND_SOC_CQ0093VC if MFD_DAVINCI_VOICECODEC +	select SND_SOC_CS35L32 if I2C  	select SND_SOC_CS42L51_I2C if I2C  	select SND_SOC_CS42L52 if I2C && INPUT  	select SND_SOC_CS42L56 if I2C && INPUT @@ -325,6 +326,10 @@ config SND_SOC_ALC5632  config SND_SOC_CQ0093VC  	tristate +config SND_SOC_CS35L32 +	tristate "Cirrus Logic CS35L32 CODEC" +	depends on I2C +  config SND_SOC_CS42L51  	tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 31a8283006d1..afba944657bc 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -32,6 +32,7 @@ snd-soc-ak4671-objs := ak4671.o  snd-soc-ak5386-objs := ak5386.o  snd-soc-arizona-objs := arizona.o  snd-soc-cq93vc-objs := cq93vc.o +snd-soc-cs35l32-objs := cs35l32.o  snd-soc-cs42l51-objs := cs42l51.o  snd-soc-cs42l51-i2c-objs := cs42l51-i2c.o  snd-soc-cs42l52-objs := cs42l52.o @@ -206,6 +207,7 @@ obj-$(CONFIG_SND_SOC_ALC5623)    += snd-soc-alc5623.o  obj-$(CONFIG_SND_SOC_ALC5632)	+= snd-soc-alc5632.o  obj-$(CONFIG_SND_SOC_ARIZONA)	+= snd-soc-arizona.o  obj-$(CONFIG_SND_SOC_CQ0093VC) += snd-soc-cq93vc.o +obj-$(CONFIG_SND_SOC_CS35L32)	+= snd-soc-cs35l32.o  obj-$(CONFIG_SND_SOC_CS42L51)	+= snd-soc-cs42l51.o  obj-$(CONFIG_SND_SOC_CS42L51_I2C)	+= snd-soc-cs42l51-i2c.o  obj-$(CONFIG_SND_SOC_CS42L52)	+= snd-soc-cs42l52.o diff --git a/sound/soc/codecs/ab8500-codec.c b/sound/soc/codecs/ab8500-codec.c index 1fb4402bf72d..fd43827bb856 100644 --- a/sound/soc/codecs/ab8500-codec.c +++ b/sound/soc/codecs/ab8500-codec.c @@ -56,8 +56,7 @@  #define GPIO31_DIR_OUTPUT			0x40  /* Macrocell register definitions */ -#define AB8500_CTRL3_REG			0x0200 -#define AB8500_GPIO_DIR4_REG			0x1013 +#define AB8500_GPIO_DIR4_REG			0x13 /* Bank AB8500_MISC */  /* Nr of FIR/IIR-coeff banks in ANC-block */  #define AB8500_NR_OF_ANC_COEFF_BANKS		2 @@ -126,6 +125,8 @@ struct ab8500_codec_drvdata_dbg {  /* Private data for AB8500 device-driver */  struct ab8500_codec_drvdata { +	struct regmap *regmap; +  	/* Sidetone */  	long *sid_fir_values;  	enum sid_state sid_status; @@ -166,49 +167,35 @@ static inline const char *amic_type_str(enum amic_type type)   */  /* Read a register from the audio-bank of AB8500 */ -static unsigned int ab8500_codec_read_reg(struct snd_soc_codec *codec, -					unsigned int reg) +static int ab8500_codec_read_reg(void *context, unsigned int reg, +				 unsigned int *value)  { +	struct device *dev = context;  	int status; -	unsigned int value = 0;  	u8 value8; -	status = abx500_get_register_interruptible(codec->dev, AB8500_AUDIO, -						reg, &value8); -	if (status < 0) { -		dev_err(codec->dev, -			"%s: ERROR: Register (0x%02x:0x%02x) read failed (%d).\n", -			__func__, (u8)AB8500_AUDIO, (u8)reg, status); -	} else { -		dev_dbg(codec->dev, -			"%s: Read 0x%02x from register 0x%02x:0x%02x\n", -			__func__, value8, (u8)AB8500_AUDIO, (u8)reg); -		value = (unsigned int)value8; -	} +	status = abx500_get_register_interruptible(dev, AB8500_AUDIO, +						   reg, &value8); +	*value = (unsigned int)value8; -	return value; +	return status;  }  /* Write to a register in the audio-bank of AB8500 */ -static int ab8500_codec_write_reg(struct snd_soc_codec *codec, -				unsigned int reg, unsigned int value) +static int ab8500_codec_write_reg(void *context, unsigned int reg, +				  unsigned int value)  { -	int status; - -	status = abx500_set_register_interruptible(codec->dev, AB8500_AUDIO, -						reg, value); -	if (status < 0) -		dev_err(codec->dev, -			"%s: ERROR: Register (%02x:%02x) write failed (%d).\n", -			__func__, (u8)AB8500_AUDIO, (u8)reg, status); -	else -		dev_dbg(codec->dev, -			"%s: Wrote 0x%02x into register %02x:%02x\n", -			__func__, (u8)value, (u8)AB8500_AUDIO, (u8)reg); +	struct device *dev = context; -	return status; +	return abx500_set_register_interruptible(dev, AB8500_AUDIO, +						 reg, value);  } +static const struct regmap_config ab8500_codec_regmap = { +	.reg_read = ab8500_codec_read_reg, +	.reg_write = ab8500_codec_write_reg, +}; +  /*   * Controls - DAPM   */ @@ -1968,16 +1955,16 @@ static int ab8500_audio_setup_mics(struct snd_soc_codec *codec,  	dev_dbg(codec->dev, "%s: Enter.\n", __func__);  	/* Set DMic-clocks to outputs */ -	status = abx500_get_register_interruptible(codec->dev, (u8)AB8500_MISC, -						(u8)AB8500_GPIO_DIR4_REG, +	status = abx500_get_register_interruptible(codec->dev, AB8500_MISC, +						AB8500_GPIO_DIR4_REG,  						&value8);  	if (status < 0)  		return status;  	value = value8 | GPIO27_DIR_OUTPUT | GPIO29_DIR_OUTPUT |  		GPIO31_DIR_OUTPUT;  	status = abx500_set_register_interruptible(codec->dev, -						(u8)AB8500_MISC, -						(u8)AB8500_GPIO_DIR4_REG, +						AB8500_MISC, +						AB8500_GPIO_DIR4_REG,  						value);  	if (status < 0)  		return status; @@ -2565,9 +2552,6 @@ static int ab8500_codec_probe(struct snd_soc_codec *codec)  static struct snd_soc_codec_driver ab8500_codec_driver = {  	.probe =		ab8500_codec_probe, -	.read =			ab8500_codec_read_reg, -	.write =		ab8500_codec_write_reg, -	.reg_word_size =	sizeof(u8),  	.controls =		ab8500_ctrls,  	.num_controls =		ARRAY_SIZE(ab8500_ctrls),  	.dapm_widgets =		ab8500_dapm_widgets, @@ -2592,6 +2576,15 @@ static int ab8500_codec_driver_probe(struct platform_device *pdev)  	drvdata->anc_status = ANC_UNCONFIGURED;  	dev_set_drvdata(&pdev->dev, drvdata); +	drvdata->regmap = devm_regmap_init(&pdev->dev, NULL, &pdev->dev, +					   &ab8500_codec_regmap); +	if (IS_ERR(drvdata->regmap)) { +		status = PTR_ERR(drvdata->regmap); +		dev_err(&pdev->dev, "%s: Failed to allocate regmap: %d\n", +			__func__, status); +		return status; +	} +  	dev_dbg(&pdev->dev, "%s: Register codec.\n", __func__);  	status = snd_soc_register_codec(&pdev->dev, &ab8500_codec_driver,  				ab8500_codec_dai, diff --git a/sound/soc/codecs/ac97.c b/sound/soc/codecs/ac97.c index e889e1b84192..bd9b1839c8b0 100644 --- a/sound/soc/codecs/ac97.c +++ b/sound/soc/codecs/ac97.c @@ -69,19 +69,6 @@ static struct snd_soc_dai_driver ac97_dai = {  	.ops = &ac97_dai_ops,  }; -static unsigned int ac97_read(struct snd_soc_codec *codec, -	unsigned int reg) -{ -	return soc_ac97_ops->read(codec->ac97, reg); -} - -static int ac97_write(struct snd_soc_codec *codec, unsigned int reg, -	unsigned int val) -{ -	soc_ac97_ops->write(codec->ac97, reg, val); -	return 0; -} -  static int ac97_soc_probe(struct snd_soc_codec *codec)  {  	struct snd_ac97_bus *ac97_bus; @@ -122,8 +109,6 @@ static int ac97_soc_resume(struct snd_soc_codec *codec)  #endif  static struct snd_soc_codec_driver soc_codec_dev_ac97 = { -	.write =	ac97_write, -	.read =		ac97_read,  	.probe = 	ac97_soc_probe,  	.suspend =	ac97_soc_suspend,  	.resume =	ac97_soc_resume, diff --git a/sound/soc/codecs/cs35l32.c b/sound/soc/codecs/cs35l32.c new file mode 100644 index 000000000000..c125925da92e --- /dev/null +++ b/sound/soc/codecs/cs35l32.c @@ -0,0 +1,631 @@ +/* + * cs35l32.c -- CS35L32 ALSA SoC audio driver + * + * Copyright 2014 CirrusLogic, Inc. + * + * Author: Brian Austin <brian.austin@cirrus.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/version.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/gpio.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/gpio/consumer.h> +#include <linux/of_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> +#include <sound/tlv.h> +#include <dt-bindings/sound/cs35l32.h> + +#include "cs35l32.h" + +#define CS35L32_NUM_SUPPLIES 2 +static const char *const cs35l32_supply_names[CS35L32_NUM_SUPPLIES] = { +	"VA", +	"VP", +}; + +struct  cs35l32_private { +	struct regmap *regmap; +	struct snd_soc_codec *codec; +	struct regulator_bulk_data supplies[CS35L32_NUM_SUPPLIES]; +	struct cs35l32_platform_data pdata; +	struct gpio_desc *reset_gpio; +}; + +static const struct reg_default cs35l32_reg_defaults[] = { + +	{ 0x06, 0x04 }, /* Power Ctl 1 */ +	{ 0x07, 0xE8 }, /* Power Ctl 2 */ +	{ 0x08, 0x40 }, /* Clock Ctl */ +	{ 0x09, 0x20 }, /* Low Battery Threshold */ +	{ 0x0A, 0x00 }, /* Voltage Monitor [RO] */ +	{ 0x0B, 0x40 }, /* Conv Peak Curr Protection CTL */ +	{ 0x0C, 0x07 }, /* IMON Scaling */ +	{ 0x0D, 0x03 }, /* Audio/LED Pwr Manager */ +	{ 0x0F, 0x20 }, /* Serial Port Control */ +	{ 0x10, 0x14 }, /* Class D Amp CTL */ +	{ 0x11, 0x00 }, /* Protection Release CTL */ +	{ 0x12, 0xFF }, /* Interrupt Mask 1 */ +	{ 0x13, 0xFF }, /* Interrupt Mask 2 */ +	{ 0x14, 0xFF }, /* Interrupt Mask 3 */ +	{ 0x19, 0x00 }, /* LED Flash Mode Current */ +	{ 0x1A, 0x00 }, /* LED Movie Mode Current */ +	{ 0x1B, 0x20 }, /* LED Flash Timer */ +	{ 0x1C, 0x00 }, /* LED Flash Inhibit Current */ +}; + +static bool cs35l32_readable_register(struct device *dev, unsigned int reg) +{ +	switch (reg) { +	case CS35L32_DEVID_AB: +	case CS35L32_DEVID_CD: +	case CS35L32_DEVID_E: +	case CS35L32_FAB_ID: +	case CS35L32_REV_ID: +	case CS35L32_PWRCTL1: +	case CS35L32_PWRCTL2: +	case CS35L32_CLK_CTL: +	case CS35L32_BATT_THRESHOLD: +	case CS35L32_VMON: +	case CS35L32_BST_CPCP_CTL: +	case CS35L32_IMON_SCALING: +	case CS35L32_AUDIO_LED_MNGR: +	case CS35L32_ADSP_CTL: +	case CS35L32_CLASSD_CTL: +	case CS35L32_PROTECT_CTL: +	case CS35L32_INT_MASK_1: +	case CS35L32_INT_MASK_2: +	case CS35L32_INT_MASK_3: +	case CS35L32_INT_STATUS_1: +	case CS35L32_INT_STATUS_2: +	case CS35L32_INT_STATUS_3: +	case CS35L32_LED_STATUS: +	case CS35L32_FLASH_MODE: +	case CS35L32_MOVIE_MODE: +	case CS35L32_FLASH_TIMER: +	case CS35L32_FLASH_INHIBIT: +		return true; +	default: +		return false; +	} +} + +static bool cs35l32_volatile_register(struct device *dev, unsigned int reg) +{ +	switch (reg) { +	case CS35L32_DEVID_AB: +	case CS35L32_DEVID_CD: +	case CS35L32_DEVID_E: +	case CS35L32_FAB_ID: +	case CS35L32_REV_ID: +	case CS35L32_INT_STATUS_1: +	case CS35L32_INT_STATUS_2: +	case CS35L32_INT_STATUS_3: +	case CS35L32_LED_STATUS: +		return true; +	default: +		return false; +	} +} + +static bool cs35l32_precious_register(struct device *dev, unsigned int reg) +{ +	switch (reg) { +	case CS35L32_INT_STATUS_1: +	case CS35L32_INT_STATUS_2: +	case CS35L32_INT_STATUS_3: +	case CS35L32_LED_STATUS: +		return true; +	default: +		return false; +	} +} + +static DECLARE_TLV_DB_SCALE(classd_ctl_tlv, 900, 300, 0); + +static const struct snd_kcontrol_new imon_ctl = +	SOC_DAPM_SINGLE("Switch", CS35L32_PWRCTL2, 6, 1, 1); + +static const struct snd_kcontrol_new vmon_ctl = +	SOC_DAPM_SINGLE("Switch", CS35L32_PWRCTL2, 7, 1, 1); + +static const struct snd_kcontrol_new vpmon_ctl = +	SOC_DAPM_SINGLE("Switch", CS35L32_PWRCTL2, 5, 1, 1); + +static const struct snd_kcontrol_new cs35l32_snd_controls[] = { +	SOC_SINGLE_TLV("Speaker Volume", CS35L32_CLASSD_CTL, +		       3, 0x04, 1, classd_ctl_tlv), +	SOC_SINGLE("Zero Cross Switch", CS35L32_CLASSD_CTL, 2, 1, 0), +	SOC_SINGLE("Gain Manager Switch", CS35L32_AUDIO_LED_MNGR, 3, 1, 0), +}; + +static const struct snd_soc_dapm_widget cs35l32_dapm_widgets[] = { + +	SND_SOC_DAPM_SUPPLY("BOOST", CS35L32_PWRCTL1, 2, 1, NULL, 0), +	SND_SOC_DAPM_OUT_DRV("Speaker", CS35L32_PWRCTL1, 7, 1, NULL, 0), + +	SND_SOC_DAPM_AIF_OUT("SDOUT", NULL, 0, CS35L32_PWRCTL2, 3, 1), + +	SND_SOC_DAPM_INPUT("VP"), +	SND_SOC_DAPM_INPUT("ISENSE"), +	SND_SOC_DAPM_INPUT("VSENSE"), + +	SND_SOC_DAPM_SWITCH("VMON ADC", CS35L32_PWRCTL2, 7, 1, &vmon_ctl), +	SND_SOC_DAPM_SWITCH("IMON ADC", CS35L32_PWRCTL2, 6, 1, &imon_ctl), +	SND_SOC_DAPM_SWITCH("VPMON ADC", CS35L32_PWRCTL2, 5, 1, &vpmon_ctl), +}; + +static const struct snd_soc_dapm_route cs35l32_audio_map[] = { + +	{"Speaker", NULL, "BOOST"}, + +	{"VMON ADC", NULL, "VSENSE"}, +	{"IMON ADC", NULL, "ISENSE"}, +	{"VPMON ADC", NULL, "VP"}, + +	{"SDOUT", "Switch", "VMON ADC"}, +	{"SDOUT",  "Switch", "IMON ADC"}, +	{"SDOUT", "Switch", "VPMON ADC"}, + +	{"Capture", NULL, "SDOUT"}, +}; + +static int cs35l32_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ +	struct snd_soc_codec *codec = codec_dai->codec; + +	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { +	case SND_SOC_DAIFMT_CBM_CFM: +		snd_soc_update_bits(codec, CS35L32_ADSP_CTL, +				    CS35L32_ADSP_MASTER_MASK, +				CS35L32_ADSP_MASTER_MASK); +		break; +	case SND_SOC_DAIFMT_CBS_CFS: +		snd_soc_update_bits(codec, CS35L32_ADSP_CTL, +				    CS35L32_ADSP_MASTER_MASK, 0); +		break; +	default: +		return -EINVAL; +	} + +	return 0; +} + +static int cs35l32_set_tristate(struct snd_soc_dai *dai, int tristate) +{ +	struct snd_soc_codec *codec = dai->codec; + +	return snd_soc_update_bits(codec, CS35L32_PWRCTL2, +					CS35L32_SDOUT_3ST, tristate << 3); +} + +static const struct snd_soc_dai_ops cs35l32_ops = { +	.set_fmt = cs35l32_set_dai_fmt, +	.set_tristate = cs35l32_set_tristate, +}; + +static struct snd_soc_dai_driver cs35l32_dai[] = { +	{ +		.name = "cs35l32-monitor", +		.id = 0, +		.capture = { +			.stream_name = "Capture", +			.channels_min = 2, +			.channels_max = 2, +			.rates = CS35L32_RATES, +			.formats = CS35L32_FORMATS, +		}, +		.ops = &cs35l32_ops, +		.symmetric_rates = 1, +	} +}; + +static int cs35l32_codec_set_sysclk(struct snd_soc_codec *codec, +			      int clk_id, int source, unsigned int freq, int dir) +{ +	unsigned int val; + +	switch (freq) { +	case 6000000: +		val = CS35L32_MCLK_RATIO; +		break; +	case 12000000: +		val = CS35L32_MCLK_DIV2_MASK | CS35L32_MCLK_RATIO; +		break; +	case 6144000: +		val = 0; +		break; +	case 12288000: +		val = CS35L32_MCLK_DIV2_MASK; +		break; +	default: +		return -EINVAL; +	} + +	return snd_soc_update_bits(codec, CS35L32_CLK_CTL, +			CS35L32_MCLK_DIV2_MASK | CS35L32_MCLK_RATIO_MASK, val); +} + +static struct snd_soc_codec_driver soc_codec_dev_cs35l32 = { +	.set_sysclk = cs35l32_codec_set_sysclk, + +	.dapm_widgets = cs35l32_dapm_widgets, +	.num_dapm_widgets = ARRAY_SIZE(cs35l32_dapm_widgets), +	.dapm_routes = cs35l32_audio_map, +	.num_dapm_routes = ARRAY_SIZE(cs35l32_audio_map), + +	.controls = cs35l32_snd_controls, +	.num_controls = ARRAY_SIZE(cs35l32_snd_controls), +}; + +/* Current and threshold powerup sequence Pg37 in datasheet */ +static const struct reg_default cs35l32_monitor_patch[] = { + +	{ 0x00, 0x99 }, +	{ 0x48, 0x17 }, +	{ 0x49, 0x56 }, +	{ 0x43, 0x01 }, +	{ 0x3B, 0x62 }, +	{ 0x3C, 0x80 }, +	{ 0x00, 0x00 }, +}; + +static struct regmap_config cs35l32_regmap = { +	.reg_bits = 8, +	.val_bits = 8, + +	.max_register = CS35L32_MAX_REGISTER, +	.reg_defaults = cs35l32_reg_defaults, +	.num_reg_defaults = ARRAY_SIZE(cs35l32_reg_defaults), +	.volatile_reg = cs35l32_volatile_register, +	.readable_reg = cs35l32_readable_register, +	.precious_reg = cs35l32_precious_register, +	.cache_type = REGCACHE_RBTREE, +}; + +static int cs35l32_handle_of_data(struct i2c_client *i2c_client, +				    struct cs35l32_platform_data *pdata) +{ +	struct device_node *np = i2c_client->dev.of_node; +	unsigned int val; + +	if (of_property_read_u32(np, "cirrus,sdout-share", &val) >= 0) +		pdata->sdout_share = val; + +	of_property_read_u32(np, "cirrus,boost-manager", &val); +	switch (val) { +	case CS35L32_BOOST_MGR_AUTO: +	case CS35L32_BOOST_MGR_AUTO_AUDIO: +	case CS35L32_BOOST_MGR_BYPASS: +	case CS35L32_BOOST_MGR_FIXED: +		pdata->boost_mng = val; +		break; +	default: +		dev_err(&i2c_client->dev, +			"Wrong cirrus,boost-manager DT value %d\n", val); +		pdata->boost_mng = CS35L32_BOOST_MGR_BYPASS; +	} + +	of_property_read_u32(np, "cirrus,sdout-datacfg", &val); +	switch (val) { +	case CS35L32_DATA_CFG_LR_VP: +	case CS35L32_DATA_CFG_LR_STAT: +	case CS35L32_DATA_CFG_LR: +	case CS35L32_DATA_CFG_LR_VPSTAT: +		pdata->sdout_datacfg = val; +		break; +	default: +		dev_err(&i2c_client->dev, +			"Wrong cirrus,sdout-datacfg DT value %d\n", val); +		pdata->sdout_datacfg = CS35L32_DATA_CFG_LR; +	} + +	of_property_read_u32(np, "cirrus,battery-threshold", &val); +	switch (val) { +	case CS35L32_BATT_THRESH_3_1V: +	case CS35L32_BATT_THRESH_3_2V: +	case CS35L32_BATT_THRESH_3_3V: +	case CS35L32_BATT_THRESH_3_4V: +		pdata->batt_thresh = val; +		break; +	default: +		dev_err(&i2c_client->dev, +			"Wrong cirrus,battery-threshold DT value %d\n", val); +		pdata->batt_thresh = CS35L32_BATT_THRESH_3_3V; +	} + +	of_property_read_u32(np, "cirrus,battery-recovery", &val); +	switch (val) { +	case CS35L32_BATT_RECOV_3_1V: +	case CS35L32_BATT_RECOV_3_2V: +	case CS35L32_BATT_RECOV_3_3V: +	case CS35L32_BATT_RECOV_3_4V: +	case CS35L32_BATT_RECOV_3_5V: +	case CS35L32_BATT_RECOV_3_6V: +		pdata->batt_recov = val; +		break; +	default: +		dev_err(&i2c_client->dev, +			"Wrong cirrus,battery-recovery DT value %d\n", val); +		pdata->batt_recov = CS35L32_BATT_RECOV_3_4V; +	} + +	return 0; +} + +static int cs35l32_i2c_probe(struct i2c_client *i2c_client, +				       const struct i2c_device_id *id) +{ +	struct cs35l32_private *cs35l32; +	struct cs35l32_platform_data *pdata = +		dev_get_platdata(&i2c_client->dev); +	int ret, i; +	unsigned int devid = 0; +	unsigned int reg; + + +	cs35l32 = devm_kzalloc(&i2c_client->dev, sizeof(struct cs35l32_private), +			       GFP_KERNEL); +	if (!cs35l32) { +		dev_err(&i2c_client->dev, "could not allocate codec\n"); +		return -ENOMEM; +	} + +	i2c_set_clientdata(i2c_client, cs35l32); + +	cs35l32->regmap = devm_regmap_init_i2c(i2c_client, &cs35l32_regmap); +	if (IS_ERR(cs35l32->regmap)) { +		ret = PTR_ERR(cs35l32->regmap); +		dev_err(&i2c_client->dev, "regmap_init() failed: %d\n", ret); +		return ret; +	} + +	if (pdata) { +		cs35l32->pdata = *pdata; +	} else { +		pdata = devm_kzalloc(&i2c_client->dev, +				     sizeof(struct cs35l32_platform_data), +				GFP_KERNEL); +		if (!pdata) { +			dev_err(&i2c_client->dev, "could not allocate pdata\n"); +			return -ENOMEM; +		} +		if (i2c_client->dev.of_node) { +			ret = cs35l32_handle_of_data(i2c_client, +						     &cs35l32->pdata); +			if (ret != 0) +				return ret; +		} +	} + +	for (i = 0; i < ARRAY_SIZE(cs35l32->supplies); i++) +		cs35l32->supplies[i].supply = cs35l32_supply_names[i]; + +	ret = devm_regulator_bulk_get(&i2c_client->dev, +				      ARRAY_SIZE(cs35l32->supplies), +				      cs35l32->supplies); +	if (ret != 0) { +		dev_err(&i2c_client->dev, +			"Failed to request supplies: %d\n", ret); +		return ret; +	} + +	ret = regulator_bulk_enable(ARRAY_SIZE(cs35l32->supplies), +				    cs35l32->supplies); +	if (ret != 0) { +		dev_err(&i2c_client->dev, +			"Failed to enable supplies: %d\n", ret); +		return ret; +	} + +	/* Reset the Device */ +	cs35l32->reset_gpio = devm_gpiod_get(&i2c_client->dev, +		"reset-gpios"); +	if (IS_ERR(cs35l32->reset_gpio)) { +		ret = PTR_ERR(cs35l32->reset_gpio); +		if (ret != -ENOENT && ret != -ENOSYS) +			return ret; + +		cs35l32->reset_gpio = NULL; +	} else { +		ret = gpiod_direction_output(cs35l32->reset_gpio, 0); +		if (ret) +			return ret; +		gpiod_set_value_cansleep(cs35l32->reset_gpio, 1); +	} + +	/* initialize codec */ +	ret = regmap_read(cs35l32->regmap, CS35L32_DEVID_AB, ®); +	devid = (reg & 0xFF) << 12; + +	ret = regmap_read(cs35l32->regmap, CS35L32_DEVID_CD, ®); +	devid |= (reg & 0xFF) << 4; + +	ret = regmap_read(cs35l32->regmap, CS35L32_DEVID_E, ®); +	devid |= (reg & 0xF0) >> 4; + +	if (devid != CS35L32_CHIP_ID) { +		ret = -ENODEV; +		dev_err(&i2c_client->dev, +			"CS35L32 Device ID (%X). Expected %X\n", +			devid, CS35L32_CHIP_ID); +		return ret; +	} + +	ret = regmap_read(cs35l32->regmap, CS35L32_REV_ID, ®); +	if (ret < 0) { +		dev_err(&i2c_client->dev, "Get Revision ID failed\n"); +		return ret; +	} + +	ret = regmap_register_patch(cs35l32->regmap, cs35l32_monitor_patch, +				    ARRAY_SIZE(cs35l32_monitor_patch)); +	if (ret < 0) { +		dev_err(&i2c_client->dev, "Failed to apply errata patch\n"); +		return ret; +	} + +	dev_info(&i2c_client->dev, +		 "Cirrus Logic CS35L32, Revision: %02X\n", reg & 0xFF); + +	/* Setup VBOOST Management */ +	if (cs35l32->pdata.boost_mng) +		regmap_update_bits(cs35l32->regmap, CS35L32_AUDIO_LED_MNGR, +				   CS35L32_BOOST_MASK, +				cs35l32->pdata.boost_mng); + +	/* Setup ADSP Format Config */ +	if (cs35l32->pdata.sdout_share) +		regmap_update_bits(cs35l32->regmap, CS35L32_ADSP_CTL, +				    CS35L32_ADSP_SHARE_MASK, +				cs35l32->pdata.sdout_share << 3); + +	/* Setup ADSP Data Configuration */ +	if (cs35l32->pdata.sdout_datacfg) +		regmap_update_bits(cs35l32->regmap, CS35L32_ADSP_CTL, +				   CS35L32_ADSP_DATACFG_MASK, +				cs35l32->pdata.sdout_datacfg << 4); + +	/* Setup Low Battery Recovery  */ +	if (cs35l32->pdata.batt_recov) +		regmap_update_bits(cs35l32->regmap, CS35L32_BATT_THRESHOLD, +				   CS35L32_BATT_REC_MASK, +				cs35l32->pdata.batt_recov << 1); + +	/* Setup Low Battery Threshold */ +	if (cs35l32->pdata.batt_thresh) +		regmap_update_bits(cs35l32->regmap, CS35L32_BATT_THRESHOLD, +				   CS35L32_BATT_THRESH_MASK, +				cs35l32->pdata.batt_thresh << 4); + +	/* Power down the AMP */ +	regmap_update_bits(cs35l32->regmap, CS35L32_PWRCTL1, CS35L32_PDN_AMP, +			    CS35L32_PDN_AMP); + +	/* Clear MCLK Error Bit since we don't have the clock yet */ +	ret = regmap_read(cs35l32->regmap, CS35L32_INT_STATUS_1, ®); + +	ret =  snd_soc_register_codec(&i2c_client->dev, +			&soc_codec_dev_cs35l32, cs35l32_dai, +			ARRAY_SIZE(cs35l32_dai)); +	if (ret < 0) +		goto err_disable; + +	return 0; + +err_disable: +	regulator_bulk_disable(ARRAY_SIZE(cs35l32->supplies), +			       cs35l32->supplies); +	return ret; +} + +static int cs35l32_i2c_remove(struct i2c_client *i2c_client) +{ +	struct cs35l32_private *cs35l32 = i2c_get_clientdata(i2c_client); + +	snd_soc_unregister_codec(&i2c_client->dev); + +	/* Hold down reset */ +	if (cs35l32->reset_gpio) +		gpiod_set_value_cansleep(cs35l32->reset_gpio, 0); + +	return 0; +} + +#ifdef CONFIG_PM_RUNTIME +static int cs35l32_runtime_suspend(struct device *dev) +{ +	struct cs35l32_private *cs35l32 = dev_get_drvdata(dev); + +	regcache_cache_only(cs35l32->regmap, true); +	regcache_mark_dirty(cs35l32->regmap); + +	/* Hold down reset */ +	if (cs35l32->reset_gpio) +		gpiod_set_value_cansleep(cs35l32->reset_gpio, 0); + +	/* remove power */ +	regulator_bulk_disable(ARRAY_SIZE(cs35l32->supplies), +			       cs35l32->supplies); + +	return 0; +} + +static int cs35l32_runtime_resume(struct device *dev) +{ +	struct cs35l32_private *cs35l32 = dev_get_drvdata(dev); +	int ret; + +	/* Enable power */ +	ret = regulator_bulk_enable(ARRAY_SIZE(cs35l32->supplies), +				    cs35l32->supplies); +	if (ret != 0) { +		dev_err(dev, "Failed to enable supplies: %d\n", +			ret); +		return ret; +	} + +	if (cs35l32->reset_gpio) +		gpiod_set_value_cansleep(cs35l32->reset_gpio, 1); + +	regcache_cache_only(cs35l32->regmap, false); +	regcache_sync(cs35l32->regmap); + +	return 0; +} +#endif + +static const struct dev_pm_ops cs35l32_runtime_pm = { +	SET_RUNTIME_PM_OPS(cs35l32_runtime_suspend, cs35l32_runtime_resume, +			   NULL) +}; + +static const struct of_device_id cs35l32_of_match[] = { +	{ .compatible = "cirrus,cs35l32", }, +	{}, +}; +MODULE_DEVICE_TABLE(of, cs35l32_of_match); + + +static const struct i2c_device_id cs35l32_id[] = { +	{"cs35l32", 0}, +	{} +}; + +MODULE_DEVICE_TABLE(i2c, cs35l32_id); + +static struct i2c_driver cs35l32_i2c_driver = { +	.driver = { +		   .name = "cs35l32", +		   .owner = THIS_MODULE, +		   .pm = &cs35l32_runtime_pm, +		   .of_match_table = cs35l32_of_match, +		   }, +	.id_table = cs35l32_id, +	.probe = cs35l32_i2c_probe, +	.remove = cs35l32_i2c_remove, +}; + +module_i2c_driver(cs35l32_i2c_driver); + +MODULE_DESCRIPTION("ASoC CS35L32 driver"); +MODULE_AUTHOR("Brian Austin, Cirrus Logic Inc, <brian.austin@cirrus.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs35l32.h b/sound/soc/codecs/cs35l32.h new file mode 100644 index 000000000000..31ab804a22bc --- /dev/null +++ b/sound/soc/codecs/cs35l32.h @@ -0,0 +1,93 @@ +/* + * cs35l32.h -- CS35L32 ALSA SoC audio driver + * + * Copyright 2014 CirrusLogic, Inc. + * + * Author: Brian Austin <brian.austin@cirrus.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#ifndef __CS35L32_H__ +#define __CS35L32_H__ + +struct cs35l32_platform_data { +	/* Low Battery Threshold */ +	unsigned int batt_thresh; +	/* Low Battery Recovery */ +	unsigned int batt_recov; +	/* LED Current Management*/ +	unsigned int led_mng; +	/* Audio Gain w/ LED */ +	unsigned int audiogain_mng; +	/* Boost Management */ +	unsigned int boost_mng; +	/* Data CFG for DUAL device */ +	unsigned int sdout_datacfg; +	/* SDOUT Sharing */ +	unsigned int sdout_share; +}; + +#define CS35L32_CHIP_ID		0x00035A32 +#define CS35L32_DEVID_AB	0x01	/* Device ID A & B [RO] */ +#define CS35L32_DEVID_CD	0x02    /* Device ID C & D [RO] */ +#define CS35L32_DEVID_E		0x03    /* Device ID E [RO] */ +#define CS35L32_FAB_ID		0x04	/* Fab ID [RO] */ +#define CS35L32_REV_ID		0x05	/* Revision ID [RO] */ +#define CS35L32_PWRCTL1		0x06    /* Power Ctl 1 */ +#define CS35L32_PWRCTL2		0x07    /* Power Ctl 2 */ +#define CS35L32_CLK_CTL		0x08	/* Clock Ctl */ +#define CS35L32_BATT_THRESHOLD	0x09	/* Low Battery Threshold */ +#define CS35L32_VMON		0x0A	/* Voltage Monitor [RO] */ +#define CS35L32_BST_CPCP_CTL	0x0B	/* Conv Peak Curr Protection CTL */ +#define CS35L32_IMON_SCALING	0x0C	/* IMON Scaling */ +#define CS35L32_AUDIO_LED_MNGR	0x0D	/* Audio/LED Pwr Manager */ +#define CS35L32_ADSP_CTL	0x0F	/* Serial Port Control */ +#define CS35L32_CLASSD_CTL	0x10	/* Class D Amp CTL */ +#define CS35L32_PROTECT_CTL	0x11	/* Protection Release CTL */ +#define CS35L32_INT_MASK_1	0x12	/* Interrupt Mask 1 */ +#define CS35L32_INT_MASK_2	0x13	/* Interrupt Mask 2 */ +#define CS35L32_INT_MASK_3	0x14	/* Interrupt Mask 3 */ +#define CS35L32_INT_STATUS_1	0x15	/* Interrupt Status 1 [RO] */ +#define CS35L32_INT_STATUS_2	0x16	/* Interrupt Status 2 [RO] */ +#define CS35L32_INT_STATUS_3	0x17	/* Interrupt Status 3 [RO] */ +#define CS35L32_LED_STATUS	0x18	/* LED Lighting Status [RO] */ +#define CS35L32_FLASH_MODE	0x19	/* LED Flash Mode Current */ +#define CS35L32_MOVIE_MODE	0x1A	/* LED Movie Mode Current */ +#define CS35L32_FLASH_TIMER	0x1B	/* LED Flash Timer */ +#define CS35L32_FLASH_INHIBIT	0x1C	/* LED Flash Inhibit Current */ +#define CS35L32_MAX_REGISTER	0x1C + +#define CS35L32_MCLK_DIV2	0x01 +#define CS35L32_MCLK_RATIO	0x01 +#define CS35L32_MCLKDIS		0x80 +#define CS35L32_PDN_ALL		0x01 +#define CS35L32_PDN_AMP		0x80 +#define CS35L32_PDN_BOOST	0x04 +#define CS35L32_PDN_IMON	0x40 +#define CS35L32_PDN_VMON	0x80 +#define CS35L32_PDN_VPMON	0x20 +#define CS35L32_PDN_ADSP	0x08 + +#define CS35L32_MCLK_DIV2_MASK		0x40 +#define CS35L32_MCLK_RATIO_MASK		0x01 +#define CS35L32_MCLK_MASK		0x41 +#define CS35L32_ADSP_MASTER_MASK	0x40 +#define CS35L32_BOOST_MASK		0x03 +#define CS35L32_GAIN_MGR_MASK		0x08 +#define CS35L32_ADSP_SHARE_MASK		0x08 +#define CS35L32_ADSP_DATACFG_MASK	0x30 +#define CS35L32_SDOUT_3ST		0x80 +#define CS35L32_BATT_REC_MASK		0x0E +#define CS35L32_BATT_THRESH_MASK	0x30 + +#define CS35L32_RATES (SNDRV_PCM_RATE_48000) +#define CS35L32_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ +			SNDRV_PCM_FMTBIT_S24_LE | \ +			SNDRV_PCM_FMTBIT_S32_LE) + + +#endif | 
