[alsa-devel] [PATCH] ASoC: tlv320aic32x4: Add SPI support
The TLV320AIC32x4 support the control channel over either SPI or I2C. Added the proper interfacing with regmap to make the driver able to handle either possibility.
Signed-off-by: Jeremy McDermond nh6z@nh6z.net --- sound/soc/codecs/Kconfig | 5 +- sound/soc/codecs/tlv320aic32x4.c | 181 +++++++++++++++++++++++++++++++++++---- 2 files changed, 165 insertions(+), 21 deletions(-)
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 907c804..1ddf7b8 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -129,7 +129,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_TLV320AIC23_SPI if SPI_MASTER select SND_SOC_TLV320AIC26 if SPI_MASTER select SND_SOC_TLV320AIC31XX if I2C - select SND_SOC_TLV320AIC32X4 if I2C + select SND_SOC_TLV320AIC32X4 if SND_SOC_I2C_AND_SPI select SND_SOC_TLV320AIC3X if I2C select SND_SOC_TPA6130A2 if I2C select SND_SOC_TLV320DAC33 if I2C @@ -768,7 +768,8 @@ config SND_SOC_TLV320AIC31XX select REGMAP_I2C
config SND_SOC_TLV320AIC32X4 - tristate + tristate "Texas Instruments TLV320AIC32x4 CODECs" + depends on SND_SOC_I2C_AND_SPI
config SND_SOC_TLV320AIC3X tristate "Texas Instruments TLV320AIC3x CODECs" diff --git a/sound/soc/codecs/tlv320aic32x4.c b/sound/soc/codecs/tlv320aic32x4.c index f2d3191..1c888b7 100644 --- a/sound/soc/codecs/tlv320aic32x4.c +++ b/sound/soc/codecs/tlv320aic32x4.c @@ -31,6 +31,7 @@ #include <linux/gpio.h> #include <linux/of_gpio.h> #include <linux/i2c.h> +#include <linux/spi/spi.h> #include <linux/cdev.h> #include <linux/slab.h> #include <linux/clk.h> @@ -287,15 +288,6 @@ static const struct regmap_range_cfg aic32x4_regmap_pages[] = { }, };
-static const struct regmap_config aic32x4_regmap = { - .reg_bits = 8, - .val_bits = 8, - - .max_register = AIC32X4_RMICPGAVOL, - .ranges = aic32x4_regmap_pages, - .num_ranges = ARRAY_SIZE(aic32x4_regmap_pages), -}; - static inline int aic32x4_get_divs(int mclk, int rate) { int i; @@ -668,7 +660,7 @@ static struct snd_soc_codec_driver soc_codec_dev_aic32x4 = { };
static int aic32x4_parse_dt(struct aic32x4_priv *aic32x4, - struct device_node *np) + struct device_node *np, struct device *dev) { aic32x4->swapdacs = false; aic32x4->micpga_routing = 0; @@ -777,6 +769,23 @@ error_ldo: return ret; }
+static const struct of_device_id aic32x4_of_id[] = { + { .compatible = "ti,tlv320aic32x4", }, + { /* senitel */ } +}; +MODULE_DEVICE_TABLE(of, aic32x4_of_id); + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + +static const struct regmap_config aic32x4_i2c_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = AIC32X4_RMICPGAVOL, + .ranges = aic32x4_regmap_pages, + .num_ranges = ARRAY_SIZE(aic32x4_regmap_pages), +}; + static int aic32x4_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { @@ -790,7 +799,7 @@ static int aic32x4_i2c_probe(struct i2c_client *i2c, if (aic32x4 == NULL) return -ENOMEM;
- aic32x4->regmap = devm_regmap_init_i2c(i2c, &aic32x4_regmap); + aic32x4->regmap = devm_regmap_init_i2c(i2c, &aic32x4_i2c_regmap); if (IS_ERR(aic32x4->regmap)) return PTR_ERR(aic32x4->regmap);
@@ -802,7 +811,7 @@ static int aic32x4_i2c_probe(struct i2c_client *i2c, aic32x4->micpga_routing = pdata->micpga_routing; aic32x4->rstn_gpio = pdata->rstn_gpio; } else if (np) { - ret = aic32x4_parse_dt(aic32x4, np); + ret = aic32x4_parse_dt(aic32x4, np, &i2c->dev); if (ret) { dev_err(&i2c->dev, "Failed to parse DT node\n"); return ret; @@ -862,12 +871,6 @@ static const struct i2c_device_id aic32x4_i2c_id[] = { }; MODULE_DEVICE_TABLE(i2c, aic32x4_i2c_id);
-static const struct of_device_id aic32x4_of_id[] = { - { .compatible = "ti,tlv320aic32x4", }, - { /* senitel */ } -}; -MODULE_DEVICE_TABLE(of, aic32x4_of_id); - static struct i2c_driver aic32x4_i2c_driver = { .driver = { .name = "tlv320aic32x4", @@ -877,8 +880,148 @@ static struct i2c_driver aic32x4_i2c_driver = { .remove = aic32x4_i2c_remove, .id_table = aic32x4_i2c_id, }; +#endif
-module_i2c_driver(aic32x4_i2c_driver); +#if defined(CONFIG_SPI_MASTER) + +static const struct regmap_config aic32x4_spi_regmap = { + .reg_bits = 7, + .pad_bits = 1, + .val_bits = 8, + .read_flag_mask = 0x01, + + .max_register = AIC32X4_RMICPGAVOL, + .ranges = aic32x4_regmap_pages, + .num_ranges = ARRAY_SIZE(aic32x4_regmap_pages), +}; + +static int aic32x4_spi_probe(struct spi_device *spi) +{ + struct aic32x4_pdata *pdata = spi->dev.platform_data; + struct aic32x4_priv *aic32x4; + struct device_node *np = spi->dev.of_node; + int ret; + + aic32x4 = devm_kzalloc(&spi->dev, sizeof(struct aic32x4_priv), + GFP_KERNEL); + if (aic32x4 == NULL) + return -ENOMEM; + + aic32x4->regmap = devm_regmap_init_spi(spi, &aic32x4_spi_regmap); + if (IS_ERR(aic32x4->regmap)) + return PTR_ERR(aic32x4->regmap); + + spi_set_drvdata(spi, aic32x4); + + if (pdata) { + aic32x4->power_cfg = pdata->power_cfg; + aic32x4->swapdacs = pdata->swapdacs; + aic32x4->micpga_routing = pdata->micpga_routing; + aic32x4->rstn_gpio = pdata->rstn_gpio; + } else if (np) { + ret = aic32x4_parse_dt(aic32x4, np, &spi->dev); + if (ret) { + dev_err(&spi->dev, "Failed to parse DT node\n"); + return ret; + } + } else { + aic32x4->power_cfg = 0; + aic32x4->swapdacs = false; + aic32x4->micpga_routing = 0; + aic32x4->rstn_gpio = -1; + } + + aic32x4->mclk = devm_clk_get(&spi->dev, "mclk"); + if (IS_ERR(aic32x4->mclk)) { + dev_err(&spi->dev, "Failed getting the mclk. The current implementation does not support the usage of this codec without mclk\n"); + return PTR_ERR(aic32x4->mclk); + } + + if (gpio_is_valid(aic32x4->rstn_gpio)) { + ret = devm_gpio_request_one(&spi->dev, aic32x4->rstn_gpio, + GPIOF_OUT_INIT_LOW, "tlv320aic32x4 rstn"); + if (ret != 0) + return ret; + } + + ret = aic32x4_setup_regulators(&spi->dev, aic32x4); + if (ret) { + dev_err(&spi->dev, "Failed to setup regulators\n"); + return ret; + } + + ret = snd_soc_register_codec(&spi->dev, + &soc_codec_dev_aic32x4, &aic32x4_dai, 1); + if (ret) { + dev_err(&spi->dev, "Failed to register codec\n"); + aic32x4_disable_regulators(aic32x4); + return ret; + } + + spi_set_drvdata(spi, aic32x4); + + return 0; +} + +static int aic32x4_spi_remove(struct spi_device *dev) +{ + struct aic32x4_priv *aic32x4 = spi_get_drvdata(dev); + + aic32x4_disable_regulators(aic32x4); + + snd_soc_unregister_codec(&dev->dev); + return 0; +} + +static const struct spi_device_id aic32x4_spi_id[] = { + { "tlv320aic32x4", 0 }, + { } +}; +MODULE_DEVICE_TABLE(spi, aic32x4_spi_id); + +static struct spi_driver aic32x4_spi_driver = { + .driver = { + .name = "tlv320aic32x4", + .owner = THIS_MODULE, + .of_match_table = aic32x4_of_id, + }, + .probe = aic32x4_spi_probe, + .remove = aic32x4_spi_remove, + .id_table = aic32x4_spi_id, +}; +#endif + +static int __init aic32x4_modinit(void) +{ + int ret = 0; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + ret = i2c_add_driver(&aic32x4_i2c_driver); + if (ret) + pr_err("Failed to register tlv320aic32x4 I2C driver: %d\n", + ret); +#endif +#if defined(CONFIG_SPI_MASTER) + ret = spi_register_driver(&aic32x4_spi_driver); + if (ret) + pr_err("Failed to register tlv320aic32x4 SPI driver: %d\n", + ret); +#endif + + return ret; +} +module_init(aic32x4_modinit); + +static void __exit aic32x4_exit(void) +{ +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_del_driver(&aic32x4_i2c_driver); +#endif +#if defined(CONFIG_SPI_MASTER) + spi_unregister_driver(&aic32x4_spi_driver); +#endif +} +module_exit(aic32x4_exit);
MODULE_DESCRIPTION("ASoC tlv320aic32x4 codec driver"); MODULE_AUTHOR("Javier Martin javier.martin@vista-silicon.com");
On Fri, Apr 15, 2016 at 11:30:59AM -0700, Jeremy McDermond wrote:
The TLV320AIC32x4 support the control channel over either SPI or I2C. Added the proper interfacing with regmap to make the driver able to handle either possibility.
Signed-off-by: Jeremy McDermond nh6z@nh6z.net
sound/soc/codecs/Kconfig | 5 +- sound/soc/codecs/tlv320aic32x4.c | 181 +++++++++++++++++++++++++++++++++++---- 2 files changed, 165 insertions(+), 21 deletions(-)
Instead of putting everything in one file please split the driver so it has separate core and I2C modules and then add a SPI module like we do for newer drivers.
config SND_SOC_TLV320AIC32X4
- tristate
- tristate "Texas Instruments TLV320AIC32x4 CODECs"
- depends on SND_SOC_I2C_AND_SPI
Making the driver directly user selectable is a separate change and should be in a separate patch.
On Apr 18, 2016, at 3:35 AM, Mark Brown broonie@kernel.org wrote:
On Fri, Apr 15, 2016 at 11:30:59AM -0700, Jeremy McDermond wrote:
The TLV320AIC32x4 support the control channel over either SPI or I2C. Added the proper interfacing with regmap to make the driver able to handle either possibility.
Signed-off-by: Jeremy McDermond nh6z@nh6z.net
sound/soc/codecs/Kconfig | 5 +- sound/soc/codecs/tlv320aic32x4.c | 181 +++++++++++++++++++++++++++++++++++---- 2 files changed, 165 insertions(+), 21 deletions(-)
Instead of putting everything in one file please split the driver so it has separate core and I2C modules and then add a SPI module like we do for newer drivers.
No problem. Thanks Mark. Would you like the separation of the I2C code to be in a separate patch set, or is one patch set with two separate patches fine?
config SND_SOC_TLV320AIC32X4
- tristate
- tristate "Texas Instruments TLV320AIC32x4 CODECs"
- depends on SND_SOC_I2C_AND_SPI
Making the driver directly user selectable is a separate change and should be in a separate patch.
I should have reviewed my submission better. This was for testing only. Sorry about that.
-- Jeremy McDermond (NH6Z) Xenotropic Systems mcdermj@xenotropic.com
On Mon, Apr 18, 2016 at 07:45:35AM -0700, Jeremy McDermond wrote:
Please fix your mail client to word wrap within paragraphs at something substantially less than 80 columns. Doing this makes your messages much easier to read and reply to.
On Apr 18, 2016, at 3:35 AM, Mark Brown broonie@kernel.org wrote:
Instead of putting everything in one file please split the driver so it has separate core and I2C modules and then add a SPI module like we do for newer drivers.
No problem. Thanks Mark. Would you like the separation of the I2C code to be in a separate patch set, or is one patch set with two separate patches fine?
Separate patch please.
participants (2)
-
Jeremy McDermond
-
Mark Brown