Linux Audio

Check our new training course

Loading...
v6.13.7
  1// SPDX-License-Identifier: GPL-2.0-or-later
  2/*
  3 * LM4857 AMP driver
  4 *
  5 * Copyright 2007 Wolfson Microelectronics PLC.
  6 * Author: Graeme Gregory
  7 *         graeme.gregory@wolfsonmicro.com
  8 * Copyright 2011 Lars-Peter Clausen <lars@metafoo.de>
 
 
 
 
 
 
  9 */
 10
 11#include <linux/init.h>
 12#include <linux/module.h>
 13#include <linux/i2c.h>
 14#include <linux/regmap.h>
 15#include <linux/slab.h>
 16
 17#include <sound/core.h>
 18#include <sound/soc.h>
 19#include <sound/tlv.h>
 20
 21static const struct reg_default lm4857_default_regs[] = {
 22	{ 0x0, 0x00 },
 23	{ 0x1, 0x00 },
 24	{ 0x2, 0x00 },
 25	{ 0x3, 0x00 },
 
 
 26};
 27
 28/* The register offsets in the cache array */
 29#define LM4857_MVOL 0
 30#define LM4857_LVOL 1
 31#define LM4857_RVOL 2
 32#define LM4857_CTRL 3
 33
 34/* the shifts required to set these bits */
 35#define LM4857_3D 5
 36#define LM4857_WAKEUP 5
 37#define LM4857_EPGAIN 4
 38
 39static const unsigned int lm4857_mode_values[] = {
 40	0,
 41	6,
 42	7,
 43	8,
 44	9,
 45};
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 46
 47static const char * const lm4857_mode_texts[] = {
 48	"Off",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 49	"Earpiece",
 50	"Loudspeaker",
 51	"Loudspeaker + Headphone",
 52	"Headphone",
 53};
 54
 55static SOC_VALUE_ENUM_SINGLE_AUTODISABLE_DECL(lm4857_mode_enum,
 56	LM4857_CTRL, 0, 0xf, lm4857_mode_texts, lm4857_mode_values);
 57
 58static const struct snd_kcontrol_new lm4857_mode_ctrl =
 59	SOC_DAPM_ENUM("Mode", lm4857_mode_enum);
 60
 61static const struct snd_soc_dapm_widget lm4857_dapm_widgets[] = {
 62	SND_SOC_DAPM_INPUT("IN"),
 63
 64	SND_SOC_DAPM_DEMUX("Mode", SND_SOC_NOPM, 0, 0, &lm4857_mode_ctrl),
 65
 66	SND_SOC_DAPM_OUTPUT("LS"),
 67	SND_SOC_DAPM_OUTPUT("HP"),
 68	SND_SOC_DAPM_OUTPUT("EP"),
 69};
 70
 71static const DECLARE_TLV_DB_SCALE(stereo_tlv, -4050, 150, 0);
 72static const DECLARE_TLV_DB_SCALE(mono_tlv, -3450, 150, 0);
 73
 74static const struct snd_kcontrol_new lm4857_controls[] = {
 75	SOC_SINGLE_TLV("Left Playback Volume", LM4857_LVOL, 0, 31, 0,
 76		stereo_tlv),
 77	SOC_SINGLE_TLV("Right Playback Volume", LM4857_RVOL, 0, 31, 0,
 78		stereo_tlv),
 79	SOC_SINGLE_TLV("Mono Playback Volume", LM4857_MVOL, 0, 31, 0,
 80		mono_tlv),
 81	SOC_SINGLE("Spk 3D Playback Switch", LM4857_LVOL, LM4857_3D, 1, 0),
 82	SOC_SINGLE("HP 3D Playback Switch", LM4857_RVOL, LM4857_3D, 1, 0),
 83	SOC_SINGLE("Fast Wakeup Playback Switch", LM4857_CTRL,
 84		LM4857_WAKEUP, 1, 0),
 85	SOC_SINGLE("Earpiece 6dB Playback Switch", LM4857_CTRL,
 86		LM4857_EPGAIN, 1, 0),
 
 
 
 87};
 88
 
 
 
 
 89static const struct snd_soc_dapm_route lm4857_routes[] = {
 90	{ "Mode", NULL, "IN" },
 91	{ "LS", "Loudspeaker", "Mode" },
 92	{ "LS", "Loudspeaker + Headphone", "Mode" },
 93	{ "HP", "Headphone", "Mode" },
 94	{ "HP", "Loudspeaker + Headphone", "Mode" },
 95	{ "EP", "Earpiece", "Mode" },
 96};
 97
 98static const struct snd_soc_component_driver lm4857_component_driver = {
 99	.controls = lm4857_controls,
100	.num_controls = ARRAY_SIZE(lm4857_controls),
101	.dapm_widgets = lm4857_dapm_widgets,
102	.num_dapm_widgets = ARRAY_SIZE(lm4857_dapm_widgets),
103	.dapm_routes = lm4857_routes,
104	.num_dapm_routes = ARRAY_SIZE(lm4857_routes),
105};
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
107static const struct regmap_config lm4857_regmap_config = {
108	.val_bits = 6,
109	.reg_bits = 2,
110
111	.max_register = LM4857_CTRL,
 
112
113	.cache_type = REGCACHE_FLAT,
114	.reg_defaults = lm4857_default_regs,
115	.num_reg_defaults = ARRAY_SIZE(lm4857_default_regs),
 
 
 
 
 
116};
117
118static int lm4857_i2c_probe(struct i2c_client *i2c)
 
119{
120	struct regmap *regmap;
 
 
 
 
 
 
 
121
122	regmap = devm_regmap_init_i2c(i2c, &lm4857_regmap_config);
123	if (IS_ERR(regmap))
124		return PTR_ERR(regmap);
125
126	return devm_snd_soc_register_component(&i2c->dev,
127		&lm4857_component_driver, NULL, 0);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128}
129
130static const struct i2c_device_id lm4857_i2c_id[] = {
131	{ "lm4857" },
132	{ }
133};
134MODULE_DEVICE_TABLE(i2c, lm4857_i2c_id);
135
136static struct i2c_driver lm4857_i2c_driver = {
137	.driver = {
138		.name = "lm4857",
 
139	},
140	.probe = lm4857_i2c_probe,
 
141	.id_table = lm4857_i2c_id,
142};
143
144module_i2c_driver(lm4857_i2c_driver);
 
 
 
 
 
 
 
 
 
 
145
146MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
147MODULE_DESCRIPTION("LM4857 amplifier driver");
148MODULE_LICENSE("GPL");
v3.1
 
  1/*
  2 * LM4857 AMP driver
  3 *
  4 * Copyright 2007 Wolfson Microelectronics PLC.
  5 * Author: Graeme Gregory
  6 *         graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com
  7 * Copyright 2011 Lars-Peter Clausen <lars@metafoo.de>
  8 *
  9 *  This program is free software; you can redistribute  it and/or modify it
 10 *  under  the terms of  the GNU General  Public License as published by the
 11 *  Free Software Foundation;  either version 2 of the  License, or (at your
 12 *  option) any later version.
 13 *
 14 */
 15
 16#include <linux/init.h>
 17#include <linux/module.h>
 18#include <linux/i2c.h>
 
 19#include <linux/slab.h>
 20
 21#include <sound/core.h>
 22#include <sound/soc.h>
 23#include <sound/tlv.h>
 24
 25struct lm4857 {
 26	struct i2c_client *i2c;
 27	uint8_t mode;
 28};
 29
 30static const uint8_t lm4857_default_regs[] = {
 31	0x00, 0x00, 0x00, 0x00,
 32};
 33
 34/* The register offsets in the cache array */
 35#define LM4857_MVOL 0
 36#define LM4857_LVOL 1
 37#define LM4857_RVOL 2
 38#define LM4857_CTRL 3
 39
 40/* the shifts required to set these bits */
 41#define LM4857_3D 5
 42#define LM4857_WAKEUP 5
 43#define LM4857_EPGAIN 4
 44
 45static int lm4857_write(struct snd_soc_codec *codec, unsigned int reg,
 46		unsigned int value)
 47{
 48	uint8_t data;
 49	int ret;
 50
 51	ret = snd_soc_cache_write(codec, reg, value);
 52	if (ret < 0)
 53		return ret;
 54
 55	data = (reg << 6) | value;
 56	ret = i2c_master_send(codec->control_data, &data, 1);
 57	if (ret != 1) {
 58		dev_err(codec->dev, "Failed to write register: %d\n", ret);
 59		return ret;
 60	}
 61
 62	return 0;
 63}
 64
 65static unsigned int lm4857_read(struct snd_soc_codec *codec,
 66		unsigned int reg)
 67{
 68	unsigned int val;
 69	int ret;
 70
 71	ret = snd_soc_cache_read(codec, reg, &val);
 72	if (ret)
 73		return -1;
 74
 75	return val;
 76}
 77
 78static int lm4857_get_mode(struct snd_kcontrol *kcontrol,
 79	struct snd_ctl_elem_value *ucontrol)
 80{
 81	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
 82	struct lm4857 *lm4857 = snd_soc_codec_get_drvdata(codec);
 83
 84	ucontrol->value.integer.value[0] = lm4857->mode;
 85
 86	return 0;
 87}
 88
 89static int lm4857_set_mode(struct snd_kcontrol *kcontrol,
 90	struct snd_ctl_elem_value *ucontrol)
 91{
 92	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
 93	struct lm4857 *lm4857 = snd_soc_codec_get_drvdata(codec);
 94	uint8_t value = ucontrol->value.integer.value[0];
 95
 96	lm4857->mode = value;
 97
 98	if (codec->dapm.bias_level == SND_SOC_BIAS_ON)
 99		snd_soc_update_bits(codec, LM4857_CTRL, 0x0F, value + 6);
100
101	return 1;
102}
103
104static int lm4857_set_bias_level(struct snd_soc_codec *codec,
105				 enum snd_soc_bias_level level)
106{
107	struct lm4857 *lm4857 = snd_soc_codec_get_drvdata(codec);
108
109	switch (level) {
110	case SND_SOC_BIAS_ON:
111		snd_soc_update_bits(codec, LM4857_CTRL, 0x0F, lm4857->mode + 6);
112		break;
113	case SND_SOC_BIAS_STANDBY:
114		snd_soc_update_bits(codec, LM4857_CTRL, 0x0F, 0);
115		break;
116	default:
117		break;
118	}
119
120	codec->dapm.bias_level = level;
121
122	return 0;
123}
124
125static const char *lm4857_mode[] = {
126	"Earpiece",
127	"Loudspeaker",
128	"Loudspeaker + Headphone",
129	"Headphone",
130};
131
132static const struct soc_enum lm4857_mode_enum =
133	SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(lm4857_mode), lm4857_mode);
 
 
 
