174dc55edSMark Brown /* 274dc55edSMark Brown * wm8961.c -- WM8961 ALSA SoC Audio driver 374dc55edSMark Brown * 474dc55edSMark Brown * Author: Mark Brown 574dc55edSMark Brown * 674dc55edSMark Brown * This program is free software; you can redistribute it and/or modify 774dc55edSMark Brown * it under the terms of the GNU General Public License version 2 as 874dc55edSMark Brown * published by the Free Software Foundation. 974dc55edSMark Brown * 1074dc55edSMark Brown * Currently unimplemented features: 1174dc55edSMark Brown * - ALC 1274dc55edSMark Brown */ 1374dc55edSMark Brown 1474dc55edSMark Brown #include <linux/module.h> 1574dc55edSMark Brown #include <linux/moduleparam.h> 1674dc55edSMark Brown #include <linux/init.h> 1774dc55edSMark Brown #include <linux/delay.h> 1874dc55edSMark Brown #include <linux/pm.h> 1974dc55edSMark Brown #include <linux/i2c.h> 2074dc55edSMark Brown #include <linux/platform_device.h> 215a0e3ad6STejun Heo #include <linux/slab.h> 2274dc55edSMark Brown #include <sound/core.h> 2374dc55edSMark Brown #include <sound/pcm.h> 2474dc55edSMark Brown #include <sound/pcm_params.h> 2574dc55edSMark Brown #include <sound/soc.h> 2674dc55edSMark Brown #include <sound/initval.h> 2774dc55edSMark Brown #include <sound/tlv.h> 2874dc55edSMark Brown 2974dc55edSMark Brown #include "wm8961.h" 3074dc55edSMark Brown 3174dc55edSMark Brown #define WM8961_MAX_REGISTER 0xFC 3274dc55edSMark Brown 3374dc55edSMark Brown static u16 wm8961_reg_defaults[] = { 3474dc55edSMark Brown 0x009F, /* R0 - Left Input volume */ 3574dc55edSMark Brown 0x009F, /* R1 - Right Input volume */ 3674dc55edSMark Brown 0x0000, /* R2 - LOUT1 volume */ 3774dc55edSMark Brown 0x0000, /* R3 - ROUT1 volume */ 3874dc55edSMark Brown 0x0020, /* R4 - Clocking1 */ 3974dc55edSMark Brown 0x0008, /* R5 - ADC & DAC Control 1 */ 4074dc55edSMark Brown 0x0000, /* R6 - ADC & DAC Control 2 */ 4174dc55edSMark Brown 0x000A, /* R7 - Audio Interface 0 */ 4274dc55edSMark Brown 0x01F4, /* R8 - Clocking2 */ 4374dc55edSMark Brown 0x0000, /* R9 - Audio Interface 1 */ 4474dc55edSMark Brown 0x00FF, /* R10 - Left DAC volume */ 4574dc55edSMark Brown 0x00FF, /* R11 - Right DAC volume */ 4674dc55edSMark Brown 0x0000, /* R12 */ 4774dc55edSMark Brown 0x0000, /* R13 */ 4874dc55edSMark Brown 0x0040, /* R14 - Audio Interface 2 */ 4974dc55edSMark Brown 0x0000, /* R15 - Software Reset */ 5074dc55edSMark Brown 0x0000, /* R16 */ 5174dc55edSMark Brown 0x007B, /* R17 - ALC1 */ 5274dc55edSMark Brown 0x0000, /* R18 - ALC2 */ 5374dc55edSMark Brown 0x0032, /* R19 - ALC3 */ 5474dc55edSMark Brown 0x0000, /* R20 - Noise Gate */ 5574dc55edSMark Brown 0x00C0, /* R21 - Left ADC volume */ 5674dc55edSMark Brown 0x00C0, /* R22 - Right ADC volume */ 5774dc55edSMark Brown 0x0120, /* R23 - Additional control(1) */ 5874dc55edSMark Brown 0x0000, /* R24 - Additional control(2) */ 5974dc55edSMark Brown 0x0000, /* R25 - Pwr Mgmt (1) */ 6074dc55edSMark Brown 0x0000, /* R26 - Pwr Mgmt (2) */ 6174dc55edSMark Brown 0x0000, /* R27 - Additional Control (3) */ 6274dc55edSMark Brown 0x0000, /* R28 - Anti-pop */ 6374dc55edSMark Brown 0x0000, /* R29 */ 6474dc55edSMark Brown 0x005F, /* R30 - Clocking 3 */ 6574dc55edSMark Brown 0x0000, /* R31 */ 6674dc55edSMark Brown 0x0000, /* R32 - ADCL signal path */ 6774dc55edSMark Brown 0x0000, /* R33 - ADCR signal path */ 6874dc55edSMark Brown 0x0000, /* R34 */ 6974dc55edSMark Brown 0x0000, /* R35 */ 7074dc55edSMark Brown 0x0000, /* R36 */ 7174dc55edSMark Brown 0x0000, /* R37 */ 7274dc55edSMark Brown 0x0000, /* R38 */ 7374dc55edSMark Brown 0x0000, /* R39 */ 7474dc55edSMark Brown 0x0000, /* R40 - LOUT2 volume */ 7574dc55edSMark Brown 0x0000, /* R41 - ROUT2 volume */ 7674dc55edSMark Brown 0x0000, /* R42 */ 7774dc55edSMark Brown 0x0000, /* R43 */ 7874dc55edSMark Brown 0x0000, /* R44 */ 7974dc55edSMark Brown 0x0000, /* R45 */ 8074dc55edSMark Brown 0x0000, /* R46 */ 8174dc55edSMark Brown 0x0000, /* R47 - Pwr Mgmt (3) */ 8274dc55edSMark Brown 0x0023, /* R48 - Additional Control (4) */ 8374dc55edSMark Brown 0x0000, /* R49 - Class D Control 1 */ 8474dc55edSMark Brown 0x0000, /* R50 */ 8574dc55edSMark Brown 0x0003, /* R51 - Class D Control 2 */ 8674dc55edSMark Brown 0x0000, /* R52 */ 8774dc55edSMark Brown 0x0000, /* R53 */ 8874dc55edSMark Brown 0x0000, /* R54 */ 8974dc55edSMark Brown 0x0000, /* R55 */ 9074dc55edSMark Brown 0x0106, /* R56 - Clocking 4 */ 9174dc55edSMark Brown 0x0000, /* R57 - DSP Sidetone 0 */ 9274dc55edSMark Brown 0x0000, /* R58 - DSP Sidetone 1 */ 9374dc55edSMark Brown 0x0000, /* R59 */ 9474dc55edSMark Brown 0x0000, /* R60 - DC Servo 0 */ 9574dc55edSMark Brown 0x0000, /* R61 - DC Servo 1 */ 9674dc55edSMark Brown 0x0000, /* R62 */ 9774dc55edSMark Brown 0x015E, /* R63 - DC Servo 3 */ 9874dc55edSMark Brown 0x0010, /* R64 */ 9974dc55edSMark Brown 0x0010, /* R65 - DC Servo 5 */ 10074dc55edSMark Brown 0x0000, /* R66 */ 10174dc55edSMark Brown 0x0001, /* R67 */ 10274dc55edSMark Brown 0x0003, /* R68 - Analogue PGA Bias */ 10374dc55edSMark Brown 0x0000, /* R69 - Analogue HP 0 */ 10474dc55edSMark Brown 0x0060, /* R70 */ 10574dc55edSMark Brown 0x01FB, /* R71 - Analogue HP 2 */ 10674dc55edSMark Brown 0x0000, /* R72 - Charge Pump 1 */ 10774dc55edSMark Brown 0x0065, /* R73 */ 10874dc55edSMark Brown 0x005F, /* R74 */ 10974dc55edSMark Brown 0x0059, /* R75 */ 11074dc55edSMark Brown 0x006B, /* R76 */ 11174dc55edSMark Brown 0x0038, /* R77 */ 11274dc55edSMark Brown 0x000C, /* R78 */ 11374dc55edSMark Brown 0x000A, /* R79 */ 11474dc55edSMark Brown 0x006B, /* R80 */ 11574dc55edSMark Brown 0x0000, /* R81 */ 11674dc55edSMark Brown 0x0000, /* R82 - Charge Pump B */ 11774dc55edSMark Brown 0x0087, /* R83 */ 11874dc55edSMark Brown 0x0000, /* R84 */ 11974dc55edSMark Brown 0x005C, /* R85 */ 12074dc55edSMark Brown 0x0000, /* R86 */ 12174dc55edSMark Brown 0x0000, /* R87 - Write Sequencer 1 */ 12274dc55edSMark Brown 0x0000, /* R88 - Write Sequencer 2 */ 12374dc55edSMark Brown 0x0000, /* R89 - Write Sequencer 3 */ 12474dc55edSMark Brown 0x0000, /* R90 - Write Sequencer 4 */ 12574dc55edSMark Brown 0x0000, /* R91 - Write Sequencer 5 */ 12674dc55edSMark Brown 0x0000, /* R92 - Write Sequencer 6 */ 12774dc55edSMark Brown 0x0000, /* R93 - Write Sequencer 7 */ 12874dc55edSMark Brown 0x0000, /* R94 */ 12974dc55edSMark Brown 0x0000, /* R95 */ 13074dc55edSMark Brown 0x0000, /* R96 */ 13174dc55edSMark Brown 0x0000, /* R97 */ 13274dc55edSMark Brown 0x0000, /* R98 */ 13374dc55edSMark Brown 0x0000, /* R99 */ 13474dc55edSMark Brown 0x0000, /* R100 */ 13574dc55edSMark Brown 0x0000, /* R101 */ 13674dc55edSMark Brown 0x0000, /* R102 */ 13774dc55edSMark Brown 0x0000, /* R103 */ 13874dc55edSMark Brown 0x0000, /* R104 */ 13974dc55edSMark Brown 0x0000, /* R105 */ 14074dc55edSMark Brown 0x0000, /* R106 */ 14174dc55edSMark Brown 0x0000, /* R107 */ 14274dc55edSMark Brown 0x0000, /* R108 */ 14374dc55edSMark Brown 0x0000, /* R109 */ 14474dc55edSMark Brown 0x0000, /* R110 */ 14574dc55edSMark Brown 0x0000, /* R111 */ 14674dc55edSMark Brown 0x0000, /* R112 */ 14774dc55edSMark Brown 0x0000, /* R113 */ 14874dc55edSMark Brown 0x0000, /* R114 */ 14974dc55edSMark Brown 0x0000, /* R115 */ 15074dc55edSMark Brown 0x0000, /* R116 */ 15174dc55edSMark Brown 0x0000, /* R117 */ 15274dc55edSMark Brown 0x0000, /* R118 */ 15374dc55edSMark Brown 0x0000, /* R119 */ 15474dc55edSMark Brown 0x0000, /* R120 */ 15574dc55edSMark Brown 0x0000, /* R121 */ 15674dc55edSMark Brown 0x0000, /* R122 */ 15774dc55edSMark Brown 0x0000, /* R123 */ 15874dc55edSMark Brown 0x0000, /* R124 */ 15974dc55edSMark Brown 0x0000, /* R125 */ 16074dc55edSMark Brown 0x0000, /* R126 */ 16174dc55edSMark Brown 0x0000, /* R127 */ 16274dc55edSMark Brown 0x0000, /* R128 */ 16374dc55edSMark Brown 0x0000, /* R129 */ 16474dc55edSMark Brown 0x0000, /* R130 */ 16574dc55edSMark Brown 0x0000, /* R131 */ 16674dc55edSMark Brown 0x0000, /* R132 */ 16774dc55edSMark Brown 0x0000, /* R133 */ 16874dc55edSMark Brown 0x0000, /* R134 */ 16974dc55edSMark Brown 0x0000, /* R135 */ 17074dc55edSMark Brown 0x0000, /* R136 */ 17174dc55edSMark Brown 0x0000, /* R137 */ 17274dc55edSMark Brown 0x0000, /* R138 */ 17374dc55edSMark Brown 0x0000, /* R139 */ 17474dc55edSMark Brown 0x0000, /* R140 */ 17574dc55edSMark Brown 0x0000, /* R141 */ 17674dc55edSMark Brown 0x0000, /* R142 */ 17774dc55edSMark Brown 0x0000, /* R143 */ 17874dc55edSMark Brown 0x0000, /* R144 */ 17974dc55edSMark Brown 0x0000, /* R145 */ 18074dc55edSMark Brown 0x0000, /* R146 */ 18174dc55edSMark Brown 0x0000, /* R147 */ 18274dc55edSMark Brown 0x0000, /* R148 */ 18374dc55edSMark Brown 0x0000, /* R149 */ 18474dc55edSMark Brown 0x0000, /* R150 */ 18574dc55edSMark Brown 0x0000, /* R151 */ 18674dc55edSMark Brown 0x0000, /* R152 */ 18774dc55edSMark Brown 0x0000, /* R153 */ 18874dc55edSMark Brown 0x0000, /* R154 */ 18974dc55edSMark Brown 0x0000, /* R155 */ 19074dc55edSMark Brown 0x0000, /* R156 */ 19174dc55edSMark Brown 0x0000, /* R157 */ 19274dc55edSMark Brown 0x0000, /* R158 */ 19374dc55edSMark Brown 0x0000, /* R159 */ 19474dc55edSMark Brown 0x0000, /* R160 */ 19574dc55edSMark Brown 0x0000, /* R161 */ 19674dc55edSMark Brown 0x0000, /* R162 */ 19774dc55edSMark Brown 0x0000, /* R163 */ 19874dc55edSMark Brown 0x0000, /* R164 */ 19974dc55edSMark Brown 0x0000, /* R165 */ 20074dc55edSMark Brown 0x0000, /* R166 */ 20174dc55edSMark Brown 0x0000, /* R167 */ 20274dc55edSMark Brown 0x0000, /* R168 */ 20374dc55edSMark Brown 0x0000, /* R169 */ 20474dc55edSMark Brown 0x0000, /* R170 */ 20574dc55edSMark Brown 0x0000, /* R171 */ 20674dc55edSMark Brown 0x0000, /* R172 */ 20774dc55edSMark Brown 0x0000, /* R173 */ 20874dc55edSMark Brown 0x0000, /* R174 */ 20974dc55edSMark Brown 0x0000, /* R175 */ 21074dc55edSMark Brown 0x0000, /* R176 */ 21174dc55edSMark Brown 0x0000, /* R177 */ 21274dc55edSMark Brown 0x0000, /* R178 */ 21374dc55edSMark Brown 0x0000, /* R179 */ 21474dc55edSMark Brown 0x0000, /* R180 */ 21574dc55edSMark Brown 0x0000, /* R181 */ 21674dc55edSMark Brown 0x0000, /* R182 */ 21774dc55edSMark Brown 0x0000, /* R183 */ 21874dc55edSMark Brown 0x0000, /* R184 */ 21974dc55edSMark Brown 0x0000, /* R185 */ 22074dc55edSMark Brown 0x0000, /* R186 */ 22174dc55edSMark Brown 0x0000, /* R187 */ 22274dc55edSMark Brown 0x0000, /* R188 */ 22374dc55edSMark Brown 0x0000, /* R189 */ 22474dc55edSMark Brown 0x0000, /* R190 */ 22574dc55edSMark Brown 0x0000, /* R191 */ 22674dc55edSMark Brown 0x0000, /* R192 */ 22774dc55edSMark Brown 0x0000, /* R193 */ 22874dc55edSMark Brown 0x0000, /* R194 */ 22974dc55edSMark Brown 0x0000, /* R195 */ 23074dc55edSMark Brown 0x0030, /* R196 */ 23174dc55edSMark Brown 0x0006, /* R197 */ 23274dc55edSMark Brown 0x0000, /* R198 */ 23374dc55edSMark Brown 0x0060, /* R199 */ 23474dc55edSMark Brown 0x0000, /* R200 */ 23574dc55edSMark Brown 0x003F, /* R201 */ 23674dc55edSMark Brown 0x0000, /* R202 */ 23774dc55edSMark Brown 0x0000, /* R203 */ 23874dc55edSMark Brown 0x0000, /* R204 */ 23974dc55edSMark Brown 0x0001, /* R205 */ 24074dc55edSMark Brown 0x0000, /* R206 */ 24174dc55edSMark Brown 0x0181, /* R207 */ 24274dc55edSMark Brown 0x0005, /* R208 */ 24374dc55edSMark Brown 0x0008, /* R209 */ 24474dc55edSMark Brown 0x0008, /* R210 */ 24574dc55edSMark Brown 0x0000, /* R211 */ 24674dc55edSMark Brown 0x013B, /* R212 */ 24774dc55edSMark Brown 0x0000, /* R213 */ 24874dc55edSMark Brown 0x0000, /* R214 */ 24974dc55edSMark Brown 0x0000, /* R215 */ 25074dc55edSMark Brown 0x0000, /* R216 */ 25174dc55edSMark Brown 0x0070, /* R217 */ 25274dc55edSMark Brown 0x0000, /* R218 */ 25374dc55edSMark Brown 0x0000, /* R219 */ 25474dc55edSMark Brown 0x0000, /* R220 */ 25574dc55edSMark Brown 0x0000, /* R221 */ 25674dc55edSMark Brown 0x0000, /* R222 */ 25774dc55edSMark Brown 0x0003, /* R223 */ 25874dc55edSMark Brown 0x0000, /* R224 */ 25974dc55edSMark Brown 0x0000, /* R225 */ 26074dc55edSMark Brown 0x0001, /* R226 */ 26174dc55edSMark Brown 0x0008, /* R227 */ 26274dc55edSMark Brown 0x0000, /* R228 */ 26374dc55edSMark Brown 0x0000, /* R229 */ 26474dc55edSMark Brown 0x0000, /* R230 */ 26574dc55edSMark Brown 0x0000, /* R231 */ 26674dc55edSMark Brown 0x0004, /* R232 */ 26774dc55edSMark Brown 0x0000, /* R233 */ 26874dc55edSMark Brown 0x0000, /* R234 */ 26974dc55edSMark Brown 0x0000, /* R235 */ 27074dc55edSMark Brown 0x0000, /* R236 */ 27174dc55edSMark Brown 0x0000, /* R237 */ 27274dc55edSMark Brown 0x0080, /* R238 */ 27374dc55edSMark Brown 0x0000, /* R239 */ 27474dc55edSMark Brown 0x0000, /* R240 */ 27574dc55edSMark Brown 0x0000, /* R241 */ 27674dc55edSMark Brown 0x0000, /* R242 */ 27774dc55edSMark Brown 0x0000, /* R243 */ 27874dc55edSMark Brown 0x0000, /* R244 */ 27974dc55edSMark Brown 0x0052, /* R245 */ 28074dc55edSMark Brown 0x0110, /* R246 */ 28174dc55edSMark Brown 0x0040, /* R247 */ 28274dc55edSMark Brown 0x0000, /* R248 */ 28374dc55edSMark Brown 0x0030, /* R249 */ 28474dc55edSMark Brown 0x0000, /* R250 */ 28574dc55edSMark Brown 0x0000, /* R251 */ 28674dc55edSMark Brown 0x0001, /* R252 - General test 1 */ 28774dc55edSMark Brown }; 28874dc55edSMark Brown 28974dc55edSMark Brown struct wm8961_priv { 290f0fba2adSLiam Girdwood enum snd_soc_control_type control_type; 29174dc55edSMark Brown int sysclk; 29274dc55edSMark Brown }; 29374dc55edSMark Brown 294d4754ec9SDimitris Papastamos static int wm8961_volatile_register(struct snd_soc_codec *codec, unsigned int reg) 29574dc55edSMark Brown { 29674dc55edSMark Brown switch (reg) { 2978d50e447SMark Brown case WM8961_SOFTWARE_RESET: 29874dc55edSMark Brown case WM8961_WRITE_SEQUENCER_7: 29974dc55edSMark Brown case WM8961_DC_SERVO_1: 30074dc55edSMark Brown return 1; 30174dc55edSMark Brown 30274dc55edSMark Brown default: 30374dc55edSMark Brown return 0; 30474dc55edSMark Brown } 30574dc55edSMark Brown } 30674dc55edSMark Brown 30774dc55edSMark Brown static int wm8961_reset(struct snd_soc_codec *codec) 30874dc55edSMark Brown { 3098d50e447SMark Brown return snd_soc_write(codec, WM8961_SOFTWARE_RESET, 0); 31074dc55edSMark Brown } 31174dc55edSMark Brown 31274dc55edSMark Brown /* 31374dc55edSMark Brown * The headphone output supports special anti-pop sequences giving 31474dc55edSMark Brown * silent power up and power down. 31574dc55edSMark Brown */ 31674dc55edSMark Brown static int wm8961_hp_event(struct snd_soc_dapm_widget *w, 31774dc55edSMark Brown struct snd_kcontrol *kcontrol, int event) 31874dc55edSMark Brown { 31974dc55edSMark Brown struct snd_soc_codec *codec = w->codec; 3208d50e447SMark Brown u16 hp_reg = snd_soc_read(codec, WM8961_ANALOGUE_HP_0); 3218d50e447SMark Brown u16 cp_reg = snd_soc_read(codec, WM8961_CHARGE_PUMP_1); 3228d50e447SMark Brown u16 pwr_reg = snd_soc_read(codec, WM8961_PWR_MGMT_2); 3238d50e447SMark Brown u16 dcs_reg = snd_soc_read(codec, WM8961_DC_SERVO_1); 32474dc55edSMark Brown int timeout = 500; 32574dc55edSMark Brown 32674dc55edSMark Brown if (event & SND_SOC_DAPM_POST_PMU) { 32774dc55edSMark Brown /* Make sure the output is shorted */ 32874dc55edSMark Brown hp_reg &= ~(WM8961_HPR_RMV_SHORT | WM8961_HPL_RMV_SHORT); 3298d50e447SMark Brown snd_soc_write(codec, WM8961_ANALOGUE_HP_0, hp_reg); 33074dc55edSMark Brown 33174dc55edSMark Brown /* Enable the charge pump */ 33274dc55edSMark Brown cp_reg |= WM8961_CP_ENA; 3338d50e447SMark Brown snd_soc_write(codec, WM8961_CHARGE_PUMP_1, cp_reg); 33474dc55edSMark Brown mdelay(5); 33574dc55edSMark Brown 33674dc55edSMark Brown /* Enable the PGA */ 33774dc55edSMark Brown pwr_reg |= WM8961_LOUT1_PGA | WM8961_ROUT1_PGA; 3388d50e447SMark Brown snd_soc_write(codec, WM8961_PWR_MGMT_2, pwr_reg); 33974dc55edSMark Brown 34074dc55edSMark Brown /* Enable the amplifier */ 34174dc55edSMark Brown hp_reg |= WM8961_HPR_ENA | WM8961_HPL_ENA; 3428d50e447SMark Brown snd_soc_write(codec, WM8961_ANALOGUE_HP_0, hp_reg); 34374dc55edSMark Brown 34474dc55edSMark Brown /* Second stage enable */ 34574dc55edSMark Brown hp_reg |= WM8961_HPR_ENA_DLY | WM8961_HPL_ENA_DLY; 3468d50e447SMark Brown snd_soc_write(codec, WM8961_ANALOGUE_HP_0, hp_reg); 34774dc55edSMark Brown 34874dc55edSMark Brown /* Enable the DC servo & trigger startup */ 34974dc55edSMark Brown dcs_reg |= 35074dc55edSMark Brown WM8961_DCS_ENA_CHAN_HPR | WM8961_DCS_TRIG_STARTUP_HPR | 35174dc55edSMark Brown WM8961_DCS_ENA_CHAN_HPL | WM8961_DCS_TRIG_STARTUP_HPL; 35274dc55edSMark Brown dev_dbg(codec->dev, "Enabling DC servo\n"); 35374dc55edSMark Brown 3548d50e447SMark Brown snd_soc_write(codec, WM8961_DC_SERVO_1, dcs_reg); 35574dc55edSMark Brown do { 35674dc55edSMark Brown msleep(1); 3578d50e447SMark Brown dcs_reg = snd_soc_read(codec, WM8961_DC_SERVO_1); 35874dc55edSMark Brown } while (--timeout && 35974dc55edSMark Brown dcs_reg & (WM8961_DCS_TRIG_STARTUP_HPR | 36074dc55edSMark Brown WM8961_DCS_TRIG_STARTUP_HPL)); 36174dc55edSMark Brown if (dcs_reg & (WM8961_DCS_TRIG_STARTUP_HPR | 36274dc55edSMark Brown WM8961_DCS_TRIG_STARTUP_HPL)) 36374dc55edSMark Brown dev_err(codec->dev, "DC servo timed out\n"); 36474dc55edSMark Brown else 36574dc55edSMark Brown dev_dbg(codec->dev, "DC servo startup complete\n"); 36674dc55edSMark Brown 36774dc55edSMark Brown /* Enable the output stage */ 36874dc55edSMark Brown hp_reg |= WM8961_HPR_ENA_OUTP | WM8961_HPL_ENA_OUTP; 3698d50e447SMark Brown snd_soc_write(codec, WM8961_ANALOGUE_HP_0, hp_reg); 37074dc55edSMark Brown 37174dc55edSMark Brown /* Remove the short on the output stage */ 37274dc55edSMark Brown hp_reg |= WM8961_HPR_RMV_SHORT | WM8961_HPL_RMV_SHORT; 3738d50e447SMark Brown snd_soc_write(codec, WM8961_ANALOGUE_HP_0, hp_reg); 37474dc55edSMark Brown } 37574dc55edSMark Brown 37674dc55edSMark Brown if (event & SND_SOC_DAPM_PRE_PMD) { 37774dc55edSMark Brown /* Short the output */ 37874dc55edSMark Brown hp_reg &= ~(WM8961_HPR_RMV_SHORT | WM8961_HPL_RMV_SHORT); 3798d50e447SMark Brown snd_soc_write(codec, WM8961_ANALOGUE_HP_0, hp_reg); 38074dc55edSMark Brown 38174dc55edSMark Brown /* Disable the output stage */ 38274dc55edSMark Brown hp_reg &= ~(WM8961_HPR_ENA_OUTP | WM8961_HPL_ENA_OUTP); 3838d50e447SMark Brown snd_soc_write(codec, WM8961_ANALOGUE_HP_0, hp_reg); 38474dc55edSMark Brown 38574dc55edSMark Brown /* Disable DC offset cancellation */ 38674dc55edSMark Brown dcs_reg &= ~(WM8961_DCS_ENA_CHAN_HPR | 38774dc55edSMark Brown WM8961_DCS_ENA_CHAN_HPL); 3888d50e447SMark Brown snd_soc_write(codec, WM8961_DC_SERVO_1, dcs_reg); 38974dc55edSMark Brown 39074dc55edSMark Brown /* Finish up */ 39174dc55edSMark Brown hp_reg &= ~(WM8961_HPR_ENA_DLY | WM8961_HPR_ENA | 39274dc55edSMark Brown WM8961_HPL_ENA_DLY | WM8961_HPL_ENA); 3938d50e447SMark Brown snd_soc_write(codec, WM8961_ANALOGUE_HP_0, hp_reg); 39474dc55edSMark Brown 39574dc55edSMark Brown /* Disable the PGA */ 39674dc55edSMark Brown pwr_reg &= ~(WM8961_LOUT1_PGA | WM8961_ROUT1_PGA); 3978d50e447SMark Brown snd_soc_write(codec, WM8961_PWR_MGMT_2, pwr_reg); 39874dc55edSMark Brown 39974dc55edSMark Brown /* Disable the charge pump */ 40074dc55edSMark Brown dev_dbg(codec->dev, "Disabling charge pump\n"); 4018d50e447SMark Brown snd_soc_write(codec, WM8961_CHARGE_PUMP_1, 40274dc55edSMark Brown cp_reg & ~WM8961_CP_ENA); 40374dc55edSMark Brown } 40474dc55edSMark Brown 40574dc55edSMark Brown return 0; 40674dc55edSMark Brown } 40774dc55edSMark Brown 40874dc55edSMark Brown static int wm8961_spk_event(struct snd_soc_dapm_widget *w, 40974dc55edSMark Brown struct snd_kcontrol *kcontrol, int event) 41074dc55edSMark Brown { 41174dc55edSMark Brown struct snd_soc_codec *codec = w->codec; 4128d50e447SMark Brown u16 pwr_reg = snd_soc_read(codec, WM8961_PWR_MGMT_2); 4138d50e447SMark Brown u16 spk_reg = snd_soc_read(codec, WM8961_CLASS_D_CONTROL_1); 41474dc55edSMark Brown 41574dc55edSMark Brown if (event & SND_SOC_DAPM_POST_PMU) { 41674dc55edSMark Brown /* Enable the PGA */ 41774dc55edSMark Brown pwr_reg |= WM8961_SPKL_PGA | WM8961_SPKR_PGA; 4188d50e447SMark Brown snd_soc_write(codec, WM8961_PWR_MGMT_2, pwr_reg); 41974dc55edSMark Brown 42074dc55edSMark Brown /* Enable the amplifier */ 42174dc55edSMark Brown spk_reg |= WM8961_SPKL_ENA | WM8961_SPKR_ENA; 4228d50e447SMark Brown snd_soc_write(codec, WM8961_CLASS_D_CONTROL_1, spk_reg); 42374dc55edSMark Brown } 42474dc55edSMark Brown 42574dc55edSMark Brown if (event & SND_SOC_DAPM_PRE_PMD) { 42674dc55edSMark Brown /* Enable the amplifier */ 42774dc55edSMark Brown spk_reg &= ~(WM8961_SPKL_ENA | WM8961_SPKR_ENA); 4288d50e447SMark Brown snd_soc_write(codec, WM8961_CLASS_D_CONTROL_1, spk_reg); 42974dc55edSMark Brown 43074dc55edSMark Brown /* Enable the PGA */ 43174dc55edSMark Brown pwr_reg &= ~(WM8961_SPKL_PGA | WM8961_SPKR_PGA); 4328d50e447SMark Brown snd_soc_write(codec, WM8961_PWR_MGMT_2, pwr_reg); 43374dc55edSMark Brown } 43474dc55edSMark Brown 43574dc55edSMark Brown return 0; 43674dc55edSMark Brown } 43774dc55edSMark Brown 43874dc55edSMark Brown static const char *adc_hpf_text[] = { 43974dc55edSMark Brown "Hi-fi", "Voice 1", "Voice 2", "Voice 3", 44074dc55edSMark Brown }; 44174dc55edSMark Brown 44274dc55edSMark Brown static const struct soc_enum adc_hpf = 44374dc55edSMark Brown SOC_ENUM_SINGLE(WM8961_ADC_DAC_CONTROL_2, 7, 4, adc_hpf_text); 44474dc55edSMark Brown 44574dc55edSMark Brown static const char *dac_deemph_text[] = { 44674dc55edSMark Brown "None", "32kHz", "44.1kHz", "48kHz", 44774dc55edSMark Brown }; 44874dc55edSMark Brown 44974dc55edSMark Brown static const struct soc_enum dac_deemph = 45074dc55edSMark Brown SOC_ENUM_SINGLE(WM8961_ADC_DAC_CONTROL_1, 1, 4, dac_deemph_text); 45174dc55edSMark Brown 45274dc55edSMark Brown static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1); 45374dc55edSMark Brown static const DECLARE_TLV_DB_SCALE(hp_sec_tlv, -700, 100, 0); 45474dc55edSMark Brown static const DECLARE_TLV_DB_SCALE(adc_tlv, -7200, 75, 1); 45574dc55edSMark Brown static const DECLARE_TLV_DB_SCALE(sidetone_tlv, -3600, 300, 0); 45674dc55edSMark Brown static unsigned int boost_tlv[] = { 45774dc55edSMark Brown TLV_DB_RANGE_HEAD(4), 45874dc55edSMark Brown 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0), 45974dc55edSMark Brown 1, 1, TLV_DB_SCALE_ITEM(13, 0, 0), 46074dc55edSMark Brown 2, 2, TLV_DB_SCALE_ITEM(20, 0, 0), 46174dc55edSMark Brown 3, 3, TLV_DB_SCALE_ITEM(29, 0, 0), 46274dc55edSMark Brown }; 46374dc55edSMark Brown static const DECLARE_TLV_DB_SCALE(pga_tlv, -2325, 75, 0); 46474dc55edSMark Brown 46574dc55edSMark Brown static const struct snd_kcontrol_new wm8961_snd_controls[] = { 46674dc55edSMark Brown SOC_DOUBLE_R_TLV("Headphone Volume", WM8961_LOUT1_VOLUME, WM8961_ROUT1_VOLUME, 46774dc55edSMark Brown 0, 127, 0, out_tlv), 46874dc55edSMark Brown SOC_DOUBLE_TLV("Headphone Secondary Volume", WM8961_ANALOGUE_HP_2, 46974dc55edSMark Brown 6, 3, 7, 0, hp_sec_tlv), 47074dc55edSMark Brown SOC_DOUBLE_R("Headphone ZC Switch", WM8961_LOUT1_VOLUME, WM8961_ROUT1_VOLUME, 47174dc55edSMark Brown 7, 1, 0), 47274dc55edSMark Brown 47374dc55edSMark Brown SOC_DOUBLE_R_TLV("Speaker Volume", WM8961_LOUT2_VOLUME, WM8961_ROUT2_VOLUME, 47474dc55edSMark Brown 0, 127, 0, out_tlv), 47574dc55edSMark Brown SOC_DOUBLE_R("Speaker ZC Switch", WM8961_LOUT2_VOLUME, WM8961_ROUT2_VOLUME, 47674dc55edSMark Brown 7, 1, 0), 47774dc55edSMark Brown SOC_SINGLE("Speaker AC Gain", WM8961_CLASS_D_CONTROL_2, 0, 7, 0), 47874dc55edSMark Brown 47974dc55edSMark Brown SOC_SINGLE("DAC x128 OSR Switch", WM8961_ADC_DAC_CONTROL_2, 0, 1, 0), 48074dc55edSMark Brown SOC_ENUM("DAC Deemphasis", dac_deemph), 48174dc55edSMark Brown SOC_SINGLE("DAC Soft Mute Switch", WM8961_ADC_DAC_CONTROL_2, 3, 1, 0), 48274dc55edSMark Brown 48374dc55edSMark Brown SOC_DOUBLE_R_TLV("Sidetone Volume", WM8961_DSP_SIDETONE_0, 48474dc55edSMark Brown WM8961_DSP_SIDETONE_1, 4, 12, 0, sidetone_tlv), 48574dc55edSMark Brown 48674dc55edSMark Brown SOC_SINGLE("ADC High Pass Filter Switch", WM8961_ADC_DAC_CONTROL_1, 0, 1, 0), 48774dc55edSMark Brown SOC_ENUM("ADC High Pass Filter Mode", adc_hpf), 48874dc55edSMark Brown 48974dc55edSMark Brown SOC_DOUBLE_R_TLV("Capture Volume", 49074dc55edSMark Brown WM8961_LEFT_ADC_VOLUME, WM8961_RIGHT_ADC_VOLUME, 49174dc55edSMark Brown 1, 119, 0, adc_tlv), 49274dc55edSMark Brown SOC_DOUBLE_R_TLV("Capture Boost Volume", 49374dc55edSMark Brown WM8961_ADCL_SIGNAL_PATH, WM8961_ADCR_SIGNAL_PATH, 49474dc55edSMark Brown 4, 3, 0, boost_tlv), 49574dc55edSMark Brown SOC_DOUBLE_R_TLV("Capture PGA Volume", 49674dc55edSMark Brown WM8961_LEFT_INPUT_VOLUME, WM8961_RIGHT_INPUT_VOLUME, 49774dc55edSMark Brown 0, 62, 0, pga_tlv), 49874dc55edSMark Brown SOC_DOUBLE_R("Capture PGA ZC Switch", 49974dc55edSMark Brown WM8961_LEFT_INPUT_VOLUME, WM8961_RIGHT_INPUT_VOLUME, 50074dc55edSMark Brown 6, 1, 1), 50174dc55edSMark Brown SOC_DOUBLE_R("Capture PGA Switch", 50274dc55edSMark Brown WM8961_LEFT_INPUT_VOLUME, WM8961_RIGHT_INPUT_VOLUME, 50374dc55edSMark Brown 7, 1, 1), 50474dc55edSMark Brown }; 50574dc55edSMark Brown 50674dc55edSMark Brown static const char *sidetone_text[] = { 50774dc55edSMark Brown "None", "Left", "Right" 50874dc55edSMark Brown }; 50974dc55edSMark Brown 51074dc55edSMark Brown static const struct soc_enum dacl_sidetone = 51174dc55edSMark Brown SOC_ENUM_SINGLE(WM8961_DSP_SIDETONE_0, 2, 3, sidetone_text); 51274dc55edSMark Brown 51374dc55edSMark Brown static const struct soc_enum dacr_sidetone = 51474dc55edSMark Brown SOC_ENUM_SINGLE(WM8961_DSP_SIDETONE_1, 2, 3, sidetone_text); 51574dc55edSMark Brown 51674dc55edSMark Brown static const struct snd_kcontrol_new dacl_mux = 51774dc55edSMark Brown SOC_DAPM_ENUM("DACL Sidetone", dacl_sidetone); 51874dc55edSMark Brown 51974dc55edSMark Brown static const struct snd_kcontrol_new dacr_mux = 52074dc55edSMark Brown SOC_DAPM_ENUM("DACR Sidetone", dacr_sidetone); 52174dc55edSMark Brown 52274dc55edSMark Brown static const struct snd_soc_dapm_widget wm8961_dapm_widgets[] = { 52374dc55edSMark Brown SND_SOC_DAPM_INPUT("LINPUT"), 52474dc55edSMark Brown SND_SOC_DAPM_INPUT("RINPUT"), 52574dc55edSMark Brown 52674dc55edSMark Brown SND_SOC_DAPM_SUPPLY("CLK_DSP", WM8961_CLOCKING2, 4, 0, NULL, 0), 52774dc55edSMark Brown 52874dc55edSMark Brown SND_SOC_DAPM_PGA("Left Input", WM8961_PWR_MGMT_1, 5, 0, NULL, 0), 52974dc55edSMark Brown SND_SOC_DAPM_PGA("Right Input", WM8961_PWR_MGMT_1, 4, 0, NULL, 0), 53074dc55edSMark Brown 53174dc55edSMark Brown SND_SOC_DAPM_ADC("ADCL", "HiFi Capture", WM8961_PWR_MGMT_1, 3, 0), 53274dc55edSMark Brown SND_SOC_DAPM_ADC("ADCR", "HiFi Capture", WM8961_PWR_MGMT_1, 2, 0), 53374dc55edSMark Brown 53420abf088SMark Brown SND_SOC_DAPM_SUPPLY("MICBIAS", WM8961_PWR_MGMT_1, 1, 0, NULL, 0), 53574dc55edSMark Brown 53674dc55edSMark Brown SND_SOC_DAPM_MUX("DACL Sidetone", SND_SOC_NOPM, 0, 0, &dacl_mux), 53774dc55edSMark Brown SND_SOC_DAPM_MUX("DACR Sidetone", SND_SOC_NOPM, 0, 0, &dacr_mux), 53874dc55edSMark Brown 53974dc55edSMark Brown SND_SOC_DAPM_DAC("DACL", "HiFi Playback", WM8961_PWR_MGMT_2, 8, 0), 54074dc55edSMark Brown SND_SOC_DAPM_DAC("DACR", "HiFi Playback", WM8961_PWR_MGMT_2, 7, 0), 54174dc55edSMark Brown 54274dc55edSMark Brown /* Handle as a mono path for DCS */ 54374dc55edSMark Brown SND_SOC_DAPM_PGA_E("Headphone Output", SND_SOC_NOPM, 54474dc55edSMark Brown 4, 0, NULL, 0, wm8961_hp_event, 54574dc55edSMark Brown SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), 54674dc55edSMark Brown SND_SOC_DAPM_PGA_E("Speaker Output", SND_SOC_NOPM, 54774dc55edSMark Brown 4, 0, NULL, 0, wm8961_spk_event, 54874dc55edSMark Brown SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), 54974dc55edSMark Brown 55074dc55edSMark Brown SND_SOC_DAPM_OUTPUT("HP_L"), 55174dc55edSMark Brown SND_SOC_DAPM_OUTPUT("HP_R"), 55274dc55edSMark Brown SND_SOC_DAPM_OUTPUT("SPK_LN"), 55374dc55edSMark Brown SND_SOC_DAPM_OUTPUT("SPK_LP"), 55474dc55edSMark Brown SND_SOC_DAPM_OUTPUT("SPK_RN"), 55574dc55edSMark Brown SND_SOC_DAPM_OUTPUT("SPK_RP"), 55674dc55edSMark Brown }; 55774dc55edSMark Brown 55874dc55edSMark Brown 55974dc55edSMark Brown static const struct snd_soc_dapm_route audio_paths[] = { 56074dc55edSMark Brown { "DACL", NULL, "CLK_DSP" }, 56174dc55edSMark Brown { "DACL", NULL, "DACL Sidetone" }, 56274dc55edSMark Brown { "DACR", NULL, "CLK_DSP" }, 56374dc55edSMark Brown { "DACR", NULL, "DACR Sidetone" }, 56474dc55edSMark Brown 56574dc55edSMark Brown { "DACL Sidetone", "Left", "ADCL" }, 56674dc55edSMark Brown { "DACL Sidetone", "Right", "ADCR" }, 56774dc55edSMark Brown 56874dc55edSMark Brown { "DACR Sidetone", "Left", "ADCL" }, 56974dc55edSMark Brown { "DACR Sidetone", "Right", "ADCR" }, 57074dc55edSMark Brown 57174dc55edSMark Brown { "HP_L", NULL, "Headphone Output" }, 57274dc55edSMark Brown { "HP_R", NULL, "Headphone Output" }, 57374dc55edSMark Brown { "Headphone Output", NULL, "DACL" }, 57474dc55edSMark Brown { "Headphone Output", NULL, "DACR" }, 57574dc55edSMark Brown 57674dc55edSMark Brown { "SPK_LN", NULL, "Speaker Output" }, 57774dc55edSMark Brown { "SPK_LP", NULL, "Speaker Output" }, 57874dc55edSMark Brown { "SPK_RN", NULL, "Speaker Output" }, 57974dc55edSMark Brown { "SPK_RP", NULL, "Speaker Output" }, 58074dc55edSMark Brown 58174dc55edSMark Brown { "Speaker Output", NULL, "DACL" }, 58274dc55edSMark Brown { "Speaker Output", NULL, "DACR" }, 58374dc55edSMark Brown 58474dc55edSMark Brown { "ADCL", NULL, "Left Input" }, 58574dc55edSMark Brown { "ADCL", NULL, "CLK_DSP" }, 58674dc55edSMark Brown { "ADCR", NULL, "Right Input" }, 58774dc55edSMark Brown { "ADCR", NULL, "CLK_DSP" }, 58874dc55edSMark Brown 58974dc55edSMark Brown { "Left Input", NULL, "LINPUT" }, 59074dc55edSMark Brown { "Right Input", NULL, "RINPUT" }, 59174dc55edSMark Brown 59274dc55edSMark Brown }; 59374dc55edSMark Brown 59474dc55edSMark Brown /* Values for CLK_SYS_RATE */ 59574dc55edSMark Brown static struct { 59674dc55edSMark Brown int ratio; 59774dc55edSMark Brown u16 val; 59874dc55edSMark Brown } wm8961_clk_sys_ratio[] = { 59974dc55edSMark Brown { 64, 0 }, 60074dc55edSMark Brown { 128, 1 }, 60174dc55edSMark Brown { 192, 2 }, 60274dc55edSMark Brown { 256, 3 }, 60374dc55edSMark Brown { 384, 4 }, 60474dc55edSMark Brown { 512, 5 }, 60574dc55edSMark Brown { 768, 6 }, 60674dc55edSMark Brown { 1024, 7 }, 60774dc55edSMark Brown { 1408, 8 }, 60874dc55edSMark Brown { 1536, 9 }, 60974dc55edSMark Brown }; 61074dc55edSMark Brown 61174dc55edSMark Brown /* Values for SAMPLE_RATE */ 61274dc55edSMark Brown static struct { 61374dc55edSMark Brown int rate; 61474dc55edSMark Brown u16 val; 61574dc55edSMark Brown } wm8961_srate[] = { 61674dc55edSMark Brown { 48000, 0 }, 61774dc55edSMark Brown { 44100, 0 }, 61874dc55edSMark Brown { 32000, 1 }, 61974dc55edSMark Brown { 22050, 2 }, 62074dc55edSMark Brown { 24000, 2 }, 62174dc55edSMark Brown { 16000, 3 }, 62274dc55edSMark Brown { 11250, 4 }, 62374dc55edSMark Brown { 12000, 4 }, 62474dc55edSMark Brown { 8000, 5 }, 62574dc55edSMark Brown }; 62674dc55edSMark Brown 62774dc55edSMark Brown static int wm8961_hw_params(struct snd_pcm_substream *substream, 62874dc55edSMark Brown struct snd_pcm_hw_params *params, 62974dc55edSMark Brown struct snd_soc_dai *dai) 63074dc55edSMark Brown { 63174dc55edSMark Brown struct snd_soc_codec *codec = dai->codec; 632b2c812e2SMark Brown struct wm8961_priv *wm8961 = snd_soc_codec_get_drvdata(codec); 63374dc55edSMark Brown int i, best, target, fs; 63474dc55edSMark Brown u16 reg; 63574dc55edSMark Brown 63674dc55edSMark Brown fs = params_rate(params); 63774dc55edSMark Brown 63874dc55edSMark Brown if (!wm8961->sysclk) { 63974dc55edSMark Brown dev_err(codec->dev, "MCLK has not been specified\n"); 64074dc55edSMark Brown return -EINVAL; 64174dc55edSMark Brown } 64274dc55edSMark Brown 64374dc55edSMark Brown /* Find the closest sample rate for the filters */ 64474dc55edSMark Brown best = 0; 64574dc55edSMark Brown for (i = 0; i < ARRAY_SIZE(wm8961_srate); i++) { 64674dc55edSMark Brown if (abs(wm8961_srate[i].rate - fs) < 64774dc55edSMark Brown abs(wm8961_srate[best].rate - fs)) 64874dc55edSMark Brown best = i; 64974dc55edSMark Brown } 6508d50e447SMark Brown reg = snd_soc_read(codec, WM8961_ADDITIONAL_CONTROL_3); 65174dc55edSMark Brown reg &= ~WM8961_SAMPLE_RATE_MASK; 65274dc55edSMark Brown reg |= wm8961_srate[best].val; 6538d50e447SMark Brown snd_soc_write(codec, WM8961_ADDITIONAL_CONTROL_3, reg); 65474dc55edSMark Brown dev_dbg(codec->dev, "Selected SRATE %dHz for %dHz\n", 65574dc55edSMark Brown wm8961_srate[best].rate, fs); 65674dc55edSMark Brown 65774dc55edSMark Brown /* Select a CLK_SYS/fs ratio equal to or higher than required */ 65874dc55edSMark Brown target = wm8961->sysclk / fs; 65974dc55edSMark Brown 66074dc55edSMark Brown if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && target < 64) { 66174dc55edSMark Brown dev_err(codec->dev, 66274dc55edSMark Brown "SYSCLK must be at least 64*fs for DAC\n"); 66374dc55edSMark Brown return -EINVAL; 66474dc55edSMark Brown } 66574dc55edSMark Brown if (substream->stream == SNDRV_PCM_STREAM_CAPTURE && target < 256) { 66674dc55edSMark Brown dev_err(codec->dev, 66774dc55edSMark Brown "SYSCLK must be at least 256*fs for ADC\n"); 66874dc55edSMark Brown return -EINVAL; 66974dc55edSMark Brown } 67074dc55edSMark Brown 67174dc55edSMark Brown for (i = 0; i < ARRAY_SIZE(wm8961_clk_sys_ratio); i++) { 67274dc55edSMark Brown if (wm8961_clk_sys_ratio[i].ratio >= target) 67374dc55edSMark Brown break; 67474dc55edSMark Brown } 67574dc55edSMark Brown if (i == ARRAY_SIZE(wm8961_clk_sys_ratio)) { 67674dc55edSMark Brown dev_err(codec->dev, "Unable to generate CLK_SYS_RATE\n"); 67774dc55edSMark Brown return -EINVAL; 67874dc55edSMark Brown } 67974dc55edSMark Brown dev_dbg(codec->dev, "Selected CLK_SYS_RATE of %d for %d/%d=%d\n", 68074dc55edSMark Brown wm8961_clk_sys_ratio[i].ratio, wm8961->sysclk, fs, 68174dc55edSMark Brown wm8961->sysclk / fs); 68274dc55edSMark Brown 6838d50e447SMark Brown reg = snd_soc_read(codec, WM8961_CLOCKING_4); 68474dc55edSMark Brown reg &= ~WM8961_CLK_SYS_RATE_MASK; 68574dc55edSMark Brown reg |= wm8961_clk_sys_ratio[i].val << WM8961_CLK_SYS_RATE_SHIFT; 6868d50e447SMark Brown snd_soc_write(codec, WM8961_CLOCKING_4, reg); 68774dc55edSMark Brown 6888d50e447SMark Brown reg = snd_soc_read(codec, WM8961_AUDIO_INTERFACE_0); 68974dc55edSMark Brown reg &= ~WM8961_WL_MASK; 69074dc55edSMark Brown switch (params_format(params)) { 69174dc55edSMark Brown case SNDRV_PCM_FORMAT_S16_LE: 69274dc55edSMark Brown break; 69374dc55edSMark Brown case SNDRV_PCM_FORMAT_S20_3LE: 69474dc55edSMark Brown reg |= 1 << WM8961_WL_SHIFT; 69574dc55edSMark Brown break; 69674dc55edSMark Brown case SNDRV_PCM_FORMAT_S24_LE: 69774dc55edSMark Brown reg |= 2 << WM8961_WL_SHIFT; 69874dc55edSMark Brown break; 69974dc55edSMark Brown case SNDRV_PCM_FORMAT_S32_LE: 70074dc55edSMark Brown reg |= 3 << WM8961_WL_SHIFT; 70174dc55edSMark Brown break; 70274dc55edSMark Brown default: 70374dc55edSMark Brown return -EINVAL; 70474dc55edSMark Brown } 7058d50e447SMark Brown snd_soc_write(codec, WM8961_AUDIO_INTERFACE_0, reg); 70674dc55edSMark Brown 70774dc55edSMark Brown /* Sloping stop-band filter is recommended for <= 24kHz */ 7088d50e447SMark Brown reg = snd_soc_read(codec, WM8961_ADC_DAC_CONTROL_2); 70974dc55edSMark Brown if (fs <= 24000) 71074dc55edSMark Brown reg |= WM8961_DACSLOPE; 71174dc55edSMark Brown else 71208b1a384SAxel Lin reg &= ~WM8961_DACSLOPE; 7138d50e447SMark Brown snd_soc_write(codec, WM8961_ADC_DAC_CONTROL_2, reg); 71474dc55edSMark Brown 71574dc55edSMark Brown return 0; 71674dc55edSMark Brown } 71774dc55edSMark Brown 71874dc55edSMark Brown static int wm8961_set_sysclk(struct snd_soc_dai *dai, int clk_id, 71974dc55edSMark Brown unsigned int freq, 72074dc55edSMark Brown int dir) 72174dc55edSMark Brown { 72274dc55edSMark Brown struct snd_soc_codec *codec = dai->codec; 723b2c812e2SMark Brown struct wm8961_priv *wm8961 = snd_soc_codec_get_drvdata(codec); 7248d50e447SMark Brown u16 reg = snd_soc_read(codec, WM8961_CLOCKING1); 72574dc55edSMark Brown 72674dc55edSMark Brown if (freq > 33000000) { 72774dc55edSMark Brown dev_err(codec->dev, "MCLK must be <33MHz\n"); 72874dc55edSMark Brown return -EINVAL; 72974dc55edSMark Brown } 73074dc55edSMark Brown 73174dc55edSMark Brown if (freq > 16500000) { 73274dc55edSMark Brown dev_dbg(codec->dev, "Using MCLK/2 for %dHz MCLK\n", freq); 73374dc55edSMark Brown reg |= WM8961_MCLKDIV; 73474dc55edSMark Brown freq /= 2; 73574dc55edSMark Brown } else { 73674dc55edSMark Brown dev_dbg(codec->dev, "Using MCLK/1 for %dHz MCLK\n", freq); 7372f7dceedSAxel Lin reg &= ~WM8961_MCLKDIV; 73874dc55edSMark Brown } 73974dc55edSMark Brown 7408d50e447SMark Brown snd_soc_write(codec, WM8961_CLOCKING1, reg); 74174dc55edSMark Brown 74274dc55edSMark Brown wm8961->sysclk = freq; 74374dc55edSMark Brown 74474dc55edSMark Brown return 0; 74574dc55edSMark Brown } 74674dc55edSMark Brown 74774dc55edSMark Brown static int wm8961_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) 74874dc55edSMark Brown { 74974dc55edSMark Brown struct snd_soc_codec *codec = dai->codec; 7508d50e447SMark Brown u16 aif = snd_soc_read(codec, WM8961_AUDIO_INTERFACE_0); 75174dc55edSMark Brown 75274dc55edSMark Brown aif &= ~(WM8961_BCLKINV | WM8961_LRP | 75374dc55edSMark Brown WM8961_MS | WM8961_FORMAT_MASK); 75474dc55edSMark Brown 75574dc55edSMark Brown switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { 75674dc55edSMark Brown case SND_SOC_DAIFMT_CBM_CFM: 75774dc55edSMark Brown aif |= WM8961_MS; 75874dc55edSMark Brown break; 75974dc55edSMark Brown case SND_SOC_DAIFMT_CBS_CFS: 76074dc55edSMark Brown break; 76174dc55edSMark Brown default: 76274dc55edSMark Brown return -EINVAL; 76374dc55edSMark Brown } 76474dc55edSMark Brown 76574dc55edSMark Brown switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { 76674dc55edSMark Brown case SND_SOC_DAIFMT_RIGHT_J: 76774dc55edSMark Brown break; 76874dc55edSMark Brown 76974dc55edSMark Brown case SND_SOC_DAIFMT_LEFT_J: 77074dc55edSMark Brown aif |= 1; 77174dc55edSMark Brown break; 77274dc55edSMark Brown 77374dc55edSMark Brown case SND_SOC_DAIFMT_I2S: 77474dc55edSMark Brown aif |= 2; 77574dc55edSMark Brown break; 77674dc55edSMark Brown 77774dc55edSMark Brown case SND_SOC_DAIFMT_DSP_B: 77874dc55edSMark Brown aif |= WM8961_LRP; 77974dc55edSMark Brown case SND_SOC_DAIFMT_DSP_A: 78074dc55edSMark Brown aif |= 3; 78174dc55edSMark Brown switch (fmt & SND_SOC_DAIFMT_INV_MASK) { 78274dc55edSMark Brown case SND_SOC_DAIFMT_NB_NF: 78374dc55edSMark Brown case SND_SOC_DAIFMT_IB_NF: 78474dc55edSMark Brown break; 78574dc55edSMark Brown default: 78674dc55edSMark Brown return -EINVAL; 78774dc55edSMark Brown } 78874dc55edSMark Brown break; 78974dc55edSMark Brown 79074dc55edSMark Brown default: 79174dc55edSMark Brown return -EINVAL; 79274dc55edSMark Brown } 79374dc55edSMark Brown 79474dc55edSMark Brown switch (fmt & SND_SOC_DAIFMT_INV_MASK) { 79574dc55edSMark Brown case SND_SOC_DAIFMT_NB_NF: 79674dc55edSMark Brown break; 79774dc55edSMark Brown case SND_SOC_DAIFMT_NB_IF: 79874dc55edSMark Brown aif |= WM8961_LRP; 79974dc55edSMark Brown break; 80074dc55edSMark Brown case SND_SOC_DAIFMT_IB_NF: 80174dc55edSMark Brown aif |= WM8961_BCLKINV; 80274dc55edSMark Brown break; 80374dc55edSMark Brown case SND_SOC_DAIFMT_IB_IF: 80474dc55edSMark Brown aif |= WM8961_BCLKINV | WM8961_LRP; 80574dc55edSMark Brown break; 80674dc55edSMark Brown default: 80774dc55edSMark Brown return -EINVAL; 80874dc55edSMark Brown } 80974dc55edSMark Brown 8108d50e447SMark Brown return snd_soc_write(codec, WM8961_AUDIO_INTERFACE_0, aif); 81174dc55edSMark Brown } 81274dc55edSMark Brown 81374dc55edSMark Brown static int wm8961_set_tristate(struct snd_soc_dai *dai, int tristate) 81474dc55edSMark Brown { 81574dc55edSMark Brown struct snd_soc_codec *codec = dai->codec; 8168d50e447SMark Brown u16 reg = snd_soc_read(codec, WM8961_ADDITIONAL_CONTROL_2); 81774dc55edSMark Brown 81874dc55edSMark Brown if (tristate) 81974dc55edSMark Brown reg |= WM8961_TRIS; 82074dc55edSMark Brown else 82174dc55edSMark Brown reg &= ~WM8961_TRIS; 82274dc55edSMark Brown 8238d50e447SMark Brown return snd_soc_write(codec, WM8961_ADDITIONAL_CONTROL_2, reg); 82474dc55edSMark Brown } 82574dc55edSMark Brown 82674dc55edSMark Brown static int wm8961_digital_mute(struct snd_soc_dai *dai, int mute) 82774dc55edSMark Brown { 82874dc55edSMark Brown struct snd_soc_codec *codec = dai->codec; 8298d50e447SMark Brown u16 reg = snd_soc_read(codec, WM8961_ADC_DAC_CONTROL_1); 83074dc55edSMark Brown 83174dc55edSMark Brown if (mute) 83274dc55edSMark Brown reg |= WM8961_DACMU; 83374dc55edSMark Brown else 83474dc55edSMark Brown reg &= ~WM8961_DACMU; 83574dc55edSMark Brown 83674dc55edSMark Brown msleep(17); 83774dc55edSMark Brown 8388d50e447SMark Brown return snd_soc_write(codec, WM8961_ADC_DAC_CONTROL_1, reg); 83974dc55edSMark Brown } 84074dc55edSMark Brown 84174dc55edSMark Brown static int wm8961_set_clkdiv(struct snd_soc_dai *dai, int div_id, int div) 84274dc55edSMark Brown { 84374dc55edSMark Brown struct snd_soc_codec *codec = dai->codec; 84474dc55edSMark Brown u16 reg; 84574dc55edSMark Brown 84674dc55edSMark Brown switch (div_id) { 84774dc55edSMark Brown case WM8961_BCLK: 8488d50e447SMark Brown reg = snd_soc_read(codec, WM8961_CLOCKING2); 84974dc55edSMark Brown reg &= ~WM8961_BCLKDIV_MASK; 85074dc55edSMark Brown reg |= div; 8518d50e447SMark Brown snd_soc_write(codec, WM8961_CLOCKING2, reg); 85274dc55edSMark Brown break; 85374dc55edSMark Brown 85474dc55edSMark Brown case WM8961_LRCLK: 8558d50e447SMark Brown reg = snd_soc_read(codec, WM8961_AUDIO_INTERFACE_2); 85674dc55edSMark Brown reg &= ~WM8961_LRCLK_RATE_MASK; 85774dc55edSMark Brown reg |= div; 8588d50e447SMark Brown snd_soc_write(codec, WM8961_AUDIO_INTERFACE_2, reg); 85974dc55edSMark Brown break; 86074dc55edSMark Brown 86174dc55edSMark Brown default: 86274dc55edSMark Brown return -EINVAL; 86374dc55edSMark Brown } 86474dc55edSMark Brown 86574dc55edSMark Brown return 0; 86674dc55edSMark Brown } 86774dc55edSMark Brown 86874dc55edSMark Brown static int wm8961_set_bias_level(struct snd_soc_codec *codec, 86974dc55edSMark Brown enum snd_soc_bias_level level) 87074dc55edSMark Brown { 87174dc55edSMark Brown u16 reg; 87274dc55edSMark Brown 87374dc55edSMark Brown /* This is all slightly unusual since we have no bypass paths 87474dc55edSMark Brown * and the output amplifier structure means we can just slam 87574dc55edSMark Brown * the biases straight up rather than having to ramp them 87674dc55edSMark Brown * slowly. 87774dc55edSMark Brown */ 87874dc55edSMark Brown switch (level) { 87974dc55edSMark Brown case SND_SOC_BIAS_ON: 88074dc55edSMark Brown break; 88174dc55edSMark Brown 88274dc55edSMark Brown case SND_SOC_BIAS_PREPARE: 883ce6120ccSLiam Girdwood if (codec->dapm.bias_level == SND_SOC_BIAS_STANDBY) { 88474dc55edSMark Brown /* Enable bias generation */ 8858d50e447SMark Brown reg = snd_soc_read(codec, WM8961_ANTI_POP); 88674dc55edSMark Brown reg |= WM8961_BUFIOEN | WM8961_BUFDCOPEN; 8878d50e447SMark Brown snd_soc_write(codec, WM8961_ANTI_POP, reg); 88874dc55edSMark Brown 88974dc55edSMark Brown /* VMID=2*50k, VREF */ 8908d50e447SMark Brown reg = snd_soc_read(codec, WM8961_PWR_MGMT_1); 89174dc55edSMark Brown reg &= ~WM8961_VMIDSEL_MASK; 89274dc55edSMark Brown reg |= (1 << WM8961_VMIDSEL_SHIFT) | WM8961_VREF; 8938d50e447SMark Brown snd_soc_write(codec, WM8961_PWR_MGMT_1, reg); 89474dc55edSMark Brown } 89574dc55edSMark Brown break; 89674dc55edSMark Brown 89774dc55edSMark Brown case SND_SOC_BIAS_STANDBY: 898ce6120ccSLiam Girdwood if (codec->dapm.bias_level == SND_SOC_BIAS_PREPARE) { 89974dc55edSMark Brown /* VREF off */ 9008d50e447SMark Brown reg = snd_soc_read(codec, WM8961_PWR_MGMT_1); 90174dc55edSMark Brown reg &= ~WM8961_VREF; 9028d50e447SMark Brown snd_soc_write(codec, WM8961_PWR_MGMT_1, reg); 90374dc55edSMark Brown 90474dc55edSMark Brown /* Bias generation off */ 9058d50e447SMark Brown reg = snd_soc_read(codec, WM8961_ANTI_POP); 90674dc55edSMark Brown reg &= ~(WM8961_BUFIOEN | WM8961_BUFDCOPEN); 9078d50e447SMark Brown snd_soc_write(codec, WM8961_ANTI_POP, reg); 90874dc55edSMark Brown 90974dc55edSMark Brown /* VMID off */ 9108d50e447SMark Brown reg = snd_soc_read(codec, WM8961_PWR_MGMT_1); 91174dc55edSMark Brown reg &= ~WM8961_VMIDSEL_MASK; 9128d50e447SMark Brown snd_soc_write(codec, WM8961_PWR_MGMT_1, reg); 91374dc55edSMark Brown } 91474dc55edSMark Brown break; 91574dc55edSMark Brown 91674dc55edSMark Brown case SND_SOC_BIAS_OFF: 91774dc55edSMark Brown break; 91874dc55edSMark Brown } 91974dc55edSMark Brown 920ce6120ccSLiam Girdwood codec->dapm.bias_level = level; 92174dc55edSMark Brown 92274dc55edSMark Brown return 0; 92374dc55edSMark Brown } 92474dc55edSMark Brown 92574dc55edSMark Brown 92674dc55edSMark Brown #define WM8961_RATES SNDRV_PCM_RATE_8000_48000 92774dc55edSMark Brown 92874dc55edSMark Brown #define WM8961_FORMATS \ 92974dc55edSMark Brown (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ 93074dc55edSMark Brown SNDRV_PCM_FMTBIT_S24_LE) 93174dc55edSMark Brown 932*85e7652dSLars-Peter Clausen static const struct snd_soc_dai_ops wm8961_dai_ops = { 93374dc55edSMark Brown .hw_params = wm8961_hw_params, 93474dc55edSMark Brown .set_sysclk = wm8961_set_sysclk, 93574dc55edSMark Brown .set_fmt = wm8961_set_fmt, 93674dc55edSMark Brown .digital_mute = wm8961_digital_mute, 93774dc55edSMark Brown .set_tristate = wm8961_set_tristate, 93874dc55edSMark Brown .set_clkdiv = wm8961_set_clkdiv, 93974dc55edSMark Brown }; 94074dc55edSMark Brown 941f0fba2adSLiam Girdwood static struct snd_soc_dai_driver wm8961_dai = { 942f0fba2adSLiam Girdwood .name = "wm8961-hifi", 94374dc55edSMark Brown .playback = { 94474dc55edSMark Brown .stream_name = "HiFi Playback", 94574dc55edSMark Brown .channels_min = 1, 94674dc55edSMark Brown .channels_max = 2, 94774dc55edSMark Brown .rates = WM8961_RATES, 94874dc55edSMark Brown .formats = WM8961_FORMATS,}, 94974dc55edSMark Brown .capture = { 95074dc55edSMark Brown .stream_name = "HiFi Capture", 95174dc55edSMark Brown .channels_min = 1, 95274dc55edSMark Brown .channels_max = 2, 95374dc55edSMark Brown .rates = WM8961_RATES, 95474dc55edSMark Brown .formats = WM8961_FORMATS,}, 95574dc55edSMark Brown .ops = &wm8961_dai_ops, 95674dc55edSMark Brown }; 95774dc55edSMark Brown 958f0fba2adSLiam Girdwood static int wm8961_probe(struct snd_soc_codec *codec) 95974dc55edSMark Brown { 960ce6120ccSLiam Girdwood struct snd_soc_dapm_context *dapm = &codec->dapm; 96174dc55edSMark Brown int ret = 0; 96274dc55edSMark Brown u16 reg; 96374dc55edSMark Brown 9648d50e447SMark Brown ret = snd_soc_codec_set_cache_io(codec, 8, 16, SND_SOC_I2C); 9658d50e447SMark Brown if (ret != 0) { 9668d50e447SMark Brown dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); 967f0fba2adSLiam Girdwood return ret; 9688d50e447SMark Brown } 9698d50e447SMark Brown 9708d50e447SMark Brown reg = snd_soc_read(codec, WM8961_SOFTWARE_RESET); 97174dc55edSMark Brown if (reg != 0x1801) { 97274dc55edSMark Brown dev_err(codec->dev, "Device is not a WM8961: ID=0x%x\n", reg); 973f0fba2adSLiam Girdwood return -EINVAL; 97474dc55edSMark Brown } 97574dc55edSMark Brown 9768d50e447SMark Brown /* This isn't volatile - readback doesn't correspond to write */ 977370f4645SAxel Lin codec->cache_bypass = 1; 978370f4645SAxel Lin reg = snd_soc_read(codec, WM8961_RIGHT_INPUT_VOLUME); 979370f4645SAxel Lin codec->cache_bypass = 0; 98074dc55edSMark Brown dev_info(codec->dev, "WM8961 family %d revision %c\n", 98174dc55edSMark Brown (reg & WM8961_DEVICE_ID_MASK) >> WM8961_DEVICE_ID_SHIFT, 98274dc55edSMark Brown ((reg & WM8961_CHIP_REV_MASK) >> WM8961_CHIP_REV_SHIFT) 98374dc55edSMark Brown + 'A'); 98474dc55edSMark Brown 98574dc55edSMark Brown ret = wm8961_reset(codec); 98674dc55edSMark Brown if (ret < 0) { 98774dc55edSMark Brown dev_err(codec->dev, "Failed to issue reset\n"); 988f0fba2adSLiam Girdwood return ret; 98974dc55edSMark Brown } 99074dc55edSMark Brown 99174dc55edSMark Brown /* Enable class W */ 9928d50e447SMark Brown reg = snd_soc_read(codec, WM8961_CHARGE_PUMP_B); 99374dc55edSMark Brown reg |= WM8961_CP_DYN_PWR_MASK; 9948d50e447SMark Brown snd_soc_write(codec, WM8961_CHARGE_PUMP_B, reg); 99574dc55edSMark Brown 99674dc55edSMark Brown /* Latch volume update bits (right channel only, we always 99774dc55edSMark Brown * write both out) and default ZC on. */ 9988d50e447SMark Brown reg = snd_soc_read(codec, WM8961_ROUT1_VOLUME); 9998d50e447SMark Brown snd_soc_write(codec, WM8961_ROUT1_VOLUME, 100074dc55edSMark Brown reg | WM8961_LO1ZC | WM8961_OUT1VU); 10018d50e447SMark Brown snd_soc_write(codec, WM8961_LOUT1_VOLUME, reg | WM8961_LO1ZC); 10028d50e447SMark Brown reg = snd_soc_read(codec, WM8961_ROUT2_VOLUME); 10038d50e447SMark Brown snd_soc_write(codec, WM8961_ROUT2_VOLUME, 100474dc55edSMark Brown reg | WM8961_SPKRZC | WM8961_SPKVU); 10058d50e447SMark Brown snd_soc_write(codec, WM8961_LOUT2_VOLUME, reg | WM8961_SPKLZC); 100674dc55edSMark Brown 10078d50e447SMark Brown reg = snd_soc_read(codec, WM8961_RIGHT_ADC_VOLUME); 10088d50e447SMark Brown snd_soc_write(codec, WM8961_RIGHT_ADC_VOLUME, reg | WM8961_ADCVU); 10098d50e447SMark Brown reg = snd_soc_read(codec, WM8961_RIGHT_INPUT_VOLUME); 10108d50e447SMark Brown snd_soc_write(codec, WM8961_RIGHT_INPUT_VOLUME, reg | WM8961_IPVU); 101174dc55edSMark Brown 101274dc55edSMark Brown /* Use soft mute by default */ 10138d50e447SMark Brown reg = snd_soc_read(codec, WM8961_ADC_DAC_CONTROL_2); 101474dc55edSMark Brown reg |= WM8961_DACSMM; 10158d50e447SMark Brown snd_soc_write(codec, WM8961_ADC_DAC_CONTROL_2, reg); 101674dc55edSMark Brown 101774dc55edSMark Brown /* Use automatic clocking mode by default; for now this is all 101874dc55edSMark Brown * we support. 101974dc55edSMark Brown */ 10208d50e447SMark Brown reg = snd_soc_read(codec, WM8961_CLOCKING_3); 102174dc55edSMark Brown reg &= ~WM8961_MANUAL_MODE; 10228d50e447SMark Brown snd_soc_write(codec, WM8961_CLOCKING_3, reg); 102374dc55edSMark Brown 102474dc55edSMark Brown wm8961_set_bias_level(codec, SND_SOC_BIAS_STANDBY); 102574dc55edSMark Brown 1026f0fba2adSLiam Girdwood snd_soc_add_controls(codec, wm8961_snd_controls, 1027f0fba2adSLiam Girdwood ARRAY_SIZE(wm8961_snd_controls)); 1028ce6120ccSLiam Girdwood snd_soc_dapm_new_controls(dapm, wm8961_dapm_widgets, 1029f0fba2adSLiam Girdwood ARRAY_SIZE(wm8961_dapm_widgets)); 1030ce6120ccSLiam Girdwood snd_soc_dapm_add_routes(dapm, audio_paths, ARRAY_SIZE(audio_paths)); 103174dc55edSMark Brown 103274dc55edSMark Brown return 0; 103374dc55edSMark Brown } 103474dc55edSMark Brown 1035f0fba2adSLiam Girdwood static int wm8961_remove(struct snd_soc_codec *codec) 103674dc55edSMark Brown { 1037f0fba2adSLiam Girdwood wm8961_set_bias_level(codec, SND_SOC_BIAS_OFF); 1038f0fba2adSLiam Girdwood return 0; 103974dc55edSMark Brown } 104074dc55edSMark Brown 1041f0fba2adSLiam Girdwood #ifdef CONFIG_PM 1042f0fba2adSLiam Girdwood static int wm8961_suspend(struct snd_soc_codec *codec, pm_message_t state) 1043f0fba2adSLiam Girdwood { 1044f0fba2adSLiam Girdwood wm8961_set_bias_level(codec, SND_SOC_BIAS_OFF); 1045f0fba2adSLiam Girdwood 1046f0fba2adSLiam Girdwood return 0; 1047f0fba2adSLiam Girdwood } 1048f0fba2adSLiam Girdwood 1049f0fba2adSLiam Girdwood static int wm8961_resume(struct snd_soc_codec *codec) 1050f0fba2adSLiam Girdwood { 1051f0fba2adSLiam Girdwood u16 *reg_cache = codec->reg_cache; 1052f0fba2adSLiam Girdwood int i; 1053f0fba2adSLiam Girdwood 1054f0fba2adSLiam Girdwood for (i = 0; i < codec->driver->reg_cache_size; i++) { 1055f0fba2adSLiam Girdwood if (reg_cache[i] == wm8961_reg_defaults[i]) 1056f0fba2adSLiam Girdwood continue; 1057f0fba2adSLiam Girdwood 1058f0fba2adSLiam Girdwood if (i == WM8961_SOFTWARE_RESET) 1059f0fba2adSLiam Girdwood continue; 1060f0fba2adSLiam Girdwood 1061f0fba2adSLiam Girdwood snd_soc_write(codec, i, reg_cache[i]); 1062f0fba2adSLiam Girdwood } 1063f0fba2adSLiam Girdwood 1064f0fba2adSLiam Girdwood wm8961_set_bias_level(codec, SND_SOC_BIAS_STANDBY); 1065f0fba2adSLiam Girdwood 1066f0fba2adSLiam Girdwood return 0; 1067f0fba2adSLiam Girdwood } 1068f0fba2adSLiam Girdwood #else 1069f0fba2adSLiam Girdwood #define wm8961_suspend NULL 1070f0fba2adSLiam Girdwood #define wm8961_resume NULL 1071f0fba2adSLiam Girdwood #endif 1072f0fba2adSLiam Girdwood 1073f0fba2adSLiam Girdwood static struct snd_soc_codec_driver soc_codec_dev_wm8961 = { 1074f0fba2adSLiam Girdwood .probe = wm8961_probe, 1075f0fba2adSLiam Girdwood .remove = wm8961_remove, 1076f0fba2adSLiam Girdwood .suspend = wm8961_suspend, 1077f0fba2adSLiam Girdwood .resume = wm8961_resume, 1078f0fba2adSLiam Girdwood .set_bias_level = wm8961_set_bias_level, 1079e5eec34cSDimitris Papastamos .reg_cache_size = ARRAY_SIZE(wm8961_reg_defaults), 1080f0fba2adSLiam Girdwood .reg_word_size = sizeof(u16), 1081f0fba2adSLiam Girdwood .reg_cache_default = wm8961_reg_defaults, 1082f0fba2adSLiam Girdwood .volatile_register = wm8961_volatile_register, 1083f0fba2adSLiam Girdwood }; 1084f0fba2adSLiam Girdwood 1085f0fba2adSLiam Girdwood #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) 108674dc55edSMark Brown static __devinit int wm8961_i2c_probe(struct i2c_client *i2c, 108774dc55edSMark Brown const struct i2c_device_id *id) 108874dc55edSMark Brown { 108974dc55edSMark Brown struct wm8961_priv *wm8961; 1090f0fba2adSLiam Girdwood int ret; 109174dc55edSMark Brown 109274dc55edSMark Brown wm8961 = kzalloc(sizeof(struct wm8961_priv), GFP_KERNEL); 109374dc55edSMark Brown if (wm8961 == NULL) 109474dc55edSMark Brown return -ENOMEM; 109574dc55edSMark Brown 109674dc55edSMark Brown i2c_set_clientdata(i2c, wm8961); 109774dc55edSMark Brown 1098f0fba2adSLiam Girdwood ret = snd_soc_register_codec(&i2c->dev, 1099f0fba2adSLiam Girdwood &soc_codec_dev_wm8961, &wm8961_dai, 1); 1100f0fba2adSLiam Girdwood if (ret < 0) 1101f0fba2adSLiam Girdwood kfree(wm8961); 1102f0fba2adSLiam Girdwood return ret; 110374dc55edSMark Brown } 110474dc55edSMark Brown 110574dc55edSMark Brown static __devexit int wm8961_i2c_remove(struct i2c_client *client) 110674dc55edSMark Brown { 1107f0fba2adSLiam Girdwood snd_soc_unregister_codec(&client->dev); 1108f0fba2adSLiam Girdwood kfree(i2c_get_clientdata(client)); 110974dc55edSMark Brown return 0; 111074dc55edSMark Brown } 111174dc55edSMark Brown 111274dc55edSMark Brown static const struct i2c_device_id wm8961_i2c_id[] = { 111374dc55edSMark Brown { "wm8961", 0 }, 111474dc55edSMark Brown { } 111574dc55edSMark Brown }; 111674dc55edSMark Brown MODULE_DEVICE_TABLE(i2c, wm8961_i2c_id); 111774dc55edSMark Brown 111874dc55edSMark Brown static struct i2c_driver wm8961_i2c_driver = { 111974dc55edSMark Brown .driver = { 1120f0fba2adSLiam Girdwood .name = "wm8961-codec", 112174dc55edSMark Brown .owner = THIS_MODULE, 112274dc55edSMark Brown }, 112374dc55edSMark Brown .probe = wm8961_i2c_probe, 112474dc55edSMark Brown .remove = __devexit_p(wm8961_i2c_remove), 112574dc55edSMark Brown .id_table = wm8961_i2c_id, 112674dc55edSMark Brown }; 1127f0fba2adSLiam Girdwood #endif 112874dc55edSMark Brown 112974dc55edSMark Brown static int __init wm8961_modinit(void) 113074dc55edSMark Brown { 1131f0fba2adSLiam Girdwood int ret = 0; 1132f0fba2adSLiam Girdwood #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) 113374dc55edSMark Brown ret = i2c_add_driver(&wm8961_i2c_driver); 113474dc55edSMark Brown if (ret != 0) { 1135f0fba2adSLiam Girdwood printk(KERN_ERR "Failed to register wm8961 I2C driver: %d\n", 113674dc55edSMark Brown ret); 113774dc55edSMark Brown } 1138f0fba2adSLiam Girdwood #endif 113974dc55edSMark Brown return ret; 114074dc55edSMark Brown } 114174dc55edSMark Brown module_init(wm8961_modinit); 114274dc55edSMark Brown 114374dc55edSMark Brown static void __exit wm8961_exit(void) 114474dc55edSMark Brown { 1145f0fba2adSLiam Girdwood #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) 114674dc55edSMark Brown i2c_del_driver(&wm8961_i2c_driver); 1147f0fba2adSLiam Girdwood #endif 114874dc55edSMark Brown } 114974dc55edSMark Brown module_exit(wm8961_exit); 115074dc55edSMark Brown 115174dc55edSMark Brown MODULE_DESCRIPTION("ASoC WM8961 driver"); 115274dc55edSMark Brown MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); 115374dc55edSMark Brown MODULE_LICENSE("GPL"); 1154