]>
Commit | Line | Data |
---|---|---|
9b0a25f0 LPC |
1 | /* |
2 | * LM4857 AMP driver | |
3 | * | |
4 | * Copyright 2007 Wolfson Microelectronics PLC. | |
5 | * Author: Graeme Gregory | |
9a185b9a | 6 | * graeme.gregory@wolfsonmicro.com |
9b0a25f0 LPC |
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> | |
9b270968 | 19 | #include <linux/regmap.h> |
9b0a25f0 LPC |
20 | #include <linux/slab.h> |
21 | ||
22 | #include <sound/core.h> | |
23 | #include <sound/soc.h> | |
24 | #include <sound/tlv.h> | |
25 | ||
26 | struct lm4857 { | |
9b270968 | 27 | struct regmap *regmap; |
9b0a25f0 LPC |
28 | uint8_t mode; |
29 | }; | |
30 | ||
9b270968 LPC |
31 | static const struct reg_default lm4857_default_regs[] = { |
32 | { 0x0, 0x00 }, | |
33 | { 0x1, 0x00 }, | |
34 | { 0x2, 0x00 }, | |
35 | { 0x3, 0x00 }, | |
9b0a25f0 LPC |
36 | }; |
37 | ||
38 | /* The register offsets in the cache array */ | |
39 | #define LM4857_MVOL 0 | |
40 | #define LM4857_LVOL 1 | |
41 | #define LM4857_RVOL 2 | |
42 | #define LM4857_CTRL 3 | |
43 | ||
44 | /* the shifts required to set these bits */ | |
45 | #define LM4857_3D 5 | |
46 | #define LM4857_WAKEUP 5 | |
47 | #define LM4857_EPGAIN 4 | |
48 | ||
9b0a25f0 LPC |
49 | static int lm4857_get_mode(struct snd_kcontrol *kcontrol, |
50 | struct snd_ctl_elem_value *ucontrol) | |
51 | { | |
ea53bf77 | 52 | struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); |
9b0a25f0 LPC |
53 | struct lm4857 *lm4857 = snd_soc_codec_get_drvdata(codec); |
54 | ||
55 | ucontrol->value.integer.value[0] = lm4857->mode; | |
56 | ||
57 | return 0; | |
58 | } | |
59 | ||
60 | static int lm4857_set_mode(struct snd_kcontrol *kcontrol, | |
61 | struct snd_ctl_elem_value *ucontrol) | |
62 | { | |
ea53bf77 | 63 | struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); |
9b0a25f0 LPC |
64 | struct lm4857 *lm4857 = snd_soc_codec_get_drvdata(codec); |
65 | uint8_t value = ucontrol->value.integer.value[0]; | |
66 | ||
67 | lm4857->mode = value; | |
68 | ||
69 | if (codec->dapm.bias_level == SND_SOC_BIAS_ON) | |
9b270968 | 70 | regmap_update_bits(lm4857->regmap, LM4857_CTRL, 0x0F, value + 6); |
9b0a25f0 LPC |
71 | |
72 | return 1; | |
73 | } | |
74 | ||
75 | static int lm4857_set_bias_level(struct snd_soc_codec *codec, | |
76 | enum snd_soc_bias_level level) | |
77 | { | |
78 | struct lm4857 *lm4857 = snd_soc_codec_get_drvdata(codec); | |
79 | ||
80 | switch (level) { | |
81 | case SND_SOC_BIAS_ON: | |
9b270968 LPC |
82 | regmap_update_bits(lm4857->regmap, LM4857_CTRL, 0x0F, |
83 | lm4857->mode + 6); | |
9b0a25f0 LPC |
84 | break; |
85 | case SND_SOC_BIAS_STANDBY: | |
9b270968 | 86 | regmap_update_bits(lm4857->regmap, LM4857_CTRL, 0x0F, 0); |
9b0a25f0 LPC |
87 | break; |
88 | default: | |
89 | break; | |
90 | } | |
91 | ||
9b0a25f0 LPC |
92 | return 0; |
93 | } | |
94 | ||
95 | static const char *lm4857_mode[] = { | |
96 | "Earpiece", | |
97 | "Loudspeaker", | |
98 | "Loudspeaker + Headphone", | |
99 | "Headphone", | |
100 | }; | |
101 | ||
6415e307 | 102 | static SOC_ENUM_SINGLE_EXT_DECL(lm4857_mode_enum, lm4857_mode); |
9b0a25f0 LPC |
103 | |
104 | static const struct snd_soc_dapm_widget lm4857_dapm_widgets[] = { | |
105 | SND_SOC_DAPM_INPUT("IN"), | |
106 | ||
107 | SND_SOC_DAPM_OUTPUT("LS"), | |
108 | SND_SOC_DAPM_OUTPUT("HP"), | |
109 | SND_SOC_DAPM_OUTPUT("EP"), | |
110 | }; | |
111 | ||
112 | static const DECLARE_TLV_DB_SCALE(stereo_tlv, -4050, 150, 0); | |
113 | static const DECLARE_TLV_DB_SCALE(mono_tlv, -3450, 150, 0); | |
114 | ||
115 | static const struct snd_kcontrol_new lm4857_controls[] = { | |
116 | SOC_SINGLE_TLV("Left Playback Volume", LM4857_LVOL, 0, 31, 0, | |
117 | stereo_tlv), | |
118 | SOC_SINGLE_TLV("Right Playback Volume", LM4857_RVOL, 0, 31, 0, | |
119 | stereo_tlv), | |
120 | SOC_SINGLE_TLV("Mono Playback Volume", LM4857_MVOL, 0, 31, 0, | |
121 | mono_tlv), | |
122 | SOC_SINGLE("Spk 3D Playback Switch", LM4857_LVOL, LM4857_3D, 1, 0), | |
123 | SOC_SINGLE("HP 3D Playback Switch", LM4857_RVOL, LM4857_3D, 1, 0), | |
124 | SOC_SINGLE("Fast Wakeup Playback Switch", LM4857_CTRL, | |
125 | LM4857_WAKEUP, 1, 0), | |
126 | SOC_SINGLE("Earpiece 6dB Playback Switch", LM4857_CTRL, | |
127 | LM4857_EPGAIN, 1, 0), | |
128 | ||
129 | SOC_ENUM_EXT("Mode", lm4857_mode_enum, | |
130 | lm4857_get_mode, lm4857_set_mode), | |
131 | }; | |
132 | ||
25985edc | 133 | /* There is a demux between the input signal and the output signals. |
9b0a25f0 LPC |
134 | * Currently there is no easy way to model it in ASoC and since it does not make |
135 | * much of a difference in practice simply connect the input direclty to the | |
136 | * outputs. */ | |
137 | static const struct snd_soc_dapm_route lm4857_routes[] = { | |
138 | {"LS", NULL, "IN"}, | |
139 | {"HP", NULL, "IN"}, | |
140 | {"EP", NULL, "IN"}, | |
141 | }; | |
142 | ||
9b0a25f0 | 143 | static struct snd_soc_codec_driver soc_codec_dev_lm4857 = { |
9b0a25f0 | 144 | .set_bias_level = lm4857_set_bias_level, |
07ccc0f4 LPC |
145 | |
146 | .controls = lm4857_controls, | |
147 | .num_controls = ARRAY_SIZE(lm4857_controls), | |
148 | .dapm_widgets = lm4857_dapm_widgets, | |
149 | .num_dapm_widgets = ARRAY_SIZE(lm4857_dapm_widgets), | |
150 | .dapm_routes = lm4857_routes, | |
151 | .num_dapm_routes = ARRAY_SIZE(lm4857_routes), | |
9b0a25f0 LPC |
152 | }; |
153 | ||
9b270968 LPC |
154 | static const struct regmap_config lm4857_regmap_config = { |
155 | .val_bits = 6, | |
156 | .reg_bits = 2, | |
157 | ||
158 | .max_register = LM4857_CTRL, | |
159 | ||
160 | .cache_type = REGCACHE_FLAT, | |
161 | .reg_defaults = lm4857_default_regs, | |
162 | .num_reg_defaults = ARRAY_SIZE(lm4857_default_regs), | |
163 | }; | |
164 | ||
7a79e94e BP |
165 | static int lm4857_i2c_probe(struct i2c_client *i2c, |
166 | const struct i2c_device_id *id) | |
9b0a25f0 LPC |
167 | { |
168 | struct lm4857 *lm4857; | |
9b0a25f0 | 169 | |
eb3bb97c | 170 | lm4857 = devm_kzalloc(&i2c->dev, sizeof(*lm4857), GFP_KERNEL); |
9b0a25f0 LPC |
171 | if (!lm4857) |
172 | return -ENOMEM; | |
173 | ||
174 | i2c_set_clientdata(i2c, lm4857); | |
175 | ||
9b270968 LPC |
176 | lm4857->regmap = devm_regmap_init_i2c(i2c, &lm4857_regmap_config); |
177 | if (IS_ERR(lm4857->regmap)) | |
178 | return PTR_ERR(lm4857->regmap); | |
9b0a25f0 | 179 | |
9b270968 | 180 | return snd_soc_register_codec(&i2c->dev, &soc_codec_dev_lm4857, NULL, 0); |
9b0a25f0 LPC |
181 | } |
182 | ||
7a79e94e | 183 | static int lm4857_i2c_remove(struct i2c_client *i2c) |
9b0a25f0 | 184 | { |
9b0a25f0 | 185 | snd_soc_unregister_codec(&i2c->dev); |
9b0a25f0 LPC |
186 | return 0; |
187 | } | |
188 | ||
189 | static const struct i2c_device_id lm4857_i2c_id[] = { | |
190 | { "lm4857", 0 }, | |
191 | { } | |
192 | }; | |
193 | MODULE_DEVICE_TABLE(i2c, lm4857_i2c_id); | |
194 | ||
195 | static struct i2c_driver lm4857_i2c_driver = { | |
196 | .driver = { | |
197 | .name = "lm4857", | |
198 | .owner = THIS_MODULE, | |
199 | }, | |
200 | .probe = lm4857_i2c_probe, | |
7a79e94e | 201 | .remove = lm4857_i2c_remove, |
9b0a25f0 LPC |
202 | .id_table = lm4857_i2c_id, |
203 | }; | |
204 | ||
f6ec139f | 205 | module_i2c_driver(lm4857_i2c_driver); |
9b0a25f0 LPC |
206 | |
207 | MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); | |
208 | MODULE_DESCRIPTION("LM4857 amplifier driver"); | |
209 | MODULE_LICENSE("GPL"); |