From 478f73188e3687700b42ec776955cfa68e538fd7 Mon Sep 17 00:00:00 2001 From: Francisco Franco Date: Thu, 14 Nov 2013 07:32:07 +0000 Subject: [PATCH] sound_control: initial code push for Headphones gain userspace interface. Signed-off-by: Francisco Franco sound_control: add headset, speaker and mic gain interfaces. Also add the sysfs control file. Signed-off-by: Francisco Franco sound_control: add workaround to prevent audio resets after using custom values. Based on https://github.com/faux123/Nexus_5/commit/33407fd2d63290540eb0054bc7c6611f2080ea8b Signed-off-by: Francisco Franco sound: wcd9320: tidy up things a little bit. Only accept signed values from now on. The 'headset gain' will only be used to remove some gain noise, increasing it beyond 1 step above the default doesn't make a difference so rather just have it decrease the gain. Signed-off-by: franciscofranco sound_control: clean the code * proper 80-char indentation * add module_exit * clean up the store functions using proper and modern kstroul * use unsigned boost vars since we're not accepting negative values anymore * remove that stupid and useless version define sound_control: goodbye externs Signed-off-by: franciscofranco Conflicts: drivers/misc/Makefile Signed-off-by: franciscofranco --- drivers/misc/Makefile | 1 + drivers/misc/sound_control.c | 199 ++++++++++++++++++++++++++++++++++ include/linux/sound_control.h | 18 +++ sound/soc/codecs/wcd9320.c | 168 +++++++++++++++++++++++++++- 4 files changed, 385 insertions(+), 1 deletion(-) create mode 100644 drivers/misc/sound_control.c create mode 100644 include/linux/sound_control.h diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index e2b77515e2ca..81ebc6185bbb 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -74,3 +74,4 @@ obj-$(CONFIG_TI_DRV2667) += ti_drv2667.o obj-$(CONFIG_QPNP_MISC) += qpnp-misc.o obj-$(CONFIG_EARJACK_DEBUGGER) += earjack_debugger.o obj-$(CONFIG_FAN48632_BOOST) += fan48632_boost.o +obj-y += sound_control.o diff --git a/drivers/misc/sound_control.c b/drivers/misc/sound_control.c new file mode 100644 index 000000000000..112c5df3f12d --- /dev/null +++ b/drivers/misc/sound_control.c @@ -0,0 +1,199 @@ +/* + * Copyright 2013-2014 Francisco Franco + * franciscofranco.1990@gmail.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 +#include +#include +#include + +#define MAX_VALUE 20 + +/* + * Volume boost value + */ + +unsigned int headphones_boost = 0; + +/* + * Headset boost value + */ +unsigned int headset_boost = 0; + +/* + * Speaker boost value + */ +unsigned int speaker_boost = 0; + +/* + * Mic boost value + */ +unsigned int mic_boost = 0; + +/* + * Sysfs get/set entries + */ + +static ssize_t headphones_boost_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", headphones_boost); +} + +static ssize_t headphones_boost_store(struct device * dev, + struct device_attribute * attr, const char * buf, size_t size) +{ + int ret; + unsigned long val; + + ret = kstrtoul(buf, 0, &val); + if (ret < 0) + return ret; + + headphones_boost = val > MAX_VALUE ? MAX_VALUE : val; + + update_headphones_volume_boost(headphones_boost); + + pr_info("%s: %d\n", __func__, headphones_boost); + + return size; +} + +static ssize_t headset_boost_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", headset_boost); +} + +static ssize_t headset_boost_store(struct device * dev, + struct device_attribute * attr, const char * buf, size_t size) +{ + int ret; + unsigned long val; + + ret = kstrtoul(buf, 0, &val); + if (ret < 0) + return ret; + + headset_boost = val > MAX_VALUE ? MAX_VALUE : val; + + update_headset_boost(headphones_boost); + + pr_info("%s: %d\n", __func__, headset_boost); + + return size; +} + +static ssize_t speaker_boost_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", speaker_boost); +} + +static ssize_t speaker_boost_store(struct device * dev, + struct device_attribute * attr, const char * buf, size_t size) +{ + int ret; + unsigned long val; + + ret = kstrtoul(buf, 0, &val); + if (ret < 0) + return ret; + + speaker_boost = val > MAX_VALUE ? MAX_VALUE : val; + + update_speaker_gain(speaker_boost); + + pr_info("%s: %d\n", __func__, speaker_boost); + + return size; +} + +static ssize_t mic_boost_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", mic_boost); +} + +static ssize_t mic_boost_store(struct device * dev, + struct device_attribute * attr, const char * buf, size_t size) +{ + int ret; + unsigned long val; + + ret = kstrtoul(buf, 0, &val); + if (ret < 0) + return ret; + + mic_boost = val > MAX_VALUE ? MAX_VALUE : val; + + update_mic_gain(mic_boost); + + pr_info("%s: %d\n", __func__, mic_boost); + + return size; +} + +static DEVICE_ATTR(volume_boost, 0664, headphones_boost_show, + headphones_boost_store); +static DEVICE_ATTR(headset_boost, 0664, headset_boost_show, + headset_boost_store); +static DEVICE_ATTR(speaker_boost, 0664, speaker_boost_show, + speaker_boost_store); +static DEVICE_ATTR(mic_boost, 0664, mic_boost_show, mic_boost_store); + +static struct attribute *soundcontrol_attributes[] = +{ + &dev_attr_volume_boost.attr, + &dev_attr_headset_boost.attr, + &dev_attr_speaker_boost.attr, + &dev_attr_mic_boost.attr, + NULL +}; + +static struct attribute_group soundcontrol_group = +{ + .attrs = soundcontrol_attributes, +}; + +static struct miscdevice soundcontrol_device = +{ + .minor = MISC_DYNAMIC_MINOR, + .name = "soundcontrol", +}; + +static void __exit soundcontrol_exit(void) +{ + misc_deregister(&soundcontrol_device); +} + +static int __init soundcontrol_init(void) +{ + int ret; + + pr_info("%s misc_register(%s)\n", __FUNCTION__, soundcontrol_device.name); + + ret = misc_register(&soundcontrol_device); + + if (ret) { + pr_err("%s misc_register(%s) fail\n", __FUNCTION__, + soundcontrol_device.name); + return 1; + } + + if (sysfs_create_group(&soundcontrol_device.this_device->kobj, + &soundcontrol_group) < 0) { + pr_err("%s sysfs_create_group fail\n", __FUNCTION__); + pr_err("Failed to create sysfs group for device (%s)!\n", + soundcontrol_device.name); + } + + return 0; +} +late_initcall(soundcontrol_init); +module_exit(soundcontrol_exit); diff --git a/include/linux/sound_control.h b/include/linux/sound_control.h new file mode 100644 index 000000000000..8d713d756105 --- /dev/null +++ b/include/linux/sound_control.h @@ -0,0 +1,18 @@ +/* + * linux/include/linux/sound_control.h + * + * franciscofranco.1990@gmail.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 _LINUX_SOUNT_CONTROL_H +#define _LINUX_SOUNT_CONTROL_H + +void update_headphones_volume_boost(int vol_boost); +void update_headset_boost(int vol_boost); +void update_speaker_gain(int vol_boost); +void update_mic_gain(int vol_boost); + +#endif diff --git a/sound/soc/codecs/wcd9320.c b/sound/soc/codecs/wcd9320.c index 4924fb4ad167..d27fedd2a1e3 100644 --- a/sound/soc/codecs/wcd9320.c +++ b/sound/soc/codecs/wcd9320.c @@ -39,6 +39,17 @@ #include "wcd9xxx-resmgr.h" #include "wcd9xxx-common.h" +struct sound_control { + int default_headphones_value; + int default_headset_value; + int default_speaker_value; + int default_mic_value; + struct snd_soc_codec *snd_control_codec; + bool lock; +} soundcontrol = { + .lock = false, +}; + #define TAIKO_MAD_SLIMBUS_TX_PORT 12 #define TAIKO_MAD_AUDIO_FIRMWARE_PATH "wcd9320/wcd9320_mad_audio.bin" #define TAIKO_VALIDATE_RX_SBPORT_RANGE(port) ((port >= 16) && (port <= 22)) @@ -4224,10 +4235,47 @@ static int taiko_volatile(struct snd_soc_codec *ssc, unsigned int reg) return 0; } +int reg_access(unsigned int reg) +{ + int ret = 1; + + switch (reg) { + case TAIKO_A_RX_HPH_L_GAIN: + case TAIKO_A_RX_HPH_R_GAIN: + case TAIKO_A_RX_HPH_L_STATUS: + case TAIKO_A_RX_HPH_R_STATUS: + case TAIKO_A_CDC_RX1_VOL_CTL_B2_CTL: + case TAIKO_A_CDC_RX2_VOL_CTL_B2_CTL: + case TAIKO_A_CDC_RX3_VOL_CTL_B2_CTL: + case TAIKO_A_CDC_RX4_VOL_CTL_B2_CTL: + case TAIKO_A_CDC_RX5_VOL_CTL_B2_CTL: + case TAIKO_A_CDC_RX6_VOL_CTL_B2_CTL: + case TAIKO_A_CDC_RX7_VOL_CTL_B2_CTL: + case TAIKO_A_CDC_TX1_VOL_CTL_GAIN: + case TAIKO_A_CDC_TX2_VOL_CTL_GAIN: + case TAIKO_A_CDC_TX3_VOL_CTL_GAIN: + case TAIKO_A_CDC_TX4_VOL_CTL_GAIN: + case TAIKO_A_CDC_TX5_VOL_CTL_GAIN: + case TAIKO_A_CDC_TX6_VOL_CTL_GAIN: + case TAIKO_A_CDC_TX7_VOL_CTL_GAIN: + case TAIKO_A_CDC_TX8_VOL_CTL_GAIN: + case TAIKO_A_CDC_TX9_VOL_CTL_GAIN: + case TAIKO_A_CDC_TX10_VOL_CTL_GAIN: + if (soundcontrol.lock) + ret = 0; + break; + default: + break; + } + + return ret; +} + static int taiko_write(struct snd_soc_codec *codec, unsigned int reg, unsigned int value) { int ret; + int val; if (reg == SND_SOC_NOPM) return 0; @@ -4241,7 +4289,12 @@ static int taiko_write(struct snd_soc_codec *codec, unsigned int reg, reg, ret); } - return wcd9xxx_reg_write(codec->control_data, reg, value); + if (!reg_access(reg)) + val = wcd9xxx_reg_read(codec->control_data, reg); + else + val = value; + + return wcd9xxx_reg_write(codec->control_data, reg, val); } static unsigned int taiko_read(struct snd_soc_codec *codec, unsigned int reg) @@ -6219,6 +6272,8 @@ static void taiko_update_reg_defaults(struct snd_soc_codec *codec) u32 i; struct wcd9xxx *taiko_core = dev_get_drvdata(codec->dev->parent); + pr_info("Update TAIKO register defaults.\n"); + for (i = 0; i < ARRAY_SIZE(taiko_reg_defaults); i++) snd_soc_write(codec, taiko_reg_defaults[i].reg, taiko_reg_defaults[i].val); @@ -6583,6 +6638,102 @@ static struct regulator *taiko_codec_find_regulator(struct snd_soc_codec *codec, return NULL; } +void update_headphones_volume_boost(unsigned int vol_boost) +{ + int default_val = soundcontrol.default_headphones_value; + int boosted_val = default_val | vol_boost; + + pr_info("Sound Control: Headphones default value %d\n", default_val); + + soundcontrol.lock = false; + taiko_write(soundcontrol.snd_control_codec, + TAIKO_A_CDC_RX1_VOL_CTL_B2_CTL, boosted_val); + taiko_write(soundcontrol.snd_control_codec, + TAIKO_A_CDC_RX2_VOL_CTL_B2_CTL, boosted_val); + soundcontrol.lock = true; + + pr_info("Sound Control: Boosted Headphones RX1 value %d\n", + taiko_read(soundcontrol.snd_control_codec, + TAIKO_A_CDC_RX1_VOL_CTL_B2_CTL)); + + pr_info("Sound Control: Boosted Headphones RX2 value %d\n", + taiko_read(soundcontrol.snd_control_codec, + TAIKO_A_CDC_RX2_VOL_CTL_B2_CTL)); +} + +void update_headset_boost(unsigned int vol_boost) +{ + int default_val = soundcontrol.default_headset_value; + int boosted_val = default_val | vol_boost; + + pr_info("Sound Control: Headset default value %d\n", default_val); + + soundcontrol.lock = false; + taiko_write(soundcontrol.snd_control_codec, + TAIKO_A_RX_HPH_R_GAIN, boosted_val); + taiko_write(soundcontrol.snd_control_codec, + TAIKO_A_RX_HPH_L_GAIN, boosted_val); + + taiko_write(soundcontrol.snd_control_codec, + TAIKO_A_RX_HPH_R_STATUS, + taiko_read(soundcontrol.snd_control_codec,TAIKO_A_RX_HPH_R_STATUS) + | (boosted_val << 4)); + taiko_write(soundcontrol.snd_control_codec, + TAIKO_A_RX_HPH_L_STATUS, + taiko_read(soundcontrol.snd_control_codec,TAIKO_A_RX_HPH_L_STATUS) + | (boosted_val << 4)); + soundcontrol.lock = true; + + pr_info("Sound Control: Boosted Headset R value %d\n", + taiko_read(soundcontrol.snd_control_codec, + TAIKO_A_RX_HPH_R_GAIN)); + + pr_info("Sound Control: Boosted Headset L value %d\n", + taiko_read(soundcontrol.snd_control_codec, + TAIKO_A_RX_HPH_L_GAIN)); +} + +void update_speaker_gain(unsigned int vol_boost) +{ + int default_val = soundcontrol.default_speaker_value; + int boosted_val = default_val | vol_boost; + + pr_info("Sound Control: Speaker default value %d\n", default_val); + + soundcontrol.lock = false; + taiko_write(soundcontrol.snd_control_codec, + TAIKO_A_CDC_RX3_VOL_CTL_B2_CTL, boosted_val); + + taiko_write(soundcontrol.snd_control_codec, + TAIKO_A_CDC_RX7_VOL_CTL_B2_CTL, boosted_val); + soundcontrol.lock = true; + + pr_info("Sound Control: Boosted Speaker RX3 value %d\n", + taiko_read(soundcontrol.snd_control_codec, + TAIKO_A_CDC_RX3_VOL_CTL_B2_CTL)); + + pr_info("Sound Control: Boosted Speaker RX7 value %d\n", + taiko_read(soundcontrol.snd_control_codec, + TAIKO_A_CDC_RX7_VOL_CTL_B2_CTL)); +} + +void update_mic_gain(unsigned int vol_boost) +{ + int default_val = soundcontrol.default_mic_value; + int boosted_val = default_val | vol_boost; + + pr_info("Sound Control: Mic default value %d\n", default_val); + + soundcontrol.lock = false; + taiko_write(soundcontrol.snd_control_codec, + TAIKO_A_CDC_TX7_VOL_CTL_GAIN, boosted_val); + soundcontrol.lock = true; + + pr_info("Sound Control: Boosted Mic value %d\n", + taiko_read(soundcontrol.snd_control_codec, + TAIKO_A_CDC_TX7_VOL_CTL_GAIN)); +} + static int taiko_codec_probe(struct snd_soc_codec *codec) { struct wcd9xxx *control; @@ -6595,6 +6746,8 @@ static int taiko_codec_probe(struct snd_soc_codec *codec) void *ptr = NULL; struct wcd9xxx *core = dev_get_drvdata(codec->dev->parent); + soundcontrol.snd_control_codec = codec; + codec->control_data = dev_get_drvdata(codec->dev->parent); control = codec->control_data; @@ -6757,6 +6910,19 @@ static int taiko_codec_probe(struct snd_soc_codec *codec) mutex_unlock(&dapm->codec->mutex); codec->ignore_pmdown_time = 1; + + /* + * Get the default values during probe + */ + soundcontrol.default_headphones_value = taiko_read(codec, + TAIKO_A_CDC_RX1_VOL_CTL_B2_CTL); + soundcontrol.default_headset_value = taiko_read(codec, + TAIKO_A_RX_HPH_R_GAIN); + soundcontrol.default_speaker_value = taiko_read(codec, + TAIKO_A_CDC_RX3_VOL_CTL_B2_CTL); + soundcontrol.default_mic_value = taiko_read(codec, + TAIKO_A_CDC_TX7_VOL_CTL_GAIN); + return ret; err_irq: