1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * cs35l32.c -- CS35L32 ALSA SoC audio driver |
4 | * |
5 | * Copyright 2014 CirrusLogic, Inc. |
6 | * |
7 | * Author: Brian Austin <brian.austin@cirrus.com> |
8 | */ |
9 | |
10 | #include <linux/module.h> |
11 | #include <linux/moduleparam.h> |
12 | #include <linux/kernel.h> |
13 | #include <linux/init.h> |
14 | #include <linux/delay.h> |
15 | #include <linux/i2c.h> |
16 | #include <linux/regmap.h> |
17 | #include <linux/slab.h> |
18 | #include <linux/platform_device.h> |
19 | #include <linux/regulator/consumer.h> |
20 | #include <linux/gpio/consumer.h> |
21 | #include <linux/of.h> |
22 | #include <sound/core.h> |
23 | #include <sound/pcm.h> |
24 | #include <sound/pcm_params.h> |
25 | #include <sound/soc.h> |
26 | #include <sound/soc-dapm.h> |
27 | #include <sound/initval.h> |
28 | #include <sound/tlv.h> |
29 | #include <dt-bindings/sound/cs35l32.h> |
30 | |
31 | #include "cs35l32.h" |
32 | #include "cirrus_legacy.h" |
33 | |
34 | #define CS35L32_NUM_SUPPLIES 2 |
35 | static const char *const cs35l32_supply_names[CS35L32_NUM_SUPPLIES] = { |
36 | "VA" , |
37 | "VP" , |
38 | }; |
39 | |
40 | struct cs35l32_private { |
41 | struct regmap *regmap; |
42 | struct snd_soc_component *component; |
43 | struct regulator_bulk_data supplies[CS35L32_NUM_SUPPLIES]; |
44 | struct cs35l32_platform_data pdata; |
45 | struct gpio_desc *reset_gpio; |
46 | }; |
47 | |
48 | static const struct reg_default cs35l32_reg_defaults[] = { |
49 | |
50 | { 0x06, 0x04 }, /* Power Ctl 1 */ |
51 | { 0x07, 0xE8 }, /* Power Ctl 2 */ |
52 | { 0x08, 0x40 }, /* Clock Ctl */ |
53 | { 0x09, 0x20 }, /* Low Battery Threshold */ |
54 | { 0x0A, 0x00 }, /* Voltage Monitor [RO] */ |
55 | { 0x0B, 0x40 }, /* Conv Peak Curr Protection CTL */ |
56 | { 0x0C, 0x07 }, /* IMON Scaling */ |
57 | { 0x0D, 0x03 }, /* Audio/LED Pwr Manager */ |
58 | { 0x0F, 0x20 }, /* Serial Port Control */ |
59 | { 0x10, 0x14 }, /* Class D Amp CTL */ |
60 | { 0x11, 0x00 }, /* Protection Release CTL */ |
61 | { 0x12, 0xFF }, /* Interrupt Mask 1 */ |
62 | { 0x13, 0xFF }, /* Interrupt Mask 2 */ |
63 | { 0x14, 0xFF }, /* Interrupt Mask 3 */ |
64 | { 0x19, 0x00 }, /* LED Flash Mode Current */ |
65 | { 0x1A, 0x00 }, /* LED Movie Mode Current */ |
66 | { 0x1B, 0x20 }, /* LED Flash Timer */ |
67 | { 0x1C, 0x00 }, /* LED Flash Inhibit Current */ |
68 | }; |
69 | |
70 | static bool cs35l32_readable_register(struct device *dev, unsigned int reg) |
71 | { |
72 | switch (reg) { |
73 | case CS35L32_DEVID_AB ... CS35L32_AUDIO_LED_MNGR: |
74 | case CS35L32_ADSP_CTL ... CS35L32_FLASH_INHIBIT: |
75 | return true; |
76 | default: |
77 | return false; |
78 | } |
79 | } |
80 | |
81 | static bool cs35l32_volatile_register(struct device *dev, unsigned int reg) |
82 | { |
83 | switch (reg) { |
84 | case CS35L32_DEVID_AB ... CS35L32_REV_ID: |
85 | case CS35L32_INT_STATUS_1 ... CS35L32_LED_STATUS: |
86 | return true; |
87 | default: |
88 | return false; |
89 | } |
90 | } |
91 | |
92 | static bool cs35l32_precious_register(struct device *dev, unsigned int reg) |
93 | { |
94 | switch (reg) { |
95 | case CS35L32_INT_STATUS_1 ... CS35L32_LED_STATUS: |
96 | return true; |
97 | default: |
98 | return false; |
99 | } |
100 | } |
101 | |
102 | static DECLARE_TLV_DB_SCALE(classd_ctl_tlv, 900, 300, 0); |
103 | |
104 | static const struct snd_kcontrol_new imon_ctl = |
105 | SOC_DAPM_SINGLE("Switch" , CS35L32_PWRCTL2, 6, 1, 1); |
106 | |
107 | static const struct snd_kcontrol_new vmon_ctl = |
108 | SOC_DAPM_SINGLE("Switch" , CS35L32_PWRCTL2, 7, 1, 1); |
109 | |
110 | static const struct snd_kcontrol_new vpmon_ctl = |
111 | SOC_DAPM_SINGLE("Switch" , CS35L32_PWRCTL2, 5, 1, 1); |
112 | |
113 | static const struct snd_kcontrol_new cs35l32_snd_controls[] = { |
114 | SOC_SINGLE_TLV("Speaker Volume" , CS35L32_CLASSD_CTL, |
115 | 3, 0x04, 1, classd_ctl_tlv), |
116 | SOC_SINGLE("Zero Cross Switch" , CS35L32_CLASSD_CTL, 2, 1, 0), |
117 | SOC_SINGLE("Gain Manager Switch" , CS35L32_AUDIO_LED_MNGR, 3, 1, 0), |
118 | }; |
119 | |
120 | static const struct snd_soc_dapm_widget cs35l32_dapm_widgets[] = { |
121 | |
122 | SND_SOC_DAPM_SUPPLY("BOOST" , CS35L32_PWRCTL1, 2, 1, NULL, 0), |
123 | SND_SOC_DAPM_OUT_DRV("Speaker" , CS35L32_PWRCTL1, 7, 1, NULL, 0), |
124 | |
125 | SND_SOC_DAPM_AIF_OUT("SDOUT" , NULL, 0, CS35L32_PWRCTL2, 3, 1), |
126 | |
127 | SND_SOC_DAPM_INPUT("VP" ), |
128 | SND_SOC_DAPM_INPUT("ISENSE" ), |
129 | SND_SOC_DAPM_INPUT("VSENSE" ), |
130 | |
131 | SND_SOC_DAPM_SWITCH("VMON ADC" , CS35L32_PWRCTL2, 7, 1, &vmon_ctl), |
132 | SND_SOC_DAPM_SWITCH("IMON ADC" , CS35L32_PWRCTL2, 6, 1, &imon_ctl), |
133 | SND_SOC_DAPM_SWITCH("VPMON ADC" , CS35L32_PWRCTL2, 5, 1, &vpmon_ctl), |
134 | }; |
135 | |
136 | static const struct snd_soc_dapm_route cs35l32_audio_map[] = { |
137 | |
138 | {"Speaker" , NULL, "BOOST" }, |
139 | |
140 | {"VMON ADC" , NULL, "VSENSE" }, |
141 | {"IMON ADC" , NULL, "ISENSE" }, |
142 | {"VPMON ADC" , NULL, "VP" }, |
143 | |
144 | {"SDOUT" , "Switch" , "VMON ADC" }, |
145 | {"SDOUT" , "Switch" , "IMON ADC" }, |
146 | {"SDOUT" , "Switch" , "VPMON ADC" }, |
147 | |
148 | {"Capture" , NULL, "SDOUT" }, |
149 | }; |
150 | |
151 | static int cs35l32_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) |
152 | { |
153 | struct snd_soc_component *component = codec_dai->component; |
154 | |
155 | switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { |
156 | case SND_SOC_DAIFMT_CBM_CFM: |
157 | snd_soc_component_update_bits(component, CS35L32_ADSP_CTL, |
158 | CS35L32_ADSP_MASTER_MASK, |
159 | CS35L32_ADSP_MASTER_MASK); |
160 | break; |
161 | case SND_SOC_DAIFMT_CBS_CFS: |
162 | snd_soc_component_update_bits(component, CS35L32_ADSP_CTL, |
163 | CS35L32_ADSP_MASTER_MASK, val: 0); |
164 | break; |
165 | default: |
166 | return -EINVAL; |
167 | } |
168 | |
169 | return 0; |
170 | } |
171 | |
172 | static int cs35l32_set_tristate(struct snd_soc_dai *dai, int tristate) |
173 | { |
174 | struct snd_soc_component *component = dai->component; |
175 | |
176 | return snd_soc_component_update_bits(component, CS35L32_PWRCTL2, |
177 | CS35L32_SDOUT_3ST, val: tristate << 3); |
178 | } |
179 | |
180 | static const struct snd_soc_dai_ops cs35l32_ops = { |
181 | .set_fmt = cs35l32_set_dai_fmt, |
182 | .set_tristate = cs35l32_set_tristate, |
183 | }; |
184 | |
185 | static struct snd_soc_dai_driver cs35l32_dai[] = { |
186 | { |
187 | .name = "cs35l32-monitor" , |
188 | .id = 0, |
189 | .capture = { |
190 | .stream_name = "Capture" , |
191 | .channels_min = 2, |
192 | .channels_max = 2, |
193 | .rates = CS35L32_RATES, |
194 | .formats = CS35L32_FORMATS, |
195 | }, |
196 | .ops = &cs35l32_ops, |
197 | .symmetric_rate = 1, |
198 | } |
199 | }; |
200 | |
201 | static int cs35l32_component_set_sysclk(struct snd_soc_component *component, |
202 | int clk_id, int source, unsigned int freq, int dir) |
203 | { |
204 | unsigned int val; |
205 | |
206 | switch (freq) { |
207 | case 6000000: |
208 | val = CS35L32_MCLK_RATIO; |
209 | break; |
210 | case 12000000: |
211 | val = CS35L32_MCLK_DIV2_MASK | CS35L32_MCLK_RATIO; |
212 | break; |
213 | case 6144000: |
214 | val = 0; |
215 | break; |
216 | case 12288000: |
217 | val = CS35L32_MCLK_DIV2_MASK; |
218 | break; |
219 | default: |
220 | return -EINVAL; |
221 | } |
222 | |
223 | return snd_soc_component_update_bits(component, CS35L32_CLK_CTL, |
224 | CS35L32_MCLK_DIV2_MASK | CS35L32_MCLK_RATIO_MASK, val); |
225 | } |
226 | |
227 | static const struct snd_soc_component_driver soc_component_dev_cs35l32 = { |
228 | .set_sysclk = cs35l32_component_set_sysclk, |
229 | .controls = cs35l32_snd_controls, |
230 | .num_controls = ARRAY_SIZE(cs35l32_snd_controls), |
231 | .dapm_widgets = cs35l32_dapm_widgets, |
232 | .num_dapm_widgets = ARRAY_SIZE(cs35l32_dapm_widgets), |
233 | .dapm_routes = cs35l32_audio_map, |
234 | .num_dapm_routes = ARRAY_SIZE(cs35l32_audio_map), |
235 | .idle_bias_on = 1, |
236 | .use_pmdown_time = 1, |
237 | .endianness = 1, |
238 | }; |
239 | |
240 | /* Current and threshold powerup sequence Pg37 in datasheet */ |
241 | static const struct reg_sequence cs35l32_monitor_patch[] = { |
242 | |
243 | { 0x00, 0x99 }, |
244 | { 0x48, 0x17 }, |
245 | { 0x49, 0x56 }, |
246 | { 0x43, 0x01 }, |
247 | { 0x3B, 0x62 }, |
248 | { 0x3C, 0x80 }, |
249 | { 0x00, 0x00 }, |
250 | }; |
251 | |
252 | static const struct regmap_config cs35l32_regmap = { |
253 | .reg_bits = 8, |
254 | .val_bits = 8, |
255 | |
256 | .max_register = CS35L32_MAX_REGISTER, |
257 | .reg_defaults = cs35l32_reg_defaults, |
258 | .num_reg_defaults = ARRAY_SIZE(cs35l32_reg_defaults), |
259 | .volatile_reg = cs35l32_volatile_register, |
260 | .readable_reg = cs35l32_readable_register, |
261 | .precious_reg = cs35l32_precious_register, |
262 | .cache_type = REGCACHE_MAPLE, |
263 | |
264 | .use_single_read = true, |
265 | .use_single_write = true, |
266 | }; |
267 | |
268 | static int cs35l32_handle_of_data(struct i2c_client *i2c_client, |
269 | struct cs35l32_platform_data *pdata) |
270 | { |
271 | struct device_node *np = i2c_client->dev.of_node; |
272 | unsigned int val; |
273 | |
274 | if (of_property_read_u32(np, propname: "cirrus,sdout-share" , out_value: &val) >= 0) |
275 | pdata->sdout_share = val; |
276 | |
277 | if (of_property_read_u32(np, propname: "cirrus,boost-manager" , out_value: &val)) |
278 | val = -1u; |
279 | |
280 | switch (val) { |
281 | case CS35L32_BOOST_MGR_AUTO: |
282 | case CS35L32_BOOST_MGR_AUTO_AUDIO: |
283 | case CS35L32_BOOST_MGR_BYPASS: |
284 | case CS35L32_BOOST_MGR_FIXED: |
285 | pdata->boost_mng = val; |
286 | break; |
287 | case -1u: |
288 | default: |
289 | dev_err(&i2c_client->dev, |
290 | "Wrong cirrus,boost-manager DT value %d\n" , val); |
291 | pdata->boost_mng = CS35L32_BOOST_MGR_BYPASS; |
292 | } |
293 | |
294 | if (of_property_read_u32(np, propname: "cirrus,sdout-datacfg" , out_value: &val)) |
295 | val = -1u; |
296 | switch (val) { |
297 | case CS35L32_DATA_CFG_LR_VP: |
298 | case CS35L32_DATA_CFG_LR_STAT: |
299 | case CS35L32_DATA_CFG_LR: |
300 | case CS35L32_DATA_CFG_LR_VPSTAT: |
301 | pdata->sdout_datacfg = val; |
302 | break; |
303 | case -1u: |
304 | default: |
305 | dev_err(&i2c_client->dev, |
306 | "Wrong cirrus,sdout-datacfg DT value %d\n" , val); |
307 | pdata->sdout_datacfg = CS35L32_DATA_CFG_LR; |
308 | } |
309 | |
310 | if (of_property_read_u32(np, propname: "cirrus,battery-threshold" , out_value: &val)) |
311 | val = -1u; |
312 | switch (val) { |
313 | case CS35L32_BATT_THRESH_3_1V: |
314 | case CS35L32_BATT_THRESH_3_2V: |
315 | case CS35L32_BATT_THRESH_3_3V: |
316 | case CS35L32_BATT_THRESH_3_4V: |
317 | pdata->batt_thresh = val; |
318 | break; |
319 | case -1u: |
320 | default: |
321 | dev_err(&i2c_client->dev, |
322 | "Wrong cirrus,battery-threshold DT value %d\n" , val); |
323 | pdata->batt_thresh = CS35L32_BATT_THRESH_3_3V; |
324 | } |
325 | |
326 | if (of_property_read_u32(np, propname: "cirrus,battery-recovery" , out_value: &val)) |
327 | val = -1u; |
328 | switch (val) { |
329 | case CS35L32_BATT_RECOV_3_1V: |
330 | case CS35L32_BATT_RECOV_3_2V: |
331 | case CS35L32_BATT_RECOV_3_3V: |
332 | case CS35L32_BATT_RECOV_3_4V: |
333 | case CS35L32_BATT_RECOV_3_5V: |
334 | case CS35L32_BATT_RECOV_3_6V: |
335 | pdata->batt_recov = val; |
336 | break; |
337 | case -1u: |
338 | default: |
339 | dev_err(&i2c_client->dev, |
340 | "Wrong cirrus,battery-recovery DT value %d\n" , val); |
341 | pdata->batt_recov = CS35L32_BATT_RECOV_3_4V; |
342 | } |
343 | |
344 | return 0; |
345 | } |
346 | |
347 | static int cs35l32_i2c_probe(struct i2c_client *i2c_client) |
348 | { |
349 | struct cs35l32_private *cs35l32; |
350 | struct cs35l32_platform_data *pdata = |
351 | dev_get_platdata(dev: &i2c_client->dev); |
352 | int ret, i, devid; |
353 | unsigned int reg; |
354 | |
355 | cs35l32 = devm_kzalloc(dev: &i2c_client->dev, size: sizeof(*cs35l32), GFP_KERNEL); |
356 | if (!cs35l32) |
357 | return -ENOMEM; |
358 | |
359 | i2c_set_clientdata(client: i2c_client, data: cs35l32); |
360 | |
361 | cs35l32->regmap = devm_regmap_init_i2c(i2c_client, &cs35l32_regmap); |
362 | if (IS_ERR(ptr: cs35l32->regmap)) { |
363 | ret = PTR_ERR(ptr: cs35l32->regmap); |
364 | dev_err(&i2c_client->dev, "regmap_init() failed: %d\n" , ret); |
365 | return ret; |
366 | } |
367 | |
368 | if (pdata) { |
369 | cs35l32->pdata = *pdata; |
370 | } else { |
371 | pdata = devm_kzalloc(dev: &i2c_client->dev, size: sizeof(*pdata), |
372 | GFP_KERNEL); |
373 | if (!pdata) |
374 | return -ENOMEM; |
375 | |
376 | if (i2c_client->dev.of_node) { |
377 | ret = cs35l32_handle_of_data(i2c_client, |
378 | pdata: &cs35l32->pdata); |
379 | if (ret != 0) |
380 | return ret; |
381 | } |
382 | } |
383 | |
384 | for (i = 0; i < ARRAY_SIZE(cs35l32->supplies); i++) |
385 | cs35l32->supplies[i].supply = cs35l32_supply_names[i]; |
386 | |
387 | ret = devm_regulator_bulk_get(dev: &i2c_client->dev, |
388 | ARRAY_SIZE(cs35l32->supplies), |
389 | consumers: cs35l32->supplies); |
390 | if (ret != 0) { |
391 | dev_err(&i2c_client->dev, |
392 | "Failed to request supplies: %d\n" , ret); |
393 | return ret; |
394 | } |
395 | |
396 | ret = regulator_bulk_enable(ARRAY_SIZE(cs35l32->supplies), |
397 | consumers: cs35l32->supplies); |
398 | if (ret != 0) { |
399 | dev_err(&i2c_client->dev, |
400 | "Failed to enable supplies: %d\n" , ret); |
401 | return ret; |
402 | } |
403 | |
404 | /* Reset the Device */ |
405 | cs35l32->reset_gpio = devm_gpiod_get_optional(dev: &i2c_client->dev, |
406 | con_id: "reset" , flags: GPIOD_OUT_LOW); |
407 | if (IS_ERR(ptr: cs35l32->reset_gpio)) { |
408 | ret = PTR_ERR(ptr: cs35l32->reset_gpio); |
409 | goto err_supplies; |
410 | } |
411 | |
412 | gpiod_set_value_cansleep(desc: cs35l32->reset_gpio, value: 1); |
413 | |
414 | /* initialize codec */ |
415 | devid = cirrus_read_device_id(regmap: cs35l32->regmap, CS35L32_DEVID_AB); |
416 | if (devid < 0) { |
417 | ret = devid; |
418 | dev_err(&i2c_client->dev, "Failed to read device ID: %d\n" , ret); |
419 | goto err_disable; |
420 | } |
421 | |
422 | if (devid != CS35L32_CHIP_ID) { |
423 | ret = -ENODEV; |
424 | dev_err(&i2c_client->dev, |
425 | "CS35L32 Device ID (%X). Expected %X\n" , |
426 | devid, CS35L32_CHIP_ID); |
427 | goto err_disable; |
428 | } |
429 | |
430 | ret = regmap_read(map: cs35l32->regmap, CS35L32_REV_ID, val: ®); |
431 | if (ret < 0) { |
432 | dev_err(&i2c_client->dev, "Get Revision ID failed\n" ); |
433 | goto err_disable; |
434 | } |
435 | |
436 | ret = regmap_register_patch(map: cs35l32->regmap, regs: cs35l32_monitor_patch, |
437 | ARRAY_SIZE(cs35l32_monitor_patch)); |
438 | if (ret < 0) { |
439 | dev_err(&i2c_client->dev, "Failed to apply errata patch\n" ); |
440 | goto err_disable; |
441 | } |
442 | |
443 | dev_info(&i2c_client->dev, |
444 | "Cirrus Logic CS35L32, Revision: %02X\n" , reg & 0xFF); |
445 | |
446 | /* Setup VBOOST Management */ |
447 | if (cs35l32->pdata.boost_mng) |
448 | regmap_update_bits(map: cs35l32->regmap, CS35L32_AUDIO_LED_MNGR, |
449 | CS35L32_BOOST_MASK, |
450 | val: cs35l32->pdata.boost_mng); |
451 | |
452 | /* Setup ADSP Format Config */ |
453 | if (cs35l32->pdata.sdout_share) |
454 | regmap_update_bits(map: cs35l32->regmap, CS35L32_ADSP_CTL, |
455 | CS35L32_ADSP_SHARE_MASK, |
456 | val: cs35l32->pdata.sdout_share << 3); |
457 | |
458 | /* Setup ADSP Data Configuration */ |
459 | if (cs35l32->pdata.sdout_datacfg) |
460 | regmap_update_bits(map: cs35l32->regmap, CS35L32_ADSP_CTL, |
461 | CS35L32_ADSP_DATACFG_MASK, |
462 | val: cs35l32->pdata.sdout_datacfg << 4); |
463 | |
464 | /* Setup Low Battery Recovery */ |
465 | if (cs35l32->pdata.batt_recov) |
466 | regmap_update_bits(map: cs35l32->regmap, CS35L32_BATT_THRESHOLD, |
467 | CS35L32_BATT_REC_MASK, |
468 | val: cs35l32->pdata.batt_recov << 1); |
469 | |
470 | /* Setup Low Battery Threshold */ |
471 | if (cs35l32->pdata.batt_thresh) |
472 | regmap_update_bits(map: cs35l32->regmap, CS35L32_BATT_THRESHOLD, |
473 | CS35L32_BATT_THRESH_MASK, |
474 | val: cs35l32->pdata.batt_thresh << 4); |
475 | |
476 | /* Power down the AMP */ |
477 | regmap_update_bits(map: cs35l32->regmap, CS35L32_PWRCTL1, CS35L32_PDN_AMP, |
478 | CS35L32_PDN_AMP); |
479 | |
480 | /* Clear MCLK Error Bit since we don't have the clock yet */ |
481 | regmap_read(map: cs35l32->regmap, CS35L32_INT_STATUS_1, val: ®); |
482 | |
483 | ret = devm_snd_soc_register_component(dev: &i2c_client->dev, |
484 | component_driver: &soc_component_dev_cs35l32, dai_drv: cs35l32_dai, |
485 | ARRAY_SIZE(cs35l32_dai)); |
486 | if (ret < 0) |
487 | goto err_disable; |
488 | |
489 | return 0; |
490 | |
491 | err_disable: |
492 | gpiod_set_value_cansleep(desc: cs35l32->reset_gpio, value: 0); |
493 | err_supplies: |
494 | regulator_bulk_disable(ARRAY_SIZE(cs35l32->supplies), |
495 | consumers: cs35l32->supplies); |
496 | return ret; |
497 | } |
498 | |
499 | static void cs35l32_i2c_remove(struct i2c_client *i2c_client) |
500 | { |
501 | struct cs35l32_private *cs35l32 = i2c_get_clientdata(client: i2c_client); |
502 | |
503 | /* Hold down reset */ |
504 | gpiod_set_value_cansleep(desc: cs35l32->reset_gpio, value: 0); |
505 | } |
506 | |
507 | #ifdef CONFIG_PM |
508 | static int cs35l32_runtime_suspend(struct device *dev) |
509 | { |
510 | struct cs35l32_private *cs35l32 = dev_get_drvdata(dev); |
511 | |
512 | regcache_cache_only(map: cs35l32->regmap, enable: true); |
513 | regcache_mark_dirty(map: cs35l32->regmap); |
514 | |
515 | /* Hold down reset */ |
516 | gpiod_set_value_cansleep(desc: cs35l32->reset_gpio, value: 0); |
517 | |
518 | /* remove power */ |
519 | regulator_bulk_disable(ARRAY_SIZE(cs35l32->supplies), |
520 | consumers: cs35l32->supplies); |
521 | |
522 | return 0; |
523 | } |
524 | |
525 | static int cs35l32_runtime_resume(struct device *dev) |
526 | { |
527 | struct cs35l32_private *cs35l32 = dev_get_drvdata(dev); |
528 | int ret; |
529 | |
530 | /* Enable power */ |
531 | ret = regulator_bulk_enable(ARRAY_SIZE(cs35l32->supplies), |
532 | consumers: cs35l32->supplies); |
533 | if (ret != 0) { |
534 | dev_err(dev, "Failed to enable supplies: %d\n" , |
535 | ret); |
536 | return ret; |
537 | } |
538 | |
539 | gpiod_set_value_cansleep(desc: cs35l32->reset_gpio, value: 1); |
540 | |
541 | regcache_cache_only(map: cs35l32->regmap, enable: false); |
542 | regcache_sync(map: cs35l32->regmap); |
543 | |
544 | return 0; |
545 | } |
546 | #endif |
547 | |
548 | static const struct dev_pm_ops cs35l32_runtime_pm = { |
549 | SET_RUNTIME_PM_OPS(cs35l32_runtime_suspend, cs35l32_runtime_resume, |
550 | NULL) |
551 | }; |
552 | |
553 | static const struct of_device_id cs35l32_of_match[] = { |
554 | { .compatible = "cirrus,cs35l32" , }, |
555 | {}, |
556 | }; |
557 | MODULE_DEVICE_TABLE(of, cs35l32_of_match); |
558 | |
559 | |
560 | static const struct i2c_device_id cs35l32_id[] = { |
561 | {"cs35l32" , 0}, |
562 | {} |
563 | }; |
564 | |
565 | MODULE_DEVICE_TABLE(i2c, cs35l32_id); |
566 | |
567 | static struct i2c_driver cs35l32_i2c_driver = { |
568 | .driver = { |
569 | .name = "cs35l32" , |
570 | .pm = &cs35l32_runtime_pm, |
571 | .of_match_table = cs35l32_of_match, |
572 | }, |
573 | .id_table = cs35l32_id, |
574 | .probe = cs35l32_i2c_probe, |
575 | .remove = cs35l32_i2c_remove, |
576 | }; |
577 | |
578 | module_i2c_driver(cs35l32_i2c_driver); |
579 | |
580 | MODULE_DESCRIPTION("ASoC CS35L32 driver" ); |
581 | MODULE_AUTHOR("Brian Austin, Cirrus Logic Inc, <brian.austin@cirrus.com>" ); |
582 | MODULE_LICENSE("GPL" ); |
583 | |