blob: 84eafceb47f9c1e11a3b923cf0ebdfe41c82ddde [file] [log] [blame]
Duy Truong790f06d2013-02-13 16:38:12 -08001/* Copyright (c) 2012, The Linux Foundation. All rights reserved.
Asish Bhattacharya656b7a22012-03-15 00:27:42 -07002 *
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License version 2 and
5 * only version 2 as published by the Free Software Foundation.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 */
12
13#define pr_fmt(fmt) "%s: " fmt, __func__
14
15#include <linux/module.h>
16#include <linux/err.h>
17#include <linux/platform_device.h>
18#include <linux/slab.h>
19#include <linux/stddef.h>
20#include <linux/debugfs.h>
21#include <linux/mfd/pm8xxx/core.h>
22#include <linux/mfd/pm8xxx/spk.h>
23
24#define PM8XXX_SPK_CTL1_REG_OFF 0
Asish Bhattacharya59442e22012-09-07 17:22:57 +053025#define PM8XXX_SPK_CTL2_REG_OFF 1
26#define PM8XXX_SPK_CTL3_REG_OFF 2
27#define PM8XXX_SPK_CTL4_REG_OFF 3
28#define PM8XXX_SPK_TEST_REG_1_OFF 4
29#define PM8XXX_SPK_TEST_REG_2_OFF 5
Asish Bhattacharya656b7a22012-03-15 00:27:42 -070030
31#define PM8XXX_SPK_BANK_SEL 4
32#define PM8XXX_SPK_BANK_WRITE 0x80
33#define PM8XXX_SPK_BANK_VAL_MASK 0xF
34
35#define BOOST_6DB_GAIN_EN_MASK 0x8
36#define VSEL_LD0_1P1 0x0
37#define VSEL_LD0_1P2 0x2
38#define VSEL_LD0_1P0 0x4
39
40#define PWM_EN_MASK 0xF
41#define PM8XXX_SPK_TEST_REG_1_BANKS 8
42#define PM8XXX_SPK_TEST_REG_2_BANKS 2
43
44#define PM8XXX_SPK_GAIN 0x5
45#define PM8XXX_ADD_EN 0x1
46
47struct pm8xxx_spk_chip {
48 struct list_head link;
49 struct pm8xxx_spk_platform_data pdata;
50 struct device *dev;
51 enum pm8xxx_version version;
52 struct mutex spk_mutex;
53 u16 base;
54 u16 end;
55};
56
57static struct pm8xxx_spk_chip *the_spk_chip;
58
59static inline bool spk_defined(void)
60{
61 if (the_spk_chip == NULL || IS_ERR(the_spk_chip))
62 return false;
63 return true;
64}
65
66static int pm8xxx_spk_bank_write(u16 reg, u16 bank, u8 val)
67{
68 int rc = 0;
69 u8 bank_val = PM8XXX_SPK_BANK_WRITE | (bank << PM8XXX_SPK_BANK_SEL);
70
71 bank_val |= (val & PM8XXX_SPK_BANK_VAL_MASK);
72 mutex_lock(&the_spk_chip->spk_mutex);
73 rc = pm8xxx_writeb(the_spk_chip->dev->parent, reg, bank_val);
74 if (rc)
75 pr_err("pm8xxx_writeb(): rc=%d\n", rc);
76 mutex_unlock(&the_spk_chip->spk_mutex);
77 return rc;
78}
79
80
81static int pm8xxx_spk_read(u16 addr)
82{
83 int rc = 0;
84 u8 val = 0;
85
86 mutex_lock(&the_spk_chip->spk_mutex);
87 rc = pm8xxx_readb(the_spk_chip->dev->parent,
88 the_spk_chip->base + addr, &val);
89 if (rc) {
90 pr_err("pm8xxx_spk_readb() failed: rc=%d\n", rc);
91 val = rc;
92 }
93 mutex_unlock(&the_spk_chip->spk_mutex);
94
95 return val;
96}
97
98static int pm8xxx_spk_write(u16 addr, u8 val)
99{
100 int rc = 0;
101
102 mutex_lock(&the_spk_chip->spk_mutex);
103 rc = pm8xxx_writeb(the_spk_chip->dev->parent,
104 the_spk_chip->base + addr, val);
105 if (rc)
106 pr_err("pm8xxx_writeb() failed: rc=%d\n", rc);
107 mutex_unlock(&the_spk_chip->spk_mutex);
108 return rc;
109}
110
111int pm8xxx_spk_mute(bool mute)
112{
113 u8 val = 0;
114 int ret = 0;
115 if (spk_defined() == false) {
116 pr_err("Invalid spk handle or no spk_chip\n");
117 return -ENODEV;
118 }
119
120 val = pm8xxx_spk_read(PM8XXX_SPK_CTL1_REG_OFF);
Asish Bhattacharya656b7a22012-03-15 00:27:42 -0700121 val |= mute << 2;
122 ret = pm8xxx_spk_write(PM8XXX_SPK_CTL1_REG_OFF, val);
123 return ret;
124}
125EXPORT_SYMBOL_GPL(pm8xxx_spk_mute);
126
127int pm8xxx_spk_gain(u8 gain)
128{
129 u8 val;
130 int ret = 0;
131
132 if (spk_defined() == false) {
133 pr_err("Invalid spk handle or no spk_chip\n");
134 return -ENODEV;
135 }
136
137 val = pm8xxx_spk_read(PM8XXX_SPK_CTL1_REG_OFF);
Asish Bhattacharyabfff5e62012-08-31 17:11:43 +0530138 val = (gain << 4) | (val & 0xF);
Asish Bhattacharya656b7a22012-03-15 00:27:42 -0700139 ret = pm8xxx_spk_write(PM8XXX_SPK_CTL1_REG_OFF, val);
140 if (!ret) {
141 pm8xxx_spk_bank_write(the_spk_chip->base
142 + PM8XXX_SPK_TEST_REG_1_OFF,
143 0, BOOST_6DB_GAIN_EN_MASK | VSEL_LD0_1P2);
144 }
145 return ret;
146}
147EXPORT_SYMBOL_GPL(pm8xxx_spk_gain);
148
149int pm8xxx_spk_enable(int enable)
150{
151 int val = 0;
152 u16 addr;
153 int ret = 0;
154
155 if (spk_defined() == false) {
156 pr_err("Invalid spk handle or no spk_chip\n");
157 return -ENODEV;
158 }
159
160 addr = the_spk_chip->base + PM8XXX_SPK_TEST_REG_1_OFF;
161 val = pm8xxx_spk_read(PM8XXX_SPK_CTL1_REG_OFF);
162 if (val < 0)
163 return val;
Asish Bhattacharya7225d642012-07-20 19:56:37 +0530164 if (enable)
165 val |= (1 << 3);
166 else
167 val &= ~(1 << 3);
Asish Bhattacharya656b7a22012-03-15 00:27:42 -0700168 ret = pm8xxx_spk_write(PM8XXX_SPK_CTL1_REG_OFF, val);
169 if (!ret)
170 ret = pm8xxx_spk_bank_write(addr, 6, PWM_EN_MASK);
171 return ret;
172}
173EXPORT_SYMBOL_GPL(pm8xxx_spk_enable);
174
175static int pm8xxx_spk_config(void)
176{
177 u16 addr;
178 int ret = 0;
179
180 if (spk_defined() == false) {
181 pr_err("Invalid spk handle or no spk_chip\n");
182 return -ENODEV;
183 }
184
185 addr = the_spk_chip->base + PM8XXX_SPK_TEST_REG_1_OFF;
186 ret = pm8xxx_spk_bank_write(addr, 6, PWM_EN_MASK & 0);
187 if (!ret)
188 ret = pm8xxx_spk_gain(PM8XXX_SPK_GAIN);
189 return ret;
190}
191
192static int __devinit pm8xxx_spk_probe(struct platform_device *pdev)
193{
194 const struct pm8xxx_spk_platform_data *pdata = pdev->dev.platform_data;
195 int ret = 0;
Asish Bhattacharya59442e22012-09-07 17:22:57 +0530196 u8 value = 0;
Asish Bhattacharya656b7a22012-03-15 00:27:42 -0700197
198 if (!pdata) {
199 pr_err("missing platform data\n");
200 return -EINVAL;
201 }
202
203 the_spk_chip = kzalloc(sizeof(struct pm8xxx_spk_chip), GFP_KERNEL);
204 if (the_spk_chip == NULL) {
205 pr_err("kzalloc() failed.\n");
206 return -ENOMEM;
207 }
208
209 mutex_init(&the_spk_chip->spk_mutex);
210
211 the_spk_chip->dev = &pdev->dev;
212 the_spk_chip->version = pm8xxx_get_version(the_spk_chip->dev->parent);
213 switch (pm8xxx_get_version(the_spk_chip->dev->parent)) {
214 case PM8XXX_VERSION_8038:
215 break;
216 default:
217 ret = -ENODEV;
218 goto err_handle;
219 }
220
221 memcpy(&(the_spk_chip->pdata), pdata,
222 sizeof(struct pm8xxx_spk_platform_data));
223
224 the_spk_chip->base = pdev->resource[0].start;
225 the_spk_chip->end = pdev->resource[0].end;
226
227 if (the_spk_chip->pdata.spk_add_enable) {
228 int val;
229 val = pm8xxx_spk_read(PM8XXX_SPK_CTL1_REG_OFF);
230 if (val < 0) {
231 ret = val;
232 goto err_handle;
233 }
234 val |= (the_spk_chip->pdata.spk_add_enable & PM8XXX_ADD_EN);
235 ret = pm8xxx_spk_write(PM8XXX_SPK_CTL1_REG_OFF, val);
236 if (ret < 0)
237 goto err_handle;
238 }
Asish Bhattacharya59442e22012-09-07 17:22:57 +0530239 value = ((the_spk_chip->pdata.cd_ng_threshold << 5) |
240 the_spk_chip->pdata.cd_nf_preamp_bias << 3);
241 pr_debug("Setting SPK_CTL2_REG = %02x\n", value);
242 pm8xxx_spk_write(PM8XXX_SPK_CTL2_REG_OFF, value);
243
244 value = ((the_spk_chip->pdata.cd_ng_hold << 5) |
245 (the_spk_chip->pdata.cd_ng_max_atten << 1) |
246 the_spk_chip->pdata.noise_mute);
247 pr_debug("Setting SPK_CTL3_REG = %02x\n", value);
248 pm8xxx_spk_write(PM8XXX_SPK_CTL3_REG_OFF, value);
249
250 value = ((the_spk_chip->pdata.cd_ng_decay_rate << 5) |
251 (the_spk_chip->pdata.cd_ng_attack_rate << 3) |
252 the_spk_chip->pdata.cd_delay << 2);
253 pr_debug("Setting SPK_CTL4_REG = %02x\n", value);
254 pm8xxx_spk_write(PM8XXX_SPK_CTL4_REG_OFF, value);
255
Asish Bhattacharya656b7a22012-03-15 00:27:42 -0700256 return pm8xxx_spk_config();
257err_handle:
258 pr_err("pm8xxx_spk_probe failed."
259 "Audio unavailable on speaker.\n");
260 mutex_destroy(&the_spk_chip->spk_mutex);
261 kfree(the_spk_chip);
262 return ret;
263}
264
265static int __devexit pm8xxx_spk_remove(struct platform_device *pdev)
266{
267 if (spk_defined() == false) {
268 pr_err("Invalid spk handle or no spk_chip\n");
269 return -ENODEV;
270 }
271 mutex_destroy(&the_spk_chip->spk_mutex);
272 kfree(the_spk_chip);
273 return 0;
274}
275
276static struct platform_driver pm8xxx_spk_driver = {
277 .probe = pm8xxx_spk_probe,
278 .remove = __devexit_p(pm8xxx_spk_remove),
279 .driver = {
280 .name = PM8XXX_SPK_DEV_NAME,
281 .owner = THIS_MODULE,
282 },
283};
284
285static int __init pm8xxx_spk_init(void)
286{
287 return platform_driver_register(&pm8xxx_spk_driver);
288}
289subsys_initcall(pm8xxx_spk_init);
290
291static void __exit pm8xxx_spk_exit(void)
292{
293 platform_driver_unregister(&pm8xxx_spk_driver);
294}
295module_exit(pm8xxx_spk_exit);
296
297MODULE_LICENSE("GPL v2");
298MODULE_DESCRIPTION("PM8XXX SPK driver");
299MODULE_ALIAS("platform:" PM8XXX_SPK_DEV_NAME);