blob: 6337038f4178f8a550e76c26e64c37d7b575771b [file] [log] [blame]
/*
* 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");