134
135static const struct snd_soc_dapm_widget lm4857_dapm_widgets[] = {
136	SND_SOC_DAPM_INPUT("IN"),
137
 
 
138	SND_SOC_DAPM_OUTPUT("LS"),
139	SND_SOC_DAPM_OUTPUT("HP"),
140	SND_SOC_DAPM_OUTPUT("EP"),
141};
142
143static const DECLARE_TLV_DB_SCALE(stereo_tlv, -4050, 150, 0);
144static const DECLARE_TLV_DB_SCALE(mono_tlv, -3450, 150, 0);
145
146static const struct snd_kcontrol_new lm4857_controls[] = {
147	SOC_SINGLE_TLV("Left Playback Volume", LM4857_LVOL, 0, 31, 0,
148		stereo_tlv),
149	SOC_SINGLE_TLV("Right Playback Volume", LM4857_RVOL, 0, 31, 0,
150		stereo_tlv),
151	SOC_SINGLE_TLV("Mono Playback Volume", LM4857_MVOL, 0, 31, 0,
152		mono_tlv),
153	SOC_SINGLE("Spk 3D Playback Switch", LM4857_LVOL, LM4857_3D, 1, 0),
154	SOC_SINGLE("HP 3D Playback Switch", LM4857_RVOL, LM4857_3D, 1, 0),
155	SOC_SINGLE("Fast Wakeup Playback Switch", LM4857_CTRL,
156		LM4857_WAKEUP, 1, 0),
157	SOC_SINGLE("Earpiece 6dB Playback Switch", LM4857_CTRL,
158		LM4857_EPGAIN, 1, 0),
159
160	SOC_ENUM_EXT("Mode", lm4857_mode_enum,
161		lm4857_get_mode, lm4857_set_mode),
162};
163
164/* There is a demux between the input signal and the output signals.
165 * Currently there is no easy way to model it in ASoC and since it does not make
166 * much of a difference in practice simply connect the input direclty to the
167 * outputs. */
168static const struct snd_soc_dapm_route lm4857_routes[] = {
169	{"LS", NULL, "IN"},
170	{"HP", NULL, "IN"},
171	{"EP", NULL, "IN"},
 
 
 
172};
173
174static int lm4857_probe(struct snd_soc_codec *codec)
175{
176	struct lm4857 *lm4857 = snd_soc_codec_get_drvdata(codec);
177	struct snd_soc_dapm_context *dapm = &codec->dapm;
178	int ret;
179
180	codec->control_data = lm4857->i2c;
181
182	ret = snd_soc_add_controls(codec, lm4857_controls,
183			ARRAY_SIZE(lm4857_controls));
184	if (ret)
185		return ret;
186
187	ret = snd_soc_dapm_new_controls(dapm, lm4857_dapm_widgets,
188			ARRAY_SIZE(lm4857_dapm_widgets));
189	if (ret)
190		return ret;
191
192	ret = snd_soc_dapm_add_routes(dapm, lm4857_routes,
193			ARRAY_SIZE(lm4857_routes));
194	if (ret)
195		return ret;
196
197	snd_soc_dapm_new_widgets(dapm);
 
 
198
199	return 0;
200}
201
202static struct snd_soc_codec_driver soc_codec_dev_lm4857 = {
203	.write = lm4857_write,
204	.read = lm4857_read,
205	.probe = lm4857_probe,
206	.reg_cache_size = ARRAY_SIZE(lm4857_default_regs),
207	.reg_word_size = sizeof(uint8_t),
208	.reg_cache_default = lm4857_default_regs,
209	.set_bias_level = lm4857_set_bias_level,
210};
211
212static int __devinit lm4857_i2c_probe(struct i2c_client *i2c,
213	const struct i2c_device_id *id)
214{
215	struct lm4857 *lm4857;
216	int ret;
217
218	lm4857 = kzalloc(sizeof(*lm4857), GFP_KERNEL);
219	if (!lm4857)
220		return -ENOMEM;
221
222	i2c_set_clientdata(i2c, lm4857);
223
224	lm4857->i2c = i2c;
 
 
225
226	ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_lm4857, NULL, 0);
227
228	if (ret) {
229		kfree(lm4857);
230		return ret;
231	}
232
233	return 0;
234}
235
236static int __devexit lm4857_i2c_remove(struct i2c_client *i2c)
237{
238	struct lm4857 *lm4857 = i2c_get_clientdata(i2c);
239
240	snd_soc_unregister_codec(&i2c->dev);
241	kfree(lm4857);
242
243	return 0;
244}
245
246static const struct i2c_device_id lm4857_i2c_id[] = {
247	{ "lm4857", 0 },
248	{ }
249};
250MODULE_DEVICE_TABLE(i2c, lm4857_i2c_id);
251
252static struct i2c_driver lm4857_i2c_driver = {
253	.driver = {
254		.name = "lm4857",
255		.owner = THIS_MODULE,
256	},
257	.probe = lm4857_i2c_probe,
258	.remove = __devexit_p(lm4857_i2c_remove),
259	.id_table = lm4857_i2c_id,
260};
261
262static int __init lm4857_init(void)
263{
264	return i2c_add_driver(&lm4857_i2c_driver);
265}
266module_init(lm4857_init);
267
268static void __exit lm4857_exit(void)
269{
270	i2c_del_driver(&lm4857_i2c_driver);
271}
272module_exit(lm4857_exit);
273
274MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
275MODULE_DESCRIPTION("LM4857 amplifier driver");
276MODULE_LICENSE("GPL");