Blame view
drivers/mfd/twl6040.c
20.8 KB
2b27bdcc2 treewide: Replace... |
1 |
// SPDX-License-Identifier: GPL-2.0-only |
f19b2823f mfd: twl6040: Add... |
2 3 4 5 6 7 8 9 |
/* * MFD driver for TWL6040 audio device * * Authors: Misael Lopez Cruz <misael.lopez@ti.com> * Jorge Eduardo Candelaria <jorge.candelaria@ti.com> * Peter Ujfalusi <peter.ujfalusi@ti.com> * * Copyright: (C) 2011 Texas Instruments, Inc. |
f19b2823f mfd: twl6040: Add... |
10 11 12 13 14 15 |
*/ #include <linux/module.h> #include <linux/types.h> #include <linux/slab.h> #include <linux/kernel.h> |
5af7df6b8 mfd: Add regulato... |
16 |
#include <linux/err.h> |
f19b2823f mfd: twl6040: Add... |
17 |
#include <linux/platform_device.h> |
37e13ceca mfd: Add support ... |
18 19 20 21 |
#include <linux/of.h> #include <linux/of_irq.h> #include <linux/of_gpio.h> #include <linux/of_platform.h> |
f19b2823f mfd: twl6040: Add... |
22 23 |
#include <linux/gpio.h> #include <linux/delay.h> |
8eaeb9393 mfd: Convert twl6... |
24 25 |
#include <linux/i2c.h> #include <linux/regmap.h> |
f19b2823f mfd: twl6040: Add... |
26 27 |
#include <linux/mfd/core.h> #include <linux/mfd/twl6040.h> |
5af7df6b8 mfd: Add regulato... |
28 |
#include <linux/regulator/consumer.h> |
f19b2823f mfd: twl6040: Add... |
29 |
|
31b402e3c MFD: twl6040: Cac... |
30 |
#define VIBRACTRL_MEMBER(reg) ((reg == TWL6040_REG_VIBCTLL) ? 0 : 1) |
5af7df6b8 mfd: Add regulato... |
31 |
#define TWL6040_NUM_SUPPLIES (2) |
31b402e3c MFD: twl6040: Cac... |
32 |
|
de1e23f83 mfd: twl6040: Con... |
33 |
static const struct reg_default twl6040_defaults[] = { |
c7f9129d2 mfd: twl6040: reg... |
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
{ 0x01, 0x4B }, /* REG_ASICID (ro) */ { 0x02, 0x00 }, /* REG_ASICREV (ro) */ { 0x03, 0x00 }, /* REG_INTID */ { 0x04, 0x00 }, /* REG_INTMR */ { 0x05, 0x00 }, /* REG_NCPCTRL */ { 0x06, 0x00 }, /* REG_LDOCTL */ { 0x07, 0x60 }, /* REG_HPPLLCTL */ { 0x08, 0x00 }, /* REG_LPPLLCTL */ { 0x09, 0x4A }, /* REG_LPPLLDIV */ { 0x0A, 0x00 }, /* REG_AMICBCTL */ { 0x0B, 0x00 }, /* REG_DMICBCTL */ { 0x0C, 0x00 }, /* REG_MICLCTL */ { 0x0D, 0x00 }, /* REG_MICRCTL */ { 0x0E, 0x00 }, /* REG_MICGAIN */ { 0x0F, 0x1B }, /* REG_LINEGAIN */ { 0x10, 0x00 }, /* REG_HSLCTL */ { 0x11, 0x00 }, /* REG_HSRCTL */ { 0x12, 0x00 }, /* REG_HSGAIN */ { 0x13, 0x00 }, /* REG_EARCTL */ { 0x14, 0x00 }, /* REG_HFLCTL */ { 0x15, 0x00 }, /* REG_HFLGAIN */ { 0x16, 0x00 }, /* REG_HFRCTL */ { 0x17, 0x00 }, /* REG_HFRGAIN */ { 0x18, 0x00 }, /* REG_VIBCTLL */ { 0x19, 0x00 }, /* REG_VIBDATL */ { 0x1A, 0x00 }, /* REG_VIBCTLR */ { 0x1B, 0x00 }, /* REG_VIBDATR */ { 0x1C, 0x00 }, /* REG_HKCTL1 */ { 0x1D, 0x00 }, /* REG_HKCTL2 */ { 0x1E, 0x00 }, /* REG_GPOCTL */ { 0x1F, 0x00 }, /* REG_ALB */ { 0x20, 0x00 }, /* REG_DLB */ /* 0x28, REG_TRIM1 */ /* 0x29, REG_TRIM2 */ /* 0x2A, REG_TRIM3 */ /* 0x2B, REG_HSOTRIM */ /* 0x2C, REG_HFOTRIM */ { 0x2D, 0x08 }, /* REG_ACCCTL */ { 0x2E, 0x00 }, /* REG_STATUS (ro) */ }; |
8019ff6cf regmap: Use reg_s... |
74 |
static struct reg_sequence twl6040_patch[] = { |
11e38e11a mfd: twl6040: Sel... |
75 76 77 78 79 80 81 |
/* * Select I2C bus access to dual access registers * Interrupt register is cleared on read * Select fast mode for i2c (400KHz) */ { TWL6040_REG_ACCCTL, TWL6040_I2CSEL | TWL6040_INTCLRMODE | TWL6040_I2CMODE(1) }, |
c7f9129d2 mfd: twl6040: reg... |
82 |
}; |
85e9b13cb mfd: twl6040: Fix... |
83 |
static bool twl6040_has_vibra(struct device_node *parent) |
ca2cad6ae mfd: Fix twl6040 ... |
84 |
{ |
85e9b13cb mfd: twl6040: Fix... |
85 86 87 88 89 |
struct device_node *node; node = of_get_child_by_name(parent, "vibra"); if (node) { of_node_put(node); |
ca2cad6ae mfd: Fix twl6040 ... |
90 |
return true; |
85e9b13cb mfd: twl6040: Fix... |
91 |
} |
ca2cad6ae mfd: Fix twl6040 ... |
92 93 |
return false; } |
f19b2823f mfd: twl6040: Add... |
94 95 96 |
int twl6040_reg_read(struct twl6040 *twl6040, unsigned int reg) { int ret; |
8eaeb9393 mfd: Convert twl6... |
97 |
unsigned int val; |
f19b2823f mfd: twl6040: Add... |
98 |
|
c6f39257c mfd: twl6040: Use... |
99 100 101 |
ret = regmap_read(twl6040->regmap, reg, &val); if (ret < 0) return ret; |
f19b2823f mfd: twl6040: Add... |
102 103 104 105 106 107 108 109 |
return val; } EXPORT_SYMBOL(twl6040_reg_read); int twl6040_reg_write(struct twl6040 *twl6040, unsigned int reg, u8 val) { int ret; |
8eaeb9393 mfd: Convert twl6... |
110 |
ret = regmap_write(twl6040->regmap, reg, val); |
f19b2823f mfd: twl6040: Add... |
111 112 113 114 115 116 117 |
return ret; } EXPORT_SYMBOL(twl6040_reg_write); int twl6040_set_bits(struct twl6040 *twl6040, unsigned int reg, u8 mask) { |
c600040f0 mfd: Remove unnee... |
118 |
return regmap_update_bits(twl6040->regmap, reg, mask, mask); |
f19b2823f mfd: twl6040: Add... |
119 120 121 122 123 |
} EXPORT_SYMBOL(twl6040_set_bits); int twl6040_clear_bits(struct twl6040 *twl6040, unsigned int reg, u8 mask) { |
c600040f0 mfd: Remove unnee... |
124 |
return regmap_update_bits(twl6040->regmap, reg, mask, 0); |
f19b2823f mfd: twl6040: Add... |
125 126 127 128 |
} EXPORT_SYMBOL(twl6040_clear_bits); /* twl6040 codec manual power-up sequence */ |
f9be13435 mfd: twl6040: Res... |
129 |
static int twl6040_power_up_manual(struct twl6040 *twl6040) |
f19b2823f mfd: twl6040: Add... |
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 |
{ u8 ldoctl, ncpctl, lppllctl; int ret; /* enable high-side LDO, reference system and internal oscillator */ ldoctl = TWL6040_HSLDOENA | TWL6040_REFENA | TWL6040_OSCENA; ret = twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl); if (ret) return ret; usleep_range(10000, 10500); /* enable negative charge pump */ ncpctl = TWL6040_NCPENA; ret = twl6040_reg_write(twl6040, TWL6040_REG_NCPCTL, ncpctl); if (ret) goto ncp_err; usleep_range(1000, 1500); /* enable low-side LDO */ ldoctl |= TWL6040_LSLDOENA; ret = twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl); if (ret) goto lsldo_err; usleep_range(1000, 1500); /* enable low-power PLL */ lppllctl = TWL6040_LPLLENA; ret = twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl); if (ret) goto lppll_err; usleep_range(5000, 5500); /* disable internal oscillator */ ldoctl &= ~TWL6040_OSCENA; ret = twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl); if (ret) goto osc_err; return 0; osc_err: lppllctl &= ~TWL6040_LPLLENA; twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl); lppll_err: ldoctl &= ~TWL6040_LSLDOENA; twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl); lsldo_err: ncpctl &= ~TWL6040_NCPENA; twl6040_reg_write(twl6040, TWL6040_REG_NCPCTL, ncpctl); ncp_err: ldoctl &= ~(TWL6040_HSLDOENA | TWL6040_REFENA | TWL6040_OSCENA); twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl); |
f9be13435 mfd: twl6040: Res... |
182 183 |
dev_err(twl6040->dev, "manual power-up failed "); |
f19b2823f mfd: twl6040: Add... |
184 185 186 187 |
return ret; } /* twl6040 manual power-down sequence */ |
f9be13435 mfd: twl6040: Res... |
188 |
static void twl6040_power_down_manual(struct twl6040 *twl6040) |
f19b2823f mfd: twl6040: Add... |
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 |
{ u8 ncpctl, ldoctl, lppllctl; ncpctl = twl6040_reg_read(twl6040, TWL6040_REG_NCPCTL); ldoctl = twl6040_reg_read(twl6040, TWL6040_REG_LDOCTL); lppllctl = twl6040_reg_read(twl6040, TWL6040_REG_LPPLLCTL); /* enable internal oscillator */ ldoctl |= TWL6040_OSCENA; twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl); usleep_range(1000, 1500); /* disable low-power PLL */ lppllctl &= ~TWL6040_LPLLENA; twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl); /* disable low-side LDO */ ldoctl &= ~TWL6040_LSLDOENA; twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl); /* disable negative charge pump */ ncpctl &= ~TWL6040_NCPENA; twl6040_reg_write(twl6040, TWL6040_REG_NCPCTL, ncpctl); /* disable high-side LDO, reference system and internal oscillator */ ldoctl &= ~(TWL6040_HSLDOENA | TWL6040_REFENA | TWL6040_OSCENA); twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl); } |
1ac96265a mfd: twl6040: Cor... |
217 |
static irqreturn_t twl6040_readyint_handler(int irq, void *data) |
f19b2823f mfd: twl6040: Add... |
218 219 |
{ struct twl6040 *twl6040 = data; |
f19b2823f mfd: twl6040: Add... |
220 |
|
1ac96265a mfd: twl6040: Cor... |
221 |
complete(&twl6040->ready); |
f19b2823f mfd: twl6040: Add... |
222 |
|
1ac96265a mfd: twl6040: Cor... |
223 224 |
return IRQ_HANDLED; } |
f19b2823f mfd: twl6040: Add... |
225 |
|
1ac96265a mfd: twl6040: Cor... |
226 227 228 229 230 231 232 233 234 235 236 237 |
static irqreturn_t twl6040_thint_handler(int irq, void *data) { struct twl6040 *twl6040 = data; u8 status; status = twl6040_reg_read(twl6040, TWL6040_REG_STATUS); if (status & TWL6040_TSHUTDET) { dev_warn(twl6040->dev, "Thermal shutdown, powering-off"); twl6040_power(twl6040, 0); } else { dev_warn(twl6040->dev, "Leaving thermal shutdown, powering-on"); twl6040_power(twl6040, 1); |
f19b2823f mfd: twl6040: Add... |
238 239 240 241 |
} return IRQ_HANDLED; } |
f9be13435 mfd: twl6040: Res... |
242 |
static int twl6040_power_up_automatic(struct twl6040 *twl6040) |
f19b2823f mfd: twl6040: Add... |
243 244 |
{ int time_left; |
f9be13435 mfd: twl6040: Res... |
245 246 |
gpio_set_value(twl6040->audpwron, 1); |
f19b2823f mfd: twl6040: Add... |
247 248 249 250 |
time_left = wait_for_completion_timeout(&twl6040->ready, msecs_to_jiffies(144)); if (!time_left) { |
f9be13435 mfd: twl6040: Res... |
251 252 253 254 |
u8 intid; dev_warn(twl6040->dev, "timeout waiting for READYINT "); |
f19b2823f mfd: twl6040: Add... |
255 256 |
intid = twl6040_reg_read(twl6040, TWL6040_REG_INTID); if (!(intid & TWL6040_READYINT)) { |
f9be13435 mfd: twl6040: Res... |
257 258 259 |
dev_err(twl6040->dev, "automatic power-up failed "); gpio_set_value(twl6040->audpwron, 0); |
f19b2823f mfd: twl6040: Add... |
260 261 262 263 264 265 266 267 268 |
return -ETIMEDOUT; } } return 0; } int twl6040_power(struct twl6040 *twl6040, int on) { |
f19b2823f mfd: twl6040: Add... |
269 270 271 272 273 274 275 276 |
int ret = 0; mutex_lock(&twl6040->mutex); if (on) { /* already powered-up */ if (twl6040->power_count++) goto out; |
32852bcab mfd: twl6040: Che... |
277 278 279 280 281 |
ret = clk_prepare_enable(twl6040->clk32k); if (ret) { twl6040->power_count = 0; goto out; } |
68bab8662 mfd: twl6040: Opt... |
282 |
|
c7f9129d2 mfd: twl6040: reg... |
283 284 |
/* Allow writes to the chip */ regcache_cache_only(twl6040->regmap, false); |
f9be13435 mfd: twl6040: Res... |
285 286 287 |
if (gpio_is_valid(twl6040->audpwron)) { /* use automatic power-up sequence */ ret = twl6040_power_up_automatic(twl6040); |
f19b2823f mfd: twl6040: Add... |
288 |
if (ret) { |
d6441dc5c mfd: twl6040: Dis... |
289 |
clk_disable_unprepare(twl6040->clk32k); |
f19b2823f mfd: twl6040: Add... |
290 291 292 293 294 |
twl6040->power_count = 0; goto out; } } else { /* use manual power-up sequence */ |
f9be13435 mfd: twl6040: Res... |
295 |
ret = twl6040_power_up_manual(twl6040); |
f19b2823f mfd: twl6040: Add... |
296 |
if (ret) { |
d6441dc5c mfd: twl6040: Dis... |
297 |
clk_disable_unprepare(twl6040->clk32k); |
f19b2823f mfd: twl6040: Add... |
298 299 300 301 |
twl6040->power_count = 0; goto out; } } |
c7f9129d2 mfd: twl6040: reg... |
302 |
|
48171d0ea mfd: twl6040: Fix... |
303 304 305 306 307 |
/* * Register access can produce errors after power-up unless we * wait at least 8ms based on measurements on duovero. */ usleep_range(10000, 12000); |
c7f9129d2 mfd: twl6040: reg... |
308 |
/* Sync with the HW */ |
48171d0ea mfd: twl6040: Fix... |
309 310 311 312 313 314 315 |
ret = regcache_sync(twl6040->regmap); if (ret) { dev_err(twl6040->dev, "Failed to sync with the HW: %i ", ret); goto out; } |
c7f9129d2 mfd: twl6040: reg... |
316 |
|
cfb7a33be MFD: twl6040: Rem... |
317 318 |
/* Default PLL configuration after power up */ twl6040->pll = TWL6040_SYSCLK_SEL_LPPLL; |
0a58da1e2 mfd: twl6040: Han... |
319 |
twl6040->sysclk_rate = 19200000; |
f19b2823f mfd: twl6040: Add... |
320 321 322 |
} else { /* already powered-down */ if (!twl6040->power_count) { |
2d7c957e2 MFD: twl6040: Rem... |
323 |
dev_err(twl6040->dev, |
f19b2823f mfd: twl6040: Add... |
324 325 326 327 328 329 330 331 |
"device is already powered-off "); ret = -EPERM; goto out; } if (--twl6040->power_count) goto out; |
f9be13435 mfd: twl6040: Res... |
332 |
if (gpio_is_valid(twl6040->audpwron)) { |
f19b2823f mfd: twl6040: Add... |
333 |
/* use AUDPWRON line */ |
f9be13435 mfd: twl6040: Res... |
334 |
gpio_set_value(twl6040->audpwron, 0); |
f19b2823f mfd: twl6040: Add... |
335 336 337 338 339 |
/* power-down sequence latency */ usleep_range(500, 700); } else { /* use manual power-down sequence */ |
f9be13435 mfd: twl6040: Res... |
340 |
twl6040_power_down_manual(twl6040); |
f19b2823f mfd: twl6040: Add... |
341 |
} |
c7f9129d2 mfd: twl6040: reg... |
342 343 344 345 |
/* Set regmap to cache only and mark it as dirty */ regcache_cache_only(twl6040->regmap, true); regcache_mark_dirty(twl6040->regmap); |
0a58da1e2 mfd: twl6040: Han... |
346 347 348 349 350 351 |
twl6040->sysclk_rate = 0; if (twl6040->pll == TWL6040_SYSCLK_SEL_HPPLL) { clk_disable_unprepare(twl6040->mclk); twl6040->mclk_rate = 0; } |
68bab8662 mfd: twl6040: Opt... |
352 353 |
clk_disable_unprepare(twl6040->clk32k); |
f19b2823f mfd: twl6040: Add... |
354 355 356 357 358 359 360 |
} out: mutex_unlock(&twl6040->mutex); return ret; } EXPORT_SYMBOL(twl6040_power); |
cfb7a33be MFD: twl6040: Rem... |
361 |
int twl6040_set_pll(struct twl6040 *twl6040, int pll_id, |
f19b2823f mfd: twl6040: Add... |
362 363 364 365 366 367 368 369 370 |
unsigned int freq_in, unsigned int freq_out) { u8 hppllctl, lppllctl; int ret = 0; mutex_lock(&twl6040->mutex); hppllctl = twl6040_reg_read(twl6040, TWL6040_REG_HPPLLCTL); lppllctl = twl6040_reg_read(twl6040, TWL6040_REG_LPPLLCTL); |
2bd05db71 mfd: Avoid twl604... |
371 372 |
/* Force full reconfiguration when switching between PLL */ if (pll_id != twl6040->pll) { |
0a58da1e2 mfd: twl6040: Han... |
373 374 |
twl6040->sysclk_rate = 0; twl6040->mclk_rate = 0; |
2bd05db71 mfd: Avoid twl604... |
375 |
} |
cfb7a33be MFD: twl6040: Rem... |
376 377 |
switch (pll_id) { case TWL6040_SYSCLK_SEL_LPPLL: |
f19b2823f mfd: twl6040: Add... |
378 |
/* low-power PLL divider */ |
2bd05db71 mfd: Avoid twl604... |
379 |
/* Change the sysclk configuration only if it has been canged */ |
0a58da1e2 mfd: twl6040: Han... |
380 |
if (twl6040->sysclk_rate != freq_out) { |
2bd05db71 mfd: Avoid twl604... |
381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 |
switch (freq_out) { case 17640000: lppllctl |= TWL6040_LPLLFIN; break; case 19200000: lppllctl &= ~TWL6040_LPLLFIN; break; default: dev_err(twl6040->dev, "freq_out %d not supported ", freq_out); ret = -EINVAL; goto pll_out; } twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl); |
f19b2823f mfd: twl6040: Add... |
398 |
} |
2bd05db71 mfd: Avoid twl604... |
399 400 401 402 |
/* The PLL in use has not been change, we can exit */ if (twl6040->pll == pll_id) break; |
f19b2823f mfd: twl6040: Add... |
403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 |
switch (freq_in) { case 32768: lppllctl |= TWL6040_LPLLENA; twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl); mdelay(5); lppllctl &= ~TWL6040_HPLLSEL; twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl); hppllctl &= ~TWL6040_HPLLENA; twl6040_reg_write(twl6040, TWL6040_REG_HPPLLCTL, hppllctl); break; default: |
2d7c957e2 MFD: twl6040: Rem... |
418 |
dev_err(twl6040->dev, |
f19b2823f mfd: twl6040: Add... |
419 420 421 422 423 |
"freq_in %d not supported ", freq_in); ret = -EINVAL; goto pll_out; } |
0a58da1e2 mfd: twl6040: Han... |
424 425 |
clk_disable_unprepare(twl6040->mclk); |
f19b2823f mfd: twl6040: Add... |
426 |
break; |
cfb7a33be MFD: twl6040: Rem... |
427 |
case TWL6040_SYSCLK_SEL_HPPLL: |
f19b2823f mfd: twl6040: Add... |
428 429 |
/* high-performance PLL can provide only 19.2 MHz */ if (freq_out != 19200000) { |
2d7c957e2 MFD: twl6040: Rem... |
430 |
dev_err(twl6040->dev, |
f19b2823f mfd: twl6040: Add... |
431 432 433 434 435 |
"freq_out %d not supported ", freq_out); ret = -EINVAL; goto pll_out; } |
0a58da1e2 mfd: twl6040: Han... |
436 |
if (twl6040->mclk_rate != freq_in) { |
2bd05db71 mfd: Avoid twl604... |
437 438 439 440 441 442 443 444 445 |
hppllctl &= ~TWL6040_MCLK_MSK; switch (freq_in) { case 12000000: /* PLL enabled, active mode */ hppllctl |= TWL6040_MCLK_12000KHZ | TWL6040_HPLLENA; break; case 19200000: |
ac8320c47 mfd: twl6040: Cor... |
446 447 448 |
/* PLL enabled, bypass mode */ hppllctl |= TWL6040_MCLK_19200KHZ | TWL6040_HPLLBP | TWL6040_HPLLENA; |
2bd05db71 mfd: Avoid twl604... |
449 450 451 452 453 454 455 |
break; case 26000000: /* PLL enabled, active mode */ hppllctl |= TWL6040_MCLK_26000KHZ | TWL6040_HPLLENA; break; case 38400000: |
ac8320c47 mfd: twl6040: Cor... |
456 |
/* PLL enabled, bypass mode */ |
2bd05db71 mfd: Avoid twl604... |
457 |
hppllctl |= TWL6040_MCLK_38400KHZ | |
ac8320c47 mfd: twl6040: Cor... |
458 |
TWL6040_HPLLBP | TWL6040_HPLLENA; |
2bd05db71 mfd: Avoid twl604... |
459 460 461 462 463 464 465 466 |
break; default: dev_err(twl6040->dev, "freq_in %d not supported ", freq_in); ret = -EINVAL; goto pll_out; } |
f19b2823f mfd: twl6040: Add... |
467 |
|
0a58da1e2 mfd: twl6040: Han... |
468 469 470 |
/* When switching to HPPLL, enable the mclk first */ if (pll_id != twl6040->pll) clk_prepare_enable(twl6040->mclk); |
f19b2823f mfd: twl6040: Add... |
471 |
/* |
2bd05db71 mfd: Avoid twl604... |
472 473 |
* enable clock slicer to ensure input waveform is * square |
f19b2823f mfd: twl6040: Add... |
474 |
*/ |
2bd05db71 mfd: Avoid twl604... |
475 |
hppllctl |= TWL6040_HPLLSQRENA; |
f19b2823f mfd: twl6040: Add... |
476 |
|
2bd05db71 mfd: Avoid twl604... |
477 478 479 480 481 482 483 484 485 |
twl6040_reg_write(twl6040, TWL6040_REG_HPPLLCTL, hppllctl); usleep_range(500, 700); lppllctl |= TWL6040_HPLLSEL; twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl); lppllctl &= ~TWL6040_LPLLENA; twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl); |
0a58da1e2 mfd: twl6040: Han... |
486 487 |
twl6040->mclk_rate = freq_in; |
2bd05db71 mfd: Avoid twl604... |
488 |
} |
f19b2823f mfd: twl6040: Add... |
489 490 |
break; default: |
2d7c957e2 MFD: twl6040: Rem... |
491 492 |
dev_err(twl6040->dev, "unknown pll id %d ", pll_id); |
f19b2823f mfd: twl6040: Add... |
493 494 495 |
ret = -EINVAL; goto pll_out; } |
0a58da1e2 mfd: twl6040: Han... |
496 |
twl6040->sysclk_rate = freq_out; |
cfb7a33be MFD: twl6040: Rem... |
497 |
twl6040->pll = pll_id; |
f19b2823f mfd: twl6040: Add... |
498 499 500 501 502 503 |
pll_out: mutex_unlock(&twl6040->mutex); return ret; } EXPORT_SYMBOL(twl6040_set_pll); |
cfb7a33be MFD: twl6040: Rem... |
504 |
int twl6040_get_pll(struct twl6040 *twl6040) |
f19b2823f mfd: twl6040: Add... |
505 |
{ |
cfb7a33be MFD: twl6040: Rem... |
506 507 508 509 |
if (twl6040->power_count) return twl6040->pll; else return -ENODEV; |
f19b2823f mfd: twl6040: Add... |
510 511 512 513 514 |
} EXPORT_SYMBOL(twl6040_get_pll); unsigned int twl6040_get_sysclk(struct twl6040 *twl6040) { |
0a58da1e2 mfd: twl6040: Han... |
515 |
return twl6040->sysclk_rate; |
f19b2823f mfd: twl6040: Add... |
516 517 |
} EXPORT_SYMBOL(twl6040_get_sysclk); |
70601ec10 MFD: twl6040: fun... |
518 519 520 |
/* Get the combined status of the vibra control register */ int twl6040_get_vibralr_status(struct twl6040 *twl6040) { |
c6f39257c mfd: twl6040: Use... |
521 522 |
unsigned int reg; int ret; |
70601ec10 MFD: twl6040: fun... |
523 |
u8 status; |
c6f39257c mfd: twl6040: Use... |
524 525 526 527 528 529 530 531 532 |
ret = regmap_read(twl6040->regmap, TWL6040_REG_VIBCTLL, ®); if (ret != 0) return ret; status = reg; ret = regmap_read(twl6040->regmap, TWL6040_REG_VIBCTLR, ®); if (ret != 0) return ret; status |= reg; |
70601ec10 MFD: twl6040: fun... |
533 534 535 536 537 |
status &= (TWL6040_VIBENA | TWL6040_VIBSEL); return status; } EXPORT_SYMBOL(twl6040_get_vibralr_status); |
0f962ae2d MFD: twl6040: Use... |
538 539 540 541 542 543 544 545 546 547 548 |
static struct resource twl6040_vibra_rsrc[] = { { .flags = IORESOURCE_IRQ, }, }; static struct resource twl6040_codec_rsrc[] = { { .flags = IORESOURCE_IRQ, }, }; |
8eaeb9393 mfd: Convert twl6... |
549 |
static bool twl6040_readable_reg(struct device *dev, unsigned int reg) |
f19b2823f mfd: twl6040: Add... |
550 |
{ |
8eaeb9393 mfd: Convert twl6... |
551 552 553 554 555 |
/* Register 0 is not readable */ if (!reg) return false; return true; } |
c6f39257c mfd: twl6040: Use... |
556 557 558 |
static bool twl6040_volatile_reg(struct device *dev, unsigned int reg) { switch (reg) { |
c7f9129d2 mfd: twl6040: reg... |
559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 |
case TWL6040_REG_ASICID: case TWL6040_REG_ASICREV: case TWL6040_REG_INTID: case TWL6040_REG_LPPLLCTL: case TWL6040_REG_HPPLLCTL: case TWL6040_REG_STATUS: return true; default: return false; } } static bool twl6040_writeable_reg(struct device *dev, unsigned int reg) { switch (reg) { case TWL6040_REG_ASICID: case TWL6040_REG_ASICREV: case TWL6040_REG_STATUS: |
c6f39257c mfd: twl6040: Use... |
577 578 579 580 581 |
return false; default: return true; } } |
de1e23f83 mfd: twl6040: Con... |
582 |
static const struct regmap_config twl6040_regmap_config = { |
8eaeb9393 mfd: Convert twl6... |
583 584 |
.reg_bits = 8, .val_bits = 8, |
c7f9129d2 mfd: twl6040: reg... |
585 586 587 |
.reg_defaults = twl6040_defaults, .num_reg_defaults = ARRAY_SIZE(twl6040_defaults), |
8eaeb9393 mfd: Convert twl6... |
588 589 590 |
.max_register = TWL6040_REG_STATUS, /* 0x2e */ .readable_reg = twl6040_readable_reg, |
c6f39257c mfd: twl6040: Use... |
591 |
.volatile_reg = twl6040_volatile_reg, |
c7f9129d2 mfd: twl6040: reg... |
592 |
.writeable_reg = twl6040_writeable_reg, |
c6f39257c mfd: twl6040: Use... |
593 594 |
.cache_type = REGCACHE_RBTREE, |
1c96a2f67 regmap: split up ... |
595 596 |
.use_single_read = true, .use_single_write = true, |
8eaeb9393 mfd: Convert twl6... |
597 |
}; |
ab7edb149 mfd: twl6040: Con... |
598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 |
static const struct regmap_irq twl6040_irqs[] = { { .reg_offset = 0, .mask = TWL6040_THINT, }, { .reg_offset = 0, .mask = TWL6040_PLUGINT | TWL6040_UNPLUGINT, }, { .reg_offset = 0, .mask = TWL6040_HOOKINT, }, { .reg_offset = 0, .mask = TWL6040_HFINT, }, { .reg_offset = 0, .mask = TWL6040_VIBINT, }, { .reg_offset = 0, .mask = TWL6040_READYINT, }, }; static struct regmap_irq_chip twl6040_irq_chip = { .name = "twl6040", .irqs = twl6040_irqs, .num_irqs = ARRAY_SIZE(twl6040_irqs), .num_regs = 1, .status_base = TWL6040_REG_INTID, .mask_base = TWL6040_REG_INTMR, }; |
612b95cd7 Drivers: mfd: rem... |
616 617 |
static int twl6040_probe(struct i2c_client *client, const struct i2c_device_id *id) |
8eaeb9393 mfd: Convert twl6... |
618 |
{ |
37e13ceca mfd: Add support ... |
619 |
struct device_node *node = client->dev.of_node; |
f19b2823f mfd: twl6040: Add... |
620 621 |
struct twl6040 *twl6040; struct mfd_cell *cell = NULL; |
1f01d60e4 mfd: Register the... |
622 |
int irq, ret, children = 0; |
f19b2823f mfd: twl6040: Add... |
623 |
|
df04b6242 mfd: twl6040: Rem... |
624 625 626 |
if (!node) { dev_err(&client->dev, "of node is missing "); |
f19b2823f mfd: twl6040: Add... |
627 628 |
return -EINVAL; } |
d20e1d21f MFD: twl6040: Dem... |
629 |
/* In order to operate correctly we need valid interrupt config */ |
6712419d6 mfd: Allocate twl... |
630 |
if (!client->irq) { |
8eaeb9393 mfd: Convert twl6... |
631 632 |
dev_err(&client->dev, "Invalid IRQ configuration "); |
d20e1d21f MFD: twl6040: Dem... |
633 634 |
return -EINVAL; } |
8eaeb9393 mfd: Convert twl6... |
635 636 |
twl6040 = devm_kzalloc(&client->dev, sizeof(struct twl6040), GFP_KERNEL); |
ecc8fa1c8 mfd: twl6040: Cle... |
637 638 |
if (!twl6040) return -ENOMEM; |
8eaeb9393 mfd: Convert twl6... |
639 |
|
bbf6adc10 mfd: Convert twl6... |
640 |
twl6040->regmap = devm_regmap_init_i2c(client, &twl6040_regmap_config); |
ecc8fa1c8 mfd: twl6040: Cle... |
641 642 |
if (IS_ERR(twl6040->regmap)) return PTR_ERR(twl6040->regmap); |
f19b2823f mfd: twl6040: Add... |
643 |
|
8eaeb9393 mfd: Convert twl6... |
644 |
i2c_set_clientdata(client, twl6040); |
f19b2823f mfd: twl6040: Add... |
645 |
|
68bab8662 mfd: twl6040: Opt... |
646 647 |
twl6040->clk32k = devm_clk_get(&client->dev, "clk32k"); if (IS_ERR(twl6040->clk32k)) { |
75c08f17e mfd: twl6040: Fix... |
648 649 |
if (PTR_ERR(twl6040->clk32k) == -EPROBE_DEFER) return -EPROBE_DEFER; |
0a58da1e2 mfd: twl6040: Han... |
650 651 |
dev_dbg(&client->dev, "clk32k is not handled "); |
68bab8662 mfd: twl6040: Opt... |
652 653 |
twl6040->clk32k = NULL; } |
0a58da1e2 mfd: twl6040: Han... |
654 655 656 657 658 659 660 661 |
twl6040->mclk = devm_clk_get(&client->dev, "mclk"); if (IS_ERR(twl6040->mclk)) { if (PTR_ERR(twl6040->mclk) == -EPROBE_DEFER) return -EPROBE_DEFER; dev_dbg(&client->dev, "mclk is not handled "); twl6040->mclk = NULL; } |
5af7df6b8 mfd: Add regulato... |
662 663 |
twl6040->supplies[0].supply = "vio"; twl6040->supplies[1].supply = "v2v1"; |
990810b03 mfd: twl6040: Use... |
664 |
ret = devm_regulator_bulk_get(&client->dev, TWL6040_NUM_SUPPLIES, |
37aefe9f0 mfd: twl6040: Cos... |
665 |
twl6040->supplies); |
5af7df6b8 mfd: Add regulato... |
666 667 668 |
if (ret != 0) { dev_err(&client->dev, "Failed to get supplies: %d ", ret); |
501d609ab mfd: twl6040: Rem... |
669 |
return ret; |
5af7df6b8 mfd: Add regulato... |
670 671 672 673 674 675 |
} ret = regulator_bulk_enable(TWL6040_NUM_SUPPLIES, twl6040->supplies); if (ret != 0) { dev_err(&client->dev, "Failed to enable supplies: %d ", ret); |
501d609ab mfd: twl6040: Rem... |
676 |
return ret; |
5af7df6b8 mfd: Add regulato... |
677 |
} |
8eaeb9393 mfd: Convert twl6... |
678 679 |
twl6040->dev = &client->dev; twl6040->irq = client->irq; |
f19b2823f mfd: twl6040: Add... |
680 681 |
mutex_init(&twl6040->mutex); |
f19b2823f mfd: twl6040: Add... |
682 |
init_completion(&twl6040->ready); |
006cea3ae mfd: twl6040: Mov... |
683 684 |
regmap_register_patch(twl6040->regmap, twl6040_patch, ARRAY_SIZE(twl6040_patch)); |
f19b2823f mfd: twl6040: Add... |
685 |
twl6040->rev = twl6040_reg_read(twl6040, TWL6040_REG_ASICREV); |
89d689983 mfd: twl6040: Che... |
686 687 688 689 |
if (twl6040->rev < 0) { dev_err(&client->dev, "Failed to read revision register: %d ", twl6040->rev); |
f2b867816 mfd: twl6040: Fix... |
690 |
ret = twl6040->rev; |
89d689983 mfd: twl6040: Che... |
691 692 |
goto gpio_err; } |
f19b2823f mfd: twl6040: Add... |
693 |
|
77f63e06c MFD: twl6040: Fix... |
694 |
/* ERRATA: Automatic power-up is not possible in ES1.0 */ |
df04b6242 mfd: twl6040: Rem... |
695 696 697 698 |
if (twl6040_get_revid(twl6040) > TWL6040_REV_ES1_0) twl6040->audpwron = of_get_named_gpio(node, "ti,audpwron-gpio", 0); else |
77f63e06c MFD: twl6040: Fix... |
699 |
twl6040->audpwron = -EINVAL; |
f19b2823f mfd: twl6040: Add... |
700 |
if (gpio_is_valid(twl6040->audpwron)) { |
990810b03 mfd: twl6040: Use... |
701 |
ret = devm_gpio_request_one(&client->dev, twl6040->audpwron, |
37aefe9f0 mfd: twl6040: Cos... |
702 |
GPIOF_OUT_INIT_LOW, "audpwron"); |
f19b2823f mfd: twl6040: Add... |
703 |
if (ret) |
5af7df6b8 mfd: Add regulato... |
704 |
goto gpio_err; |
02d02728e mfd: twl6040: Cle... |
705 706 707 |
/* Clear any pending interrupt */ twl6040_reg_read(twl6040, TWL6040_REG_INTID); |
f19b2823f mfd: twl6040: Add... |
708 |
} |
37aefe9f0 mfd: twl6040: Cos... |
709 |
ret = regmap_add_irq_chip(twl6040->regmap, twl6040->irq, IRQF_ONESHOT, |
c06f308aa mfd: twl6040: Fix... |
710 |
0, &twl6040_irq_chip, &twl6040->irq_data); |
ab7edb149 mfd: twl6040: Con... |
711 |
if (ret < 0) |
990810b03 mfd: twl6040: Use... |
712 |
goto gpio_err; |
d20e1d21f MFD: twl6040: Dem... |
713 |
|
ab7edb149 mfd: twl6040: Con... |
714 |
twl6040->irq_ready = regmap_irq_get_virq(twl6040->irq_data, |
37aefe9f0 mfd: twl6040: Cos... |
715 |
TWL6040_IRQ_READY); |
ab7edb149 mfd: twl6040: Con... |
716 |
twl6040->irq_th = regmap_irq_get_virq(twl6040->irq_data, |
37aefe9f0 mfd: twl6040: Cos... |
717 |
TWL6040_IRQ_TH); |
ab7edb149 mfd: twl6040: Con... |
718 |
|
990810b03 mfd: twl6040: Use... |
719 |
ret = devm_request_threaded_irq(twl6040->dev, twl6040->irq_ready, NULL, |
37aefe9f0 mfd: twl6040: Cos... |
720 721 |
twl6040_readyint_handler, IRQF_ONESHOT, "twl6040_irq_ready", twl6040); |
d20e1d21f MFD: twl6040: Dem... |
722 |
if (ret) { |
1ac96265a mfd: twl6040: Cor... |
723 724 725 726 |
dev_err(twl6040->dev, "READY IRQ request failed: %d ", ret); goto readyirq_err; } |
990810b03 mfd: twl6040: Use... |
727 |
ret = devm_request_threaded_irq(twl6040->dev, twl6040->irq_th, NULL, |
37aefe9f0 mfd: twl6040: Cos... |
728 729 |
twl6040_thint_handler, IRQF_ONESHOT, "twl6040_irq_th", twl6040); |
1ac96265a mfd: twl6040: Cor... |
730 731 732 |
if (ret) { dev_err(twl6040->dev, "Thermal IRQ request failed: %d ", ret); |
fc5ee96fe mfd: twl6040: Dro... |
733 |
goto readyirq_err; |
f19b2823f mfd: twl6040: Add... |
734 |
} |
1f01d60e4 mfd: Register the... |
735 736 737 |
/* * The main functionality of twl6040 to provide audio on OMAP4+ systems. * We can add the ASoC codec child whenever this driver has been loaded. |
1f01d60e4 mfd: Register the... |
738 |
*/ |
ab7edb149 mfd: twl6040: Con... |
739 |
irq = regmap_irq_get_virq(twl6040->irq_data, TWL6040_IRQ_PLUG); |
1f01d60e4 mfd: Register the... |
740 741 742 743 744 745 |
cell = &twl6040->cells[children]; cell->name = "twl6040-codec"; twl6040_codec_rsrc[0].start = irq; twl6040_codec_rsrc[0].end = irq; cell->resources = twl6040_codec_rsrc; cell->num_resources = ARRAY_SIZE(twl6040_codec_rsrc); |
1f01d60e4 mfd: Register the... |
746 |
children++; |
f19b2823f mfd: twl6040: Add... |
747 |
|
df04b6242 mfd: twl6040: Rem... |
748 749 |
/* Vibra input driver support */ if (twl6040_has_vibra(node)) { |
ab7edb149 mfd: twl6040: Con... |
750 |
irq = regmap_irq_get_virq(twl6040->irq_data, TWL6040_IRQ_VIB); |
0f962ae2d MFD: twl6040: Use... |
751 |
|
f19b2823f mfd: twl6040: Add... |
752 753 |
cell = &twl6040->cells[children]; cell->name = "twl6040-vibra"; |
0f962ae2d MFD: twl6040: Use... |
754 755 756 757 |
twl6040_vibra_rsrc[0].start = irq; twl6040_vibra_rsrc[0].end = irq; cell->resources = twl6040_vibra_rsrc; cell->num_resources = ARRAY_SIZE(twl6040_vibra_rsrc); |
f19b2823f mfd: twl6040: Add... |
758 759 |
children++; } |
df04b6242 mfd: twl6040: Rem... |
760 761 762 763 |
/* GPO support */ cell = &twl6040->cells[children]; cell->name = "twl6040-gpo"; children++; |
5cbe786a6 mfd: twl6040: Add... |
764 |
|
0133d3234 mfd: twl6040: Reg... |
765 766 767 768 |
/* PDM clock support */ cell = &twl6040->cells[children]; cell->name = "twl6040-pdmclk"; children++; |
c7f9129d2 mfd: twl6040: reg... |
769 770 771 |
/* The chip is powered down so mark regmap to cache only and dirty */ regcache_cache_only(twl6040->regmap, true); regcache_mark_dirty(twl6040->regmap); |
1f01d60e4 mfd: Register the... |
772 |
ret = mfd_add_devices(&client->dev, -1, twl6040->cells, children, |
55692af5e mfd: core: Push i... |
773 |
NULL, 0, NULL); |
1f01d60e4 mfd: Register the... |
774 |
if (ret) |
fc5ee96fe mfd: twl6040: Dro... |
775 |
goto readyirq_err; |
f19b2823f mfd: twl6040: Add... |
776 777 |
return 0; |
1ac96265a mfd: twl6040: Cor... |
778 |
readyirq_err: |
ab7edb149 mfd: twl6040: Con... |
779 |
regmap_del_irq_chip(twl6040->irq, twl6040->irq_data); |
5af7df6b8 mfd: Add regulato... |
780 781 |
gpio_err: regulator_bulk_disable(TWL6040_NUM_SUPPLIES, twl6040->supplies); |
f19b2823f mfd: twl6040: Add... |
782 783 |
return ret; } |
612b95cd7 Drivers: mfd: rem... |
784 |
static int twl6040_remove(struct i2c_client *client) |
f19b2823f mfd: twl6040: Add... |
785 |
{ |
8eaeb9393 mfd: Convert twl6... |
786 |
struct twl6040 *twl6040 = i2c_get_clientdata(client); |
f19b2823f mfd: twl6040: Add... |
787 788 789 |
if (twl6040->power_count) twl6040_power(twl6040, 0); |
ab7edb149 mfd: twl6040: Con... |
790 |
regmap_del_irq_chip(twl6040->irq, twl6040->irq_data); |
f19b2823f mfd: twl6040: Add... |
791 |
|
8eaeb9393 mfd: Convert twl6... |
792 |
mfd_remove_devices(&client->dev); |
f19b2823f mfd: twl6040: Add... |
793 |
|
5af7df6b8 mfd: Add regulato... |
794 |
regulator_bulk_disable(TWL6040_NUM_SUPPLIES, twl6040->supplies); |
5af7df6b8 mfd: Add regulato... |
795 |
|
f19b2823f mfd: twl6040: Add... |
796 797 |
return 0; } |
8eaeb9393 mfd: Convert twl6... |
798 799 |
static const struct i2c_device_id twl6040_i2c_id[] = { { "twl6040", 0, }, |
1fc74aef0 mfd: Add support ... |
800 |
{ "twl6041", 0, }, |
8eaeb9393 mfd: Convert twl6... |
801 802 803 804 805 806 807 |
{ }, }; MODULE_DEVICE_TABLE(i2c, twl6040_i2c_id); static struct i2c_driver twl6040_driver = { .driver = { .name = "twl6040", |
8eaeb9393 mfd: Convert twl6... |
808 |
}, |
f19b2823f mfd: twl6040: Add... |
809 |
.probe = twl6040_probe, |
612b95cd7 Drivers: mfd: rem... |
810 |
.remove = twl6040_remove, |
8eaeb9393 mfd: Convert twl6... |
811 |
.id_table = twl6040_i2c_id, |
f19b2823f mfd: twl6040: Add... |
812 |
}; |
8eaeb9393 mfd: Convert twl6... |
813 |
module_i2c_driver(twl6040_driver); |
f19b2823f mfd: twl6040: Add... |
814 815 816 817 818 |
MODULE_DESCRIPTION("TWL6040 MFD"); MODULE_AUTHOR("Misael Lopez Cruz <misael.lopez@ti.com>"); MODULE_AUTHOR("Jorge Eduardo Candelaria <jorge.candelaria@ti.com>"); MODULE_LICENSE("GPL"); |