/* * ASoC Driver for TAS5713 * * Author: Sebastian Eickhoff * Copyright 2014 * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "tas5713.h" static struct i2c_client *i2c; struct tas5713_priv { struct regmap *regmap; int mclk_div; struct snd_soc_component *component; }; static struct tas5713_priv *priv_data; /* * _ _ ___ _ ___ _ _ * /_\ | | / __| /_\ / __|___ _ _| |_ _ _ ___| |___ * / _ \| |__\__ \/ _ \ | (__/ _ \ ' \ _| '_/ _ \ (_-< * /_/ \_\____|___/_/ \_\ \___\___/_||_\__|_| \___/_/__/ * */ static const DECLARE_TLV_DB_SCALE(tas5713_vol_tlv, -10000, 50, 1); static const struct snd_kcontrol_new tas5713_snd_controls[] = { SOC_SINGLE_TLV ("Master" , TAS5713_VOL_MASTER, 0, 248, 1, tas5713_vol_tlv), SOC_DOUBLE_R_TLV("Channels" , TAS5713_VOL_CH1, TAS5713_VOL_CH2, 0, 248, 1, tas5713_vol_tlv) }; /* * __ __ _ _ ___ _ * | \/ |__ _ __| |_ (_)_ _ ___ | \ _ _(_)_ _____ _ _ * | |\/| / _` / _| ' \| | ' \/ -_) | |) | '_| \ V / -_) '_| * |_| |_\__,_\__|_||_|_|_||_\___| |___/|_| |_|\_/\___|_| * */ static int tas5713_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { u16 blen = 0x00; struct snd_soc_component *component = dai->component; priv_data->component = component; switch (params_format(params)) { case SNDRV_PCM_FORMAT_S16_LE: blen = 0x03; break; case SNDRV_PCM_FORMAT_S20_3LE: blen = 0x1; break; case SNDRV_PCM_FORMAT_S24_LE: blen = 0x04; break; case SNDRV_PCM_FORMAT_S32_LE: blen = 0x05; break; default: dev_err(dai->dev, "Unsupported word length: %u\n", params_format(params)); return -EINVAL; } // set word length snd_soc_component_update_bits(component, TAS5713_SERIAL_DATA_INTERFACE, 0x7, blen); return 0; } static int tas5713_mute_stream(struct snd_soc_dai *dai, int mute, int stream) { unsigned int val = 0; struct tas5713_priv *tas5713; struct snd_soc_component *component = dai->component; tas5713 = snd_soc_component_get_drvdata(component); if (mute) { val = TAS5713_SOFT_MUTE_ALL; } return regmap_write(tas5713->regmap, TAS5713_SOFT_MUTE, val); } static const struct snd_soc_dai_ops tas5713_dai_ops = { .hw_params = tas5713_hw_params, .mute_stream = tas5713_mute_stream, }; static struct snd_soc_dai_driver tas5713_dai = { .name = "tas5713-hifi", .playback = { .stream_name = "Playback", .channels_min = 2, .channels_max = 2, .rates = SNDRV_PCM_RATE_8000_48000, .formats = (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE ), }, .ops = &tas5713_dai_ops, }; /* * ___ _ ___ _ * / __|___ __| |___ __ | \ _ _(_)_ _____ _ _ * | (__/ _ \/ _` / -_) _| | |) | '_| \ V / -_) '_| * \___\___/\__,_\___\__| |___/|_| |_|\_/\___|_| * */ static void tas5713_remove(struct snd_soc_component *component) { struct tas5713_priv *tas5713; tas5713 = snd_soc_component_get_drvdata(component); } static int tas5713_probe(struct snd_soc_component *component) { struct tas5713_priv *tas5713; int i, ret; i2c = container_of(component->dev, struct i2c_client, dev); tas5713 = snd_soc_component_get_drvdata(component); // Reset error ret = snd_soc_component_write(component, TAS5713_ERROR_STATUS, 0x00); if (ret < 0) return ret; // Trim oscillator ret = snd_soc_component_write(component, TAS5713_OSC_TRIM, 0x00); if (ret < 0) return ret; msleep(1000); // Reset error ret = snd_soc_component_write(component, TAS5713_ERROR_STATUS, 0x00); if (ret < 0) return ret; // I2S 24bit ret = snd_soc_component_write(component, TAS5713_SERIAL_DATA_INTERFACE, 0x05); if (ret < 0) return ret; // Unmute ret = snd_soc_component_write(component, TAS5713_SYSTEM_CTRL2, 0x00); if (ret < 0) return ret; ret = snd_soc_component_write(component, TAS5713_SOFT_MUTE, 0x00); if (ret < 0) return ret; // Set volume to 0db ret = snd_soc_component_write(component, TAS5713_VOL_MASTER, 0x00); if (ret < 0) return ret; // Now start programming the default initialization sequence for (i = 0; i < ARRAY_SIZE(tas5713_init_sequence); ++i) { ret = i2c_master_send(i2c, tas5713_init_sequence[i].data, tas5713_init_sequence[i].size); if (ret < 0) { printk(KERN_INFO "TAS5713 CODEC PROBE: InitSeq returns: %d\n", ret); } } // Unmute ret = snd_soc_component_write(component, TAS5713_SYSTEM_CTRL2, 0x00); if (ret < 0) return ret; return 0; } static struct snd_soc_component_driver soc_codec_dev_tas5713 = { .probe = tas5713_probe, .remove = tas5713_remove, .controls = tas5713_snd_controls, .num_controls = ARRAY_SIZE(tas5713_snd_controls), }; /* * ___ ___ ___ ___ _ * |_ _|_ ) __| | \ _ _(_)_ _____ _ _ * | | / / (__ | |) | '_| \ V / -_) '_| * |___/___\___| |___/|_| |_|\_/\___|_| * */ static const struct reg_default tas5713_reg_defaults[] = { { 0x07 ,0x80 }, // R7 - VOL_MASTER - -40dB { 0x08 , 30 }, // R8 - VOL_CH1 - 0dB { 0x09 , 30 }, // R9 - VOL_CH2 - 0dB { 0x0A ,0x80 }, // R10 - VOL_HEADPHONE - -40dB }; static bool tas5713_reg_volatile(struct device *dev, unsigned int reg) { switch (reg) { case TAS5713_DEVICE_ID: case TAS5713_ERROR_STATUS: case TAS5713_CLOCK_CTRL: return true; default: return false; } } static const struct of_device_id tas5713_of_match[] = { { .compatible = "ti,tas5713", }, { } }; MODULE_DEVICE_TABLE(of, tas5713_of_match); static struct regmap_config tas5713_regmap_config = { .reg_bits = 8, .val_bits = 8, .max_register = TAS5713_MAX_REGISTER, .volatile_reg = tas5713_reg_volatile, .cache_type = REGCACHE_RBTREE, .reg_defaults = tas5713_reg_defaults, .num_reg_defaults = ARRAY_SIZE(tas5713_reg_defaults), }; static int tas5713_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { int ret; priv_data = devm_kzalloc(&i2c->dev, sizeof *priv_data, GFP_KERNEL); if (!priv_data) return -ENOMEM; priv_data->regmap = devm_regmap_init_i2c(i2c, &tas5713_regmap_config); if (IS_ERR(priv_data->regmap)) { ret = PTR_ERR(priv_data->regmap); return ret; } i2c_set_clientdata(i2c, priv_data); ret = snd_soc_register_component(&i2c->dev, &soc_codec_dev_tas5713, &tas5713_dai, 1); return ret; } static int tas5713_i2c_remove(struct i2c_client *i2c) { snd_soc_unregister_component(&i2c->dev); i2c_set_clientdata(i2c, NULL); kfree(priv_data); return 0; } static const struct i2c_device_id tas5713_i2c_id[] = { { "tas5713", 0 }, { } }; MODULE_DEVICE_TABLE(i2c, tas5713_i2c_id); static struct i2c_driver tas5713_i2c_driver = { .driver = { .name = "tas5713", .owner = THIS_MODULE, .of_match_table = tas5713_of_match, }, .probe = tas5713_i2c_probe, .remove = tas5713_i2c_remove, .id_table = tas5713_i2c_id }; static int __init tas5713_modinit(void) { int ret = 0; ret = i2c_add_driver(&tas5713_i2c_driver); if (ret) { printk(KERN_ERR "Failed to register tas5713 I2C driver: %d\n", ret); } return ret; } module_init(tas5713_modinit); static void __exit tas5713_exit(void) { i2c_del_driver(&tas5713_i2c_driver); } module_exit(tas5713_exit); MODULE_AUTHOR("Sebastian Eickhoff "); MODULE_DESCRIPTION("ASoC driver for TAS5713"); MODULE_LICENSE("GPL v2");