| /* |
| * tas5782m.c - Driver for the TAS5782M Audio Amplifier |
| * |
| * Copyright (C) 2017 Texas Instruments Incorporated - http://www.ti.com |
| * |
| * Author: Andy Liu <andy-liu@ti.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. |
| * |
| * This program is distributed in the hope that it will be useful, but |
| * WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * General Public License for more details. |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/moduleparam.h> |
| #include <linux/slab.h> |
| #include <linux/of.h> |
| #include <linux/init.h> |
| #include <linux/i2c.h> |
| #include <linux/regmap.h> |
| #include <linux/delay.h> |
| #include <linux/gpio.h> |
| #include <linux/regulator/consumer.h> |
| #include <linux/spi/spi.h> |
| #include <linux/of.h> |
| #include <linux/of_device.h> |
| #include <linux/of_gpio.h> |
| |
| |
| #include <sound/soc.h> |
| #include <sound/pcm.h> |
| #include <sound/initval.h> |
| |
| #include "tas5782m.h" |
| |
| |
| #define TAS5872M_DRV_NAME "tas5782m" |
| |
| #define TAS5872M_RATES (SNDRV_PCM_RATE_8000_96000) |
| #define TAS5782M_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ |
| SNDRV_PCM_FMTBIT_S20_3LE |\ |
| SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) |
| |
| #define TAS5782M_REG_00 (0x00) |
| #define TAS5782M_REG_03 (0x03) |
| #define TAS5782M_REG_7F (0x7F) |
| #define TAS5782M_REG_14 (0x14) |
| #define TAS5782M_REG_15 (0x15) |
| #define TAS5782M_REG_16 (0x16) |
| #define TAS5782M_REG_17 (0x17) |
| |
| #define TAS5782M_REG_44 (0x44) |
| #define TAS5782M_REG_45 (0x45) |
| #define TAS5782M_REG_46 (0x46) |
| #define TAS5782M_REG_47 (0x47) |
| #define TAS5782M_REG_48 (0x48) |
| #define TAS5782M_REG_49 (0x49) |
| #define TAS5782M_REG_4A (0x4A) |
| #define TAS5782M_REG_4B (0x4B) |
| |
| #define TAS5782M_PAGE_00 (0x00) |
| #define TAS5782M_PAGE_8C (0x8C) |
| #define TAS5782M_PAGE_1E (0x1E) |
| #define TAS5782M_PAGE_23 (0x23) |
| |
| static const struct reg_sequence tas5782m_init_sequence[] = { |
| { 0x00, 0x00 }, |
| { 0x7f, 0x00 }, |
| { 0x02, 0x11 }, |
| { 0x01, 0x11 }, |
| { 0x00, 0x00 }, |
| { 0x03, 0x11 }, |
| { 0x2a, 0x00 }, |
| { 0x25, 0x18 }, |
| { 0x0d, 0x10 }, |
| { 0x00, 0x00 }, |
| { 0x14, 0x00 }, |
| { 0x15, 0x00 }, |
| { 0x16, 0x00 }, |
| { 0x17, 0x01 }, |
| { 0x00, 0x00 }, |
| { 0x7f, 0x00 }, |
| { 0x07, 0x00 }, |
| { 0x08, 0x20 }, |
| { 0x55, 0x07 }, |
| { 0x3c, 0x01 }, |
| { 0x3d, 0x4e }, |
| { 0x28, 0x03 }, |
| { 0x09, 0x00 }, |
| { 0x00, 0x00 }, |
| { 0x7f, 0x00 }, |
| { 0x02, 0x00 }, |
| { 0x03, 0x00 }, |
| { 0x2a, 0x11 }, |
| }; |
| |
| static const uint32_t tas5782m_volume[] = { |
| 0x07ECA9CD, |
| 0x07100C4D, |
| 0x064B6CAE, |
| 0x059C2F02, |
| 0x05000000, |
| 0x0474CD1B, |
| 0x03F8BD7A, |
| 0x038A2BAD, |
| 0x0327A01A, |
| 0x02CFCC01, |
| 0x02818508, |
| 0x023BC148, |
| 0x01FD93C2, |
| 0x01C62940, |
| 0x0194C584, |
| 0x0168C0C6, |
| 0x0141857F, |
| 0x011E8E6A, |
| 0x00FF64C1, |
| 0x00E39EA9, |
| 0x00CADDC8, |
| 0x00B4CE08, |
| 0x00A12478, |
| 0x008F9E4D, |
| 0x00800000, |
| 0x00721483, |
| 0x0065AC8C, |
| 0x005A9DF8, |
| 0x0050C336, |
| 0x0047FACD, |
| 0x004026E7, |
| 0x00392CEE, |
| 0x0032F52D, |
| 0x002D6A86, |
| 0x00287A27, |
| 0x00241347, |
| 0x002026F3, |
| 0x001CA7D7, |
| 0x00198A13, |
| 0x0016C311, |
| 0x00144961, |
| 0x0012149A, |
| 0x00101D3F, |
| 0x000E5CA1, |
| 0x000CCCCD, |
| 0x000B6873, |
| 0x000A2ADB, |
| 0x00090FCC, |
| 0x00081385, |
| 0x000732AE, |
| 0x00066A4A, |
| 0x0005B7B1, |
| 0x00051884, |
| 0x00048AA7, |
| 0x00040C37, |
| 0x00039B87, |
| 0x00033718, |
| 0x0002DD96, |
| 0x00028DCF, |
| 0x000246B5, |
| 0x00020756, |
| 0x0001CEDC, |
| 0x00019C86, |
| 0x00016FAA, |
| 0x000147AE, |
| 0x0001240C, |
| 0x00010449, |
| 0x0000E7FB, |
| 0x0000CEC1, |
| 0x0000B845, |
| 0x0000A43B, |
| 0x0000925F, |
| 0x00008274, |
| 0x00007444, |
| 0x0000679F, |
| 0x00005C5A, |
| 0x0000524F, |
| 0x0000495C, |
| 0x00004161, |
| 0x00003A45, |
| 0x000033EF, |
| 0x00002E49, |
| 0x00002941, |
| 0x000024C4, |
| 0x000020C5, |
| 0x00001D34, |
| 0x00001A07, |
| 0x00001733, |
| 0x000014AD, |
| 0x0000126D, |
| 0x0000106C, |
| 0x00000EA3, |
| 0x00000D0C, |
| 0x00000BA0, |
| 0x00000A5D, |
| 0x0000093C, |
| 0x0000083B, |
| 0x00000756, |
| 0x0000068A, |
| 0x000005D4, |
| 0x00000532, |
| 0x000004A1, |
| 0x00000420, |
| 0x000003AD, |
| 0x00000347, |
| 0x000002EC, |
| 0x0000029A, |
| 0x00000252, |
| 0x00000211, |
| 0x000001D8, |
| 0x000001A4, |
| 0x00000177, |
| 0x0000014E, |
| 0x0000012A, |
| 0x00000109, |
| 0x000000EC, |
| 0x000000D3, |
| 0x000000BC, |
| 0x000000A7, |
| 0x00000095, |
| 0x00000085, |
| 0x00000076, |
| 0x0000006A, |
| 0x0000005E, |
| 0x00000054, |
| 0x0000004B, |
| 0x00000043, |
| 0x0000003B, |
| 0x00000035, |
| 0x0000002F, |
| 0x0000002A, |
| 0x00000025, |
| 0x00000021, |
| 0x0000001E, |
| 0x0000001B, |
| }; |
| |
| struct tas5782m_priv { |
| struct regmap *regmap; |
| struct regulator *extamp_supply; |
| |
| int gpio_reset; |
| int gpio_power; |
| int volume_db; |
| }; |
| |
| const struct regmap_config tas5782m_regmap = { |
| .reg_bits = 8, |
| .val_bits = 8, |
| .cache_type = REGCACHE_RBTREE, |
| }; |
| static inline int get_db(int index) |
| { |
| /*input : 0 -- 134*/ |
| /*output : 24 -- -110*/ |
| |
| return (24 - index); |
| } |
| |
| static inline unsigned int get_volume_index(int vol) |
| { |
| /*input : 24 -- -110*/ |
| /*output : 0 -- 134*/ |
| return (24 - vol); |
| } |
| |
| static void tas5782m_set_volume(struct snd_soc_codec *codec, int db) |
| { |
| unsigned int index; |
| uint32_t volume_hex; |
| uint8_t byte4; |
| uint8_t byte3; |
| uint8_t byte2; |
| uint8_t byte1; |
| |
| index = get_volume_index(db); |
| volume_hex = tas5782m_volume[index]; |
| |
| byte4 = ((volume_hex >> 24) & 0xFF); |
| byte3 = ((volume_hex >> 16) & 0xFF); |
| byte2 = ((volume_hex >> 8) & 0xFF); |
| byte1 = ((volume_hex >> 0) & 0xFF); |
| #if 1 |
| /*w 90 00 00*/ |
| snd_soc_write(codec, TAS5782M_REG_00, TAS5782M_PAGE_00); |
| /*w 90 7f 8c*/ |
| snd_soc_write(codec, TAS5782M_REG_7F, TAS5782M_PAGE_8C); |
| /*w 90 00 1e*/ |
| snd_soc_write(codec, TAS5782M_REG_00, TAS5782M_PAGE_1E); |
| /*w 90 44 xx xx xx xx*/ |
| snd_soc_write(codec, TAS5782M_REG_44, byte4); |
| snd_soc_write(codec, TAS5782M_REG_45, byte3); |
| snd_soc_write(codec, TAS5782M_REG_46, byte2); |
| snd_soc_write(codec, TAS5782M_REG_47, byte1); |
| |
| /*w 90 00 00*/ |
| snd_soc_write(codec, TAS5782M_REG_00, TAS5782M_PAGE_00); |
| /*w 90 7f 8c*/ |
| snd_soc_write(codec, TAS5782M_REG_7F, TAS5782M_PAGE_8C); |
| /*w 90 00 1e*/ |
| snd_soc_write(codec, TAS5782M_REG_00, TAS5782M_PAGE_1E); |
| /*w 90 48 xx xx xx xx*/ |
| snd_soc_write(codec, TAS5782M_REG_48, byte4); |
| snd_soc_write(codec, TAS5782M_REG_49, byte3); |
| snd_soc_write(codec, TAS5782M_REG_4A, byte2); |
| snd_soc_write(codec, TAS5782M_REG_4B, byte1); |
| |
| /*w 90 00 00*/ |
| snd_soc_write(codec, TAS5782M_REG_00, TAS5782M_PAGE_00); |
| /*w 90 7f 8c*/ |
| snd_soc_write(codec, TAS5782M_REG_7F, TAS5782M_PAGE_8C); |
| /*w 90 00 23*/ |
| snd_soc_write(codec, TAS5782M_REG_00, TAS5782M_PAGE_23); |
| /*w 90 14 00 00 00 01*/ |
| snd_soc_write(codec, TAS5782M_REG_14, 0x00); |
| snd_soc_write(codec, TAS5782M_REG_15, 0x00); |
| snd_soc_write(codec, TAS5782M_REG_16, 0x00); |
| snd_soc_write(codec, TAS5782M_REG_17, 0x01); |
| #endif |
| } |
| |
| static int tas5782m_snd_probe(struct snd_soc_codec *codec) |
| { |
| tas5782m_set_volume(codec, 1); |
| |
| return 0; |
| } |
| |
| static struct snd_soc_codec_driver soc_codec_tas5782m = { |
| .probe = tas5782m_snd_probe, |
| }; |
| |
| static int tas5782m_mute(struct snd_soc_dai *dai, int mute) |
| { |
| |
| u8 reg3_value = 0; |
| struct snd_soc_codec *codec = dai->codec; |
| |
| if (mute) |
| reg3_value = 0x11; |
| |
| snd_soc_write(codec, TAS5782M_REG_00, 0x00); |
| snd_soc_write(codec, TAS5782M_REG_7F, 0x00); |
| snd_soc_write(codec, TAS5782M_REG_03, reg3_value); |
| |
| return 0; |
| } |
| |
| static void tas5782m_reset(struct device *dev, struct tas5782m_priv *priv) |
| { |
| if (gpio_is_valid(priv->gpio_reset)) { |
| gpio_direction_output(priv->gpio_reset, 1); |
| gpio_set_value(priv->gpio_reset, 0); |
| } |
| } |
| |
| static void tas5782m_power(struct device *dev, |
| struct tas5782m_priv *priv, bool enable) |
| { |
| if (gpio_is_valid(priv->gpio_power)) { |
| if (enable) |
| gpio_direction_output(priv->gpio_power, 1); |
| else |
| gpio_direction_output(priv->gpio_power, 0); |
| } |
| } |
| |
| static const struct snd_soc_dai_ops tas5782m_dai_ops = { |
| .digital_mute = tas5782m_mute, |
| }; |
| |
| static struct snd_soc_dai_driver tas5782m_dai = { |
| .name = "tas5782m-amplifier", |
| .playback = { |
| .stream_name = "Playback", |
| .channels_min = 2, |
| .channels_max = 2, |
| .rates = TAS5872M_RATES, |
| .formats = TAS5782M_FORMATS, |
| }, |
| .ops = &tas5782m_dai_ops, |
| }; |
| |
| static int tas5782m_probe(struct device *dev, struct regmap *regmap) |
| { |
| struct tas5782m_priv *tas5782m; |
| int ret; |
| |
| tas5782m = devm_kzalloc(dev, sizeof(struct tas5782m_priv), GFP_KERNEL); |
| if (!tas5782m) |
| return -ENOMEM; |
| |
| dev_set_drvdata(dev, tas5782m); |
| tas5782m->regmap = regmap; |
| tas5782m->volume_db = -30; |
| |
| if (!dev->of_node) { |
| pr_debug("%s, cannot find dts node!\n", __func__); |
| return ret; |
| } |
| |
| dev_set_name(dev, "%s", TAS5872M_DRV_NAME); |
| |
| tas5782m->gpio_power = of_get_named_gpio(dev->of_node, "power_gpio", 0); |
| if (!gpio_is_valid(tas5782m->gpio_power)) |
| pr_debug("%s get invalid tas5782m_power_gpio %d\n", |
| __func__, tas5782m->gpio_power); |
| |
| tas5782m_power(dev, tas5782m, 1); |
| |
| tas5782m->gpio_reset = of_get_named_gpio(dev->of_node, "rst_gpio", 0); |
| if (!gpio_is_valid(tas5782m->gpio_reset)) |
| pr_debug("%s get invalid tas5782m_rst_gpio %d\n", |
| __func__, tas5782m->gpio_reset); |
| |
| tas5782m_reset(dev, tas5782m); |
| |
| |
| ret = regmap_register_patch(regmap, tas5782m_init_sequence, |
| ARRAY_SIZE(tas5782m_init_sequence)); |
| if (ret != 0) { |
| pr_debug("Failed to initialize TAS5782M: %d\n", ret); |
| goto err; |
| } |
| |
| ret = snd_soc_register_codec(dev, |
| &soc_codec_tas5782m, |
| &tas5782m_dai, |
| 1); |
| if (ret != 0) { |
| pr_debug("Failed to register CODEC: %d\n", ret); |
| goto err; |
| } |
| |
| return 0; |
| |
| err: |
| return ret; |
| |
| } |
| |
| static int tas5782m_i2c_probe(struct i2c_client *i2c, |
| const struct i2c_device_id *id) |
| { |
| struct regmap *regmap; |
| struct regmap_config config = tas5782m_regmap; |
| |
| regmap = devm_regmap_init_i2c(i2c, &config); |
| if (IS_ERR(regmap)) |
| return PTR_ERR(regmap); |
| |
| return tas5782m_probe(&i2c->dev, regmap); |
| } |
| |
| int tas5782m_speaker_amp_probe(struct i2c_client *i2c, |
| const struct i2c_device_id *id) |
| { |
| return tas5782m_i2c_probe(i2c, id); |
| } |
| EXPORT_SYMBOL(tas5782m_speaker_amp_probe); |
| |
| static int tas5782m_remove(struct device *dev) |
| { |
| snd_soc_unregister_codec(dev); |
| |
| return 0; |
| } |
| |
| static int tas5782m_i2c_remove(struct i2c_client *i2c) |
| { |
| tas5782m_remove(&i2c->dev); |
| |
| return 0; |
| } |
| |
| int tas5782m_speaker_amp_remove(struct i2c_client *i2c) |
| { |
| return tas5782m_i2c_remove(i2c); |
| } |
| EXPORT_SYMBOL(tas5782m_speaker_amp_remove); |
| |
| static const struct i2c_device_id tas5782m_i2c_id[] = { |
| { "tas5782m", }, |
| { } |
| }; |
| MODULE_DEVICE_TABLE(i2c, tas5782m_i2c_id); |
| |
| #ifdef CONFIG_OF |
| static const struct of_device_id tas5782m_of_match[] = { |
| { .compatible = "ti,tas5782m", }, |
| { } |
| }; |
| MODULE_DEVICE_TABLE(of, tas5782m_of_match); |
| #endif |
| |
| static struct i2c_driver tas5782m_i2c_driver = { |
| .probe = tas5782m_i2c_probe, |
| .remove = tas5782m_i2c_remove, |
| .id_table = tas5782m_i2c_id, |
| .driver = { |
| .name = TAS5872M_DRV_NAME, |
| .of_match_table = tas5782m_of_match, |
| }, |
| }; |
| |
| module_i2c_driver(tas5782m_i2c_driver); |
| |
| MODULE_AUTHOR("Andy Liu <andy-liu@ti.com>"); |
| MODULE_DESCRIPTION("TAS5782M Audio Amplifier Driver"); |
| MODULE_LICENSE("GPL"); |