/* * wm8990.c -- WM8990 ALSA Soc Audio driver * * Copyright 2008 Wolfson Microelectronics PLC. * Author: Liam Girdwood * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "wm8990.h" /* codec private data */ struct wm8990_priv { struct regmap *regmap; unsigned int sysclk; unsigned int pcmclk; }; static bool wm8990_volatile_register(struct device *dev, unsigned int reg) { switch (reg) { case WM8990_RESET: return 1; default: return 0; } } static const struct reg_default wm8990_reg_defaults[] = { { 1, 0x0000 }, /* R1 - Power Management (1) */ { 2, 0x6000 }, /* R2 - Power Management (2) */ { 3, 0x0000 }, /* R3 - Power Management (3) */ { 4, 0x4050 }, /* R4 - Audio Interface (1) */ { 5, 0x4000 }, /* R5 - Audio Interface (2) */ { 6, 0x01C8 }, /* R6 - Clocking (1) */ { 7, 0x0000 }, /* R7 - Clocking (2) */ { 8, 0x0040 }, /* R8 - Audio Interface (3) */ { 9, 0x0040 }, /* R9 - Audio Interface (4) */ { 10, 0x0004 }, /* R10 - DAC CTRL */ { 11, 0x00C0 }, /* R11 - Left DAC Digital Volume */ { 12, 0x00C0 }, /* R12 - Right DAC Digital Volume */ { 13, 0x0000 }, /* R13 - Digital Side Tone */ { 14, 0x0100 }, /* R14 - ADC CTRL */ { 15, 0x00C0 }, /* R15 - Left ADC Digital Volume */ { 16, 0x00C0 }, /* R16 - Right ADC Digital Volume */ { 18, 0x0000 }, /* R18 - GPIO CTRL 1 */ { 19, 0x1000 }, /* R19 - GPIO1 & GPIO2 */ { 20, 0x1010 }, /* R20 - GPIO3 & GPIO4 */ { 21, 0x1010 }, /* R21 - GPIO5 & GPIO6 */ { 22, 0x8000 }, /* R22 - GPIOCTRL 2 */ { 23, 0x0800 }, /* R23 - GPIO_POL */ { 24, 0x008B }, /* R24 - Left Line Input 1&2 Volume */ { 25, 0x008B }, /* R25 - Left Line Input 3&4 Volume */ { 26, 0x008B }, /* R26 - Right Line Input 1&2 Volume */ { 27, 0x008B }, /* R27 - Right Line Input 3&4 Volume */ { 28, 0x0000 }, /* R28 - Left Output Volume */ { 29, 0x0000 }, /* R29 - Right Output Volume */ { 30, 0x0066 }, /* R30 - Line Outputs Volume */ { 31, 0x0022 }, /* R31 - Out3/4 Volume */ { 32, 0x0079 }, /* R32 - Left OPGA Volume */ { 33, 0x0079 }, /* R33 - Right OPGA Volume */ { 34, 0x0003 }, /* R34 - Speaker Volume */ { 35, 0x0003 }, /* R35 - ClassD1 */ { 37, 0x0100 }, /* R37 - ClassD3 */ { 38, 0x0079 }, /* R38 - ClassD4 */ { 39, 0x0000 }, /* R39 - Input Mixer1 */ { 40, 0x0000 }, /* R40 - Input Mixer2 */ { 41, 0x0000 }, /* R41 - Input Mixer3 */ { 42, 0x0000 }, /* R42 - Input Mixer4 */ { 43, 0x0000 }, /* R43 - Input Mixer5 */ { 44, 0x0000 }, /* R44 - Input Mixer6 */ { 45, 0x0000 }, /* R45 - Output Mixer1 */ { 46, 0x0000 }, /* R46 - Output Mixer2 */ { 47, 0x0000 }, /* R47 - Output Mixer3 */ { 48, 0x0000 }, /* R48 - Output Mixer4 */ { 49, 0x0000 }, /* R49 - Output Mixer5 */ { 50, 0x0000 }, /* R50 - Output Mixer6 */ { 51, 0x0180 }, /* R51 - Out3/4 Mixer */ { 52, 0x0000 }, /* R52 - Line Mixer1 */ { 53, 0x0000 }, /* R53 - Line Mixer2 */ { 54, 0x0000 }, /* R54 - Speaker Mixer */ { 55, 0x0000 }, /* R55 - Additional Control */ { 56, 0x0000 }, /* R56 - AntiPOP1 */ { 57, 0x0000 }, /* R57 - AntiPOP2 */ { 58, 0x0000 }, /* R58 - MICBIAS */ { 60, 0x0008 }, /* R60 - PLL1 */ { 61, 0x0031 }, /* R61 - PLL2 */ { 62, 0x0026 }, /* R62 - PLL3 */ }; #define wm8990_reset(c) snd_soc_write(c, WM8990_RESET, 0) static const DECLARE_TLV_DB_SCALE(rec_mix_tlv, -1500, 600, 0); static const DECLARE_TLV_DB_SCALE(in_pga_tlv, -1650, 3000, 0); static const DECLARE_TLV_DB_SCALE(out_mix_tlv, 0, -2100, 0); static const DECLARE_TLV_DB_SCALE(out_pga_tlv, -7300, 600, 0); static const DECLARE_TLV_DB_SCALE(out_omix_tlv, -600, 0, 0); static const DECLARE_TLV_DB_SCALE(out_dac_tlv, -7163, 0, 0); static const DECLARE_TLV_DB_SCALE(in_adc_tlv, -7163, 1763, 0); static const DECLARE_TLV_DB_SCALE(out_sidetone_tlv, -3600, 0, 0); static int wm899x_outpga_put_volsw_vu(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; int reg = mc->reg; int ret; u16 val; ret = snd_soc_put_volsw(kcontrol, ucontrol); if (ret < 0) return ret; /* now hit the volume update bits (always bit 8) */ val = snd_soc_read(codec, reg); return snd_soc_write(codec, reg, val | 0x0100); } #define SOC_WM899X_OUTPGA_SINGLE_R_TLV(xname, reg, shift, max, invert,\ tlv_array) \ SOC_SINGLE_EXT_TLV(xname, reg, shift, max, invert, \ snd_soc_get_volsw, wm899x_outpga_put_volsw_vu, tlv_array) static const char *wm8990_digital_sidetone[] = {"None", "Left ADC", "Right ADC", "Reserved"}; static SOC_ENUM_SINGLE_DECL(wm8990_left_digital_sidetone_enum, WM8990_DIGITAL_SIDE_TONE, WM8990_ADC_TO_DACL_SHIFT, wm8990_digital_sidetone); static SOC_ENUM_SINGLE_DECL(wm8990_right_digital_sidetone_enum, WM8990_DIGITAL_SIDE_TONE, WM8990_ADC_TO_DACR_SHIFT, wm8990_digital_sidetone); static const char *wm8990_adcmode[] = {"Hi-fi mode", "Voice mode 1", "Voice mode 2", "Voice mode 3"}; static SOC_ENUM_SINGLE_DECL(wm8990_right_adcmode_enum, WM8990_ADC_CTRL, WM8990_ADC_HPF_CUT_SHIFT, wm8990_adcmode); static const struct snd_kcontrol_new wm8990_snd_controls[] = { /* INMIXL */ SOC_SINGLE("LIN12 PGA Boost", WM8990_INPUT_MIXER3, WM8990_L12MNBST_BIT, 1, 0), SOC_SINGLE("LIN34 PGA Boost", WM8990_INPUT_MIXER3, WM8990_L34MNBST_BIT, 1, 0), /* INMIXR */ SOC_SINGLE("RIN12 PGA Boost", WM8990_INPUT_MIXER3, WM8990_R12MNBST_BIT, 1, 0), SOC_SINGLE("RIN34 PGA Boost", WM8990_INPUT_MIXER3, WM8990_R34MNBST_BIT, 1, 0), /* LOMIX */ SOC_SINGLE_TLV("LOMIX LIN3 Bypass Volume", WM8990_OUTPUT_MIXER3, WM8990_LLI3LOVOL_SHIFT, WM8990_LLI3LOVOL_MASK, 1, out_mix_tlv), SOC_SINGLE_TLV("LOMIX RIN12 PGA Bypass Volume", WM8990_OUTPUT_MIXER3, WM8990_LR12LOVOL_SHIFT, WM8990_LR12LOVOL_MASK, 1, out_mix_tlv), SOC_SINGLE_TLV("LOMIX LIN12 PGA Bypass Volume", WM8990_OUTPUT_MIXER3, WM8990_LL12LOVOL_SHIFT, WM8990_LL12LOVOL_MASK, 1, out_mix_tlv), SOC_SINGLE_TLV("LOMIX RIN3 Bypass Volume", WM8990_OUTPUT_MIXER5, WM8990_LRI3LOVOL_SHIFT, WM8990_LRI3LOVOL_MASK, 1, out_mix_tlv), SOC_SINGLE_TLV("LOMIX AINRMUX Bypass Volume", WM8990_OUTPUT_MIXER5, WM8990_LRBLOVOL_SHIFT, WM8990_LRBLOVOL_MASK, 1, out_mix_tlv), SOC_SINGLE_TLV("LOMIX AINLMUX Bypass Volume", WM8990_OUTPUT_MIXER5, WM8990_LRBLOVOL_SHIFT, WM8990_LRBLOVOL_MASK, 1, out_mix_tlv), /* ROMIX */ SOC_SINGLE_TLV("ROMIX RIN3 Bypass Volume", WM8990_OUTPUT_MIXER4, WM8990_RRI3ROVOL_SHIFT, WM8990_RRI3ROVOL_MASK, 1, out_mix_tlv), SOC_SINGLE_TLV("ROMIX LIN12 PGA Bypass Volume", WM8990_OUTPUT_MIXER4, WM8990_RL12ROVOL_SHIFT, WM8990_RL12ROVOL_MASK, 1, out_mix_tlv), SOC_SINGLE_TLV("ROMIX RIN12 PGA Bypass Volume", WM8990_OUTPUT_MIXER4, WM8990_RR12ROVOL_SHIFT, WM8990_RR12ROVOL_MASK, 1, out_mix_tlv), SOC_SINGLE_TLV("ROMIX LIN3 Bypass Volume", WM8990_OUTPUT_MIXER6, WM8990_RLI3ROVOL_SHIFT, WM8990_RLI3ROVOL_MASK, 1, out_mix_tlv), SOC_SINGLE_TLV("ROMIX AINLMUX Bypass Volume", WM8990_OUTPUT_MIXER6, WM8990_RLBROVOL_SHIFT, WM8990_RLBROVOL_MASK, 1, out_mix_tlv), SOC_SINGLE_TLV("ROMIX AINRMUX Bypass Volume", WM8990_OUTPUT_MIXER6, WM8990_RRBROVOL_SHIFT, WM8990_RRBROVOL_MASK, 1, out_mix_tlv), /* LOUT */ SOC_WM899X_OUTPGA_SINGLE_R_TLV("LOUT Volume", WM8990_LEFT_OUTPUT_VOLUME, WM8990_LOUTVOL_SHIFT, WM8990_LOUTVOL_MASK, 0, out_pga_tlv), SOC_SINGLE("LOUT ZC", WM8990_LEFT_OUTPUT_VOLUME, WM8990_LOZC_BIT, 1, 0), /* ROUT */ SOC_WM899X_OUTPGA_SINGLE_R_TLV("ROUT Volume", WM8990_RIGHT_OUTPUT_VOLUME, WM8990_ROUTVOL_SHIFT, WM8990_ROUTVOL_MASK, 0, out_pga_tlv), SOC_SINGLE("ROUT ZC", WM8990_RIGHT_OUTPUT_VOLUME, WM8990_ROZC_BIT, 1, 0), /* LOPGA */ SOC_WM899X_OUTPGA_SINGLE_R_TLV("LOPGA Volume", WM8990_LEFT_OPGA_VOLUME, WM8990_LOPGAVOL_SHIFT, WM8990_LOPGAVOL_MASK, 0, out_pga_tlv), SOC_SINGLE("LOPGA ZC Switch", WM8990_LEFT_OPGA_VOLUME, WM8990_LOPGAZC_BIT, 1, 0), /* ROPGA */ SOC_WM899X_OUTPGA_SINGLE_R_TLV("ROPGA Volume", WM8990_RIGHT_OPGA_VOLUME, WM8990_ROPGAVOL_SHIFT, WM8990_ROPGAVOL_MASK, 0, out_pga_tlv), SOC_SINGLE("ROPGA ZC Switch", WM8990_RIGHT_OPGA_VOLUME, WM8990_ROPGAZC_BIT, 1, 0), SOC_SINGLE("LON Mute Switch", WM8990_LINE_OUTPUTS_VOLUME, WM8990_LONMUTE_BIT, 1, 0), SOC_SINGLE("LOP Mute Switch", WM8990_LINE_OUTPUTS_VOLUME, WM8990_LOPMUTE_BIT, 1, 0), SOC_SINGLE("LOP Attenuatio
Kernel driver f71805f
=====================

Supported chips:
  * Fintek F71805F/FG
    Prefix: 'f71805f'
    Addresses scanned: none, address read from Super I/O config space
    Datasheet: Available from the Fintek website
  * Fintek F71806F/FG
    Prefix: 'f71872f'
    Addresses scanned: none, address read from Super I/O config space
    Datasheet: Available from the Fintek website
  * Fintek F71872F/FG
    Prefix: 'f71872f'
    Addresses scanned: none, address read from Super I/O config space
    Datasheet: Available from the Fintek website

Author: Jean Delvare <jdelvare@suse.de>

Thanks to Denis Kieft from Barracuda Networks for the donation of a
test system (custom Jetway K8M8MS motherboard, with CPU and RAM) and
for providing initial documentation.

Thanks to Kris Chen and Aaron Huang from Fintek for answering technical
questions and providing additional documentation.

Thanks to Chris Lin from Jetway for providing wiring schematics and
answering technical questions.


Description
-----------

The Fintek F71805F/FG Super I/O chip includes complete hardware monitoring
capabilities. It can monitor up to 9 voltages (counting its own power
source), 3 fans and 3 temperature sensors.

This chip also has fan controlling features, using either DC or PWM, in
three different modes (one manual, two automatic).

The Fintek F71872F/FG Super I/O chip is almost the same, with two
additional internal voltages monitored (VSB and battery). It also features
6 VID inputs. The VID inputs are not yet supported by this driver.

The Fintek F71806F/FG Super-I/O chip is essentially the same as the
F71872F/FG, and is undistinguishable therefrom.

The driver assumes that no more than one chip is present, which seems
reasonable.


Voltage Monitoring
------------------

Voltages are sampled by an 8-bit ADC with a LSB of 8 mV. The supported
range is thus from 0 to 2.040 V. Voltage values outside of this range
need external resistors. An exception is in0, which is used to monitor
the chip's own power source (+3.3V), and is divided internally by a
factor 2. For the F71872F/FG, in9 (VSB) and in10 (battery) are also
divided internally by a factor 2.

The two LSB of the voltage limit registers are not used (always 0), so
you can only set the limits in steps of 32 mV (before scaling).

The wirings and resistor values suggested by Fintek are as follow:

        pin                                           expected
        name    use           R1      R2     divider  raw val.

in0     VCC     VCC3.3V     int.    int.        2.00    1.65 V
in1     VIN1    VTT1.2V      10K       -        1.00    1.20 V
in2     VIN2    VRAM        100K    100K        2.00   ~1.25 V (1)
in3     VIN3    VCHIPSET     47K    100K        1.47    2.24 V (2)
in4     VIN4    VCC5V       200K     47K        5.25    0.95 V
in5     VIN5    +12V        200K     20K       11.00    1.05 V
in6     VIN6    VCC1.5V      10K       -        1.00    1.50 V
in7     VIN7    VCORE        10K       -        1.00   ~1.40 V (1)
in8     VIN8    VSB5V       200K     47K        1.00    0.95 V
in10    VSB     VSB3.3V     int.    int.        2.00    1.65 V (3)
in9     VBAT    VBATTERY    int.    int.        2.00    1.50 V (3)

(1) Depends on your hardware setup.
(2) Obviously not correct, swapping R1 and R2 would make more sense.
(3) F71872F/FG only.

These values can be used as hints at best, as motherboard manufacturers
are free to use a completely different setup. As a matter of fact, the
Jetway K8M8MS uses a significantly different setup. You will have to
find out documentation about your own motherboard, and edit sensors.conf
accordingly.

Each voltage measured has associated low and high limits, each of which
triggers an alarm when crossed.


Fan Monitoring
--------------

Fan rotation speeds are reported as 12-bit values from a gated clock
signal. Speeds down to 366 RPM can be measured. There is no theoretical
high limit, but values over 6000 RPM seem to cause problem. The effective
resolution is much lower than you would expect, the step between different
register values being 10 rather than 1.

The chip assumes 2 pulse-per-revolution fans.

An alarm is triggered if the rotation speed drops below a programmable
limit or is too low to be measured.


Temperature Monitoring
----------------------

Temperatures are reported in degrees Celsius. Each temperature measured
has a high limit, those crossing triggers an alarm. There is an associated
hysteresis value, below which the temperature has to drop before the
alarm is cleared.

All temperature channels are external, there is no embedded temperature
sensor. Each channel can be used for connecting either a thermal diode
or a thermistor. The driver reports the currently selected mode, but
doesn't allow changing it. In theory, the BIOS should have configured
everything properly.


Fan Control
-----------

Both PWM (pulse-width modulation) and DC fan speed control methods are
supported. The right one to use depends on external circuitry on the
motherboard, so the driver assumes that the BIOS set the method
properly. The driver will report the method, but won't let you change
it.

When the PWM method is used, you can select the operating frequency,
from 187.5 kHz (default) to 31 Hz. The best frequency depends on the
fan model. As a rule of thumb, lower frequencies seem to give better
control, but may generate annoying high-pitch noise. So a frequency just
above the audible range, such as 25 kHz, may be a good choice; if this
doesn't give you good linear control, try reducing it. Fintek recommends
not going below 1 kHz, as the fan tachometers get confused by lower
frequencies as well.

When the DC method is used, Fintek recommends not going below 5 V, which
corresponds to a pwm value of 106 for the driver. The driver doesn't
enforce this limit though.

Three different fan control modes are supported; the mode number is written
to the pwm<n>_enable file.

* 1: Manual mode
  You ask for a specific PWM duty cycle or DC voltage by writing to the
  pwm<n> file.

* 2: Temperature mode
  You define 3 temperature/fan speed trip points using the
  pwm<n>_auto_point<m>_temp and _fan files. These define a staircase
  relationship between temperature and fan speed with two additional points
  interpolated between the values that you define. When the temperature
  is below auto_point1_temp the fan is switched off.

* 3: Fan speed mode
  You ask for a specific fan speed by writing to the fan<n>_target file.

Both of the automatic modes require that pwm1 corresponds to fan1, pwm2 to
fan2 and pwm3 to fan3. Temperature mode also requires that temp1 corresponds
to pwm1 and fan1, etc.
, SND_SOC_DAPM_INPUT("RIN1"), SND_SOC_DAPM_INPUT("RIN2"), SND_SOC_DAPM_INPUT("Internal ADC Source"), SND_SOC_DAPM_SUPPLY("INL", WM8990_POWER_MANAGEMENT_2, WM8990_AINL_ENA_BIT, 0, NULL, 0), SND_SOC_DAPM_SUPPLY("INR", WM8990_POWER_MANAGEMENT_2, WM8990_AINR_ENA_BIT, 0, NULL, 0), /* DACs */ SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8990_POWER_MANAGEMENT_2, WM8990_ADCL_ENA_BIT, 0), SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8990_POWER_MANAGEMENT_2, WM8990_ADCR_ENA_BIT, 0), /* Input PGAs */ SND_SOC_DAPM_MIXER("LIN12 PGA", WM8990_POWER_MANAGEMENT_2, WM8990_LIN12_ENA_BIT, 0, &wm8990_dapm_lin12_pga_controls[0], ARRAY_SIZE(wm8990_dapm_lin12_pga_controls)), SND_SOC_DAPM_MIXER("LIN34 PGA", WM8990_POWER_MANAGEMENT_2, WM8990_LIN34_ENA_BIT, 0, &wm8990_dapm_lin34_pga_controls[0], ARRAY_SIZE(wm8990_dapm_lin34_pga_controls)), SND_SOC_DAPM_MIXER("RIN12 PGA", WM8990_POWER_MANAGEMENT_2, WM8990_RIN12_ENA_BIT, 0, &wm8990_dapm_rin12_pga_controls[0], ARRAY_SIZE(wm8990_dapm_rin12_pga_controls)), SND_SOC_DAPM_MIXER("RIN34 PGA", WM8990_POWER_MANAGEMENT_2, WM8990_RIN34_ENA_BIT, 0, &wm8990_dapm_rin34_pga_controls[0], ARRAY_SIZE(wm8990_dapm_rin34_pga_controls)), /* INMIXL */ SND_SOC_DAPM_MIXER("INMIXL", SND_SOC_NOPM, 0, 0, &wm8990_dapm_inmixl_controls[0], ARRAY_SIZE(wm8990_dapm_inmixl_controls)), /* AINLMUX */ SND_SOC_DAPM_MUX("AINLMUX", SND_SOC_NOPM, 0, 0, &wm8990_dapm_ainlmux_controls), /* INMIXR */ SND_SOC_DAPM_MIXER("INMIXR", SND_SOC_NOPM, 0, 0, &wm8990_dapm_inmixr_controls[0], ARRAY_SIZE(wm8990_dapm_inmixr_controls)), /* AINRMUX */ SND_SOC_DAPM_MUX("AINRMUX", SND_SOC_NOPM, 0, 0, &wm8990_dapm_ainrmux_controls), /* Output Side */ /* DACs */ SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8990_POWER_MANAGEMENT_3, WM8990_DACL_ENA_BIT, 0), SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8990_POWER_MANAGEMENT_3, WM8990_DACR_ENA_BIT, 0), /* LOMIX */ SND_SOC_DAPM_MIXER_E("LOMIX", WM8990_POWER_MANAGEMENT_3, WM8990_LOMIX_ENA_BIT, 0, &wm8990_dapm_lomix_controls[0], ARRAY_SIZE(wm8990_dapm_lomix_controls), outmixer_event, SND_SOC_DAPM_PRE_REG), /* LONMIX */ SND_SOC_DAPM_MIXER("LONMIX", WM8990_POWER_MANAGEMENT_3, WM8990_LON_ENA_BIT, 0, &wm8990_dapm_lonmix_controls[0], ARRAY_SIZE(wm8990_dapm_lonmix_controls)), /* LOPMIX */ SND_SOC_DAPM_MIXER("LOPMIX", WM8990_POWER_MANAGEMENT_3, WM8990_LOP_ENA_BIT, 0, &wm8990_dapm_lopmix_controls[0], ARRAY_SIZE(wm8990_dapm_lopmix_controls)), /* OUT3MIX */ SND_SOC_DAPM_MIXER("OUT3MIX", WM8990_POWER_MANAGEMENT_1, WM8990_OUT3_ENA_BIT, 0, &wm8990_dapm_out3mix_controls[0], ARRAY_SIZE(wm8990_dapm_out3mix_controls)), /* SPKMIX */ SND_SOC_DAPM_MIXER_E("SPKMIX", WM8990_POWER_MANAGEMENT_1, WM8990_SPK_ENA_BIT, 0, &wm8990_dapm_spkmix_controls[0], ARRAY_SIZE(wm8990_dapm_spkmix_controls), outmixer_event, SND_SOC_DAPM_PRE_REG), /* OUT4MIX */ SND_SOC_DAPM_MIXER("OUT4MIX", WM8990_POWER_MANAGEMENT_1, WM8990_OUT4_ENA_BIT, 0, &wm8990_dapm_out4mix_controls[0], ARRAY_SIZE(wm8990_dapm_out4mix_controls)), /* ROPMIX */ SND_SOC_DAPM_MIXER("ROPMIX", WM8990_POWER_MANAGEMENT_3, WM8990_ROP_ENA_BIT, 0, &wm8990_dapm_ropmix_controls[0], ARRAY_SIZE(wm8990_dapm_ropmix_controls)), /* RONMIX */ SND_SOC_DAPM_MIXER("RONMIX", WM8990_POWER_MANAGEMENT_3, WM8990_RON_ENA_BIT, 0, &wm8990_dapm_ronmix_controls[0], ARRAY_SIZE(wm8990_dapm_ronmix_controls)), /* ROMIX */ SND_SOC_DAPM_MIXER_E("ROMIX", WM8990_POWER_MANAGEMENT_3, WM8990_ROMIX_ENA_BIT, 0, &wm8990_dapm_romix_controls[0], ARRAY_SIZE(wm8990_dapm_romix_controls), outmixer_event, SND_SOC_DAPM_PRE_REG), /* LOUT PGA */ SND_SOC_DAPM_PGA("LOUT PGA", WM8990_POWER_MANAGEMENT_1, WM8990_LOUT_ENA_BIT, 0, NULL, 0), /* ROUT PGA */ SND_SOC_DAPM_PGA("ROUT PGA", WM8990_POWER_MANAGEMENT_1, WM8990_ROUT_ENA_BIT, 0, NULL, 0), /* LOPGA */ SND_SOC_DAPM_PGA("LOPGA", WM8990_POWER_MANAGEMENT_3, WM8990_LOPGA_ENA_BIT, 0, NULL, 0), /* ROPGA */ SND_SOC_DAPM_PGA("ROPGA", WM8990_POWER_MANAGEMENT_3, WM8990_ROPGA_ENA_BIT, 0, NULL, 0), /* MICBIAS */ SND_SOC_DAPM_SUPPLY("MICBIAS", WM8990_POWER_MANAGEMENT_1, WM8990_MICBIAS_ENA_BIT, 0, NULL, 0), SND_SOC_DAPM_OUTPUT("LON"), SND_SOC_DAPM_OUTPUT("LOP"), SND_SOC_DAPM_OUTPUT("OUT3"), SND_SOC_DAPM_OUTPUT("LOUT"), SND_SOC_DAPM_OUTPUT("SPKN"), SND_SOC_DAPM_OUTPUT("SPKP"), SND_SOC_DAPM_OUTPUT("ROUT"), SND_SOC_DAPM_OUTPUT("OUT4"), SND_SOC_DAPM_OUTPUT("ROP"), SND_SOC_DAPM_OUTPUT("RON"), SND_SOC_DAPM_OUTPUT("Internal DAC Sink"), }; static const struct snd_soc_dapm_route wm8990_dapm_routes[] = { /* Make DACs turn on when playing even if not mixed into any outputs */ {"Internal DAC Sink", NULL, "Left DAC"}, {"Internal DAC Sink", NULL, "Right DAC"}, /* Make ADCs turn on when recording even if not mixed from any inputs */ {"Left ADC", NULL, "Internal ADC Source"}, {"Right ADC", NULL, "Internal ADC Source"}, {"AINLMUX", NULL, "INL"}, {"INMIXL", NULL, "INL"}, {"AINRMUX", NULL, "INR"}, {"INMIXR", NULL, "INR"}, /* Input Side */ /* LIN12 PGA */ {"LIN12 PGA", "LIN1 Switch", "LIN1"}, {"LIN12 PGA", "LIN2 Switch", "LIN2"}, /* LIN34 PGA */ {"LIN34 PGA", "LIN3 Switch", "LIN3"}, {"LIN34 PGA", "LIN4 Switch", "LIN4/RXN"}, /* INMIXL */ {"INMIXL", "Record Left Volume", "LOMIX"}, {"INMIXL", "LIN2 Volume", "LIN2"}, {"INMIXL", "LINPGA12 Switch", "LIN12 PGA"}, {"INMIXL", "LINPGA34 Switch", "LIN34 PGA"}, /* AINLMUX */ {"AINLMUX", "INMIXL Mix", "INMIXL"}, {"AINLMUX", "DIFFINL Mix", "LIN12 PGA"}, {"AINLMUX", "DIFFINL Mix", "LIN34 PGA"}, {"AINLMUX", "RXVOICE Mix", "LIN4/RXN"}, {"AINLMUX", "RXVOICE Mix", "RIN4/RXP"}, /* ADC */ {"Left ADC", NULL, "AINLMUX"}, /* RIN12 PGA */ {"RIN12 PGA", "RIN1 Switch", "RIN1"}, {"RIN12 PGA", "RIN2 Switch", "RIN2"}, /* RIN34 PGA */ {"RIN34 PGA", "RIN3 Switch", "RIN3"}, {"RIN34 PGA", "RIN4 Switch", "RIN4/RXP"}, /* INMIXL */ {"INMIXR", "Record Right Volume", "ROMIX"}, {"INMIXR", "RIN2 Volume", "RIN2"}, {"INMIXR", "RINPGA12 Switch", "RIN12 PGA"}, {"INMIXR", "RINPGA34 Switch", "RIN34 PGA"}, /* AINRMUX */ {"AINRMUX", "INMIXR Mix", "INMIXR"}, {"AINRMUX", "DIFFINR Mix", "RIN12 PGA"}, {"AINRMUX", "DIFFINR Mix", "RIN34 PGA"}, {"AINRMUX", "RXVOICE Mix", "LIN4/RXN"}, {"AINRMUX", "RXVOICE Mix", "RIN4/RXP"}, /* ADC */ {"Right ADC", NULL, "AINRMUX"}, /* LOMIX */ {"LOMIX", "LOMIX RIN3 Bypass Switch", "RIN3"}, {"LOMIX", "LOMIX LIN3 Bypass Switch", "LIN3"}, {"LOMIX", "LOMIX LIN12 PGA Bypass Switch", "LIN12 PGA"}, {"LOMIX", "LOMIX RIN12 PGA Bypass Switch", "RIN12 PGA"}, {"LOMIX", "LOMIX Right ADC Bypass Switch", "AINRMUX"}, {"LOMIX", "LOMIX Left ADC Bypass Switch", "AINLMUX"}, {"LOMIX", "LOMIX Left DAC Switch", "Left DAC"}, /* ROMIX */ {"ROMIX", "ROMIX RIN3 Bypass Switch", "RIN3"}, {"ROMIX", "ROMIX LIN3 Bypass Switch", "LIN3"}, {"ROMIX", "ROMIX LIN12 PGA Bypass Switch", "LIN12 PGA"}, {"ROMIX", "ROMIX RIN12 PGA Bypass Switch", "RIN12 PGA"}, {"ROMIX", "ROMIX Right ADC Bypass Switch", "AINRMUX"}, {"ROMIX", "ROMIX Left ADC Bypass Switch", "AINLMUX"}, {"ROMIX", "ROMIX Right DAC Switch", "Right DAC"}, /* SPKMIX */ {"SPKMIX", "SPKMIX LIN2 Bypass Switch", "LIN2"}, {"SPKMIX", "SPKMIX RIN2 Bypass Switch", "RIN2"}, {"SPKMIX", "SPKMIX LADC Bypass Switch", "AINLMUX"}, {"SPKMIX", "SPKMIX RADC Bypass Switch", "AINRMUX"}, {"SPKMIX", "SPKMIX Left Mixer PGA Switch", "LOPGA"}, {"SPKMIX", "SPKMIX Right Mixer PGA Switch", "ROPGA"}, {"SPKMIX", "SPKMIX Right DAC Switch", "Right DAC"}, {"SPKMIX", "SPKMIX Left DAC Switch", "Left DAC"}, /* LONMIX */ {"LONMIX", "LONMIX Left Mixer PGA Switch", "LOPGA"}, {"LONMIX", "LONMIX Right Mixer PGA Switch", "ROPGA"}, {"LONMIX", "LONMIX Inverted LOP Switch", "LOPMIX"}, /* LOPMIX */ {"LOPMIX", "LOPMIX Right Mic Bypass Switch", "RIN12 PGA"}, {"LOPMIX", "LOPMIX Left Mic Bypass Switch", "LIN12 PGA"}, {"LOPMIX", "LOPMIX Left Mixer PGA Switch", "LOPGA"}, /* OUT3MIX */ {"OUT3MIX", "OUT3MIX LIN4/RXP Bypass Switch", "LIN4/RXN"}, {"OUT3MIX", "OUT3MIX Left Out PGA Switch", "LOPGA"}, /* OUT4MIX */ {"OUT4MIX", "OUT4MIX Right Out PGA Switch", "ROPGA"}, {"OUT4MIX", "OUT4MIX RIN4/RXP Bypass Switch", "RIN4/RXP"}, /* RONMIX */ {"RONMIX", "RONMIX Right Mixer PGA Switch", "ROPGA"}, {"RONMIX", "RONMIX Left Mixer PGA Switch", "LOPGA"}, {"RONMIX", "RONMIX Inverted ROP Switch", "ROPMIX"}, /* ROPMIX */ {"ROPMIX", "ROPMIX Left Mic Bypass Switch", "LIN12 PGA"}, {"ROPMIX", "ROPMIX Right Mic Bypass Switch", "RIN12 PGA"}, {"ROPMIX", "ROPMIX Right Mixer PGA Switch", "ROPGA"}, /* Out Mixer PGAs */ {"LOPGA", NULL, "LOMIX"}, {"ROPGA", NULL, "ROMIX"}, {"LOUT PGA", NULL, "LOMIX"}, {"ROUT PGA", NULL, "ROMIX"}, /* Output Pins */ {"LON", NULL, "LONMIX"}, {"LOP", NULL, "LOPMIX"}, {"OUT3", NULL, "OUT3MIX"}, {"LOUT", NULL, "LOUT PGA"}, {"SPKN", NULL, "SPKMIX"}, {"ROUT", NULL, "ROUT PGA"}, {"OUT4", NULL, "OUT4MIX"}, {"ROP", NULL, "ROPMIX"}, {"RON", NULL, "RONMIX"}, }; /* PLL divisors */ struct _pll_div { u32 div2; u32 n; u32 k; }; /* The size in bits of the pll divide multiplied by 10 * to allow rounding later */ #define FIXED_PLL_SIZE ((1 << 16) * 10) static void pll_factors(struct _pll_div *pll_div, unsigned int target, unsigned int source) { u64 Kpart; unsigned int K, Ndiv, Nmod; Ndiv = target / source; if (Ndiv < 6) { source >>= 1; pll_div->div2 = 1; Ndiv = target / source; } else pll_div->div2 = 0; if ((Ndiv < 6) || (Ndiv > 12)) printk(KERN_WARNING "WM8990 N value outwith recommended range! N = %u\n", Ndiv); pll_div->n = Ndiv; Nmod = target % source; Kpart = FIXED_PLL_SIZE * (long long)Nmod; do_div(Kpart, source); K = Kpart & 0xFFFFFFFF; /* Check if we need to round */ if ((K % 10) >= 5) K += 5; /* Move down to proper range now rounding is done */ K /= 10; pll_div->k = K; } static int wm8990_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, int source, unsigned int freq_in, unsigned int freq_out) { struct snd_soc_codec *codec = codec_dai->codec; struct _pll_div pll_div; if (freq_in && freq_out) { pll_factors(&pll_div, freq_out * 4, freq_in); /* Turn on PLL */ snd_soc_update_bits(codec, WM8990_POWER_MANAGEMENT_2, WM8990_PLL_ENA, WM8990_PLL_ENA); /* sysclk comes from PLL */ snd_soc_update_bits(codec, WM8990_CLOCKING_2, WM8990_SYSCLK_SRC, WM8990_SYSCLK_SRC); /* set up N , fractional mode and pre-divisor if necessary */ snd_soc_write(codec, WM8990_PLL1, pll_div.n | WM8990_SDM | (pll_div.div2?WM8990_PRESCALE:0)); snd_soc_write(codec, WM8990_PLL2, (u8)(pll_div.k>>8)); snd_soc_write(codec, WM8990_PLL3, (u8)(pll_div.k & 0xFF)); } else { /* Turn off PLL */ snd_soc_update_bits(codec, WM8990_POWER_MANAGEMENT_2, WM8990_PLL_ENA, 0); } return 0; } /* * Clock after PLL and dividers */ static int wm8990_set_dai_sysclk(struct snd_soc_dai *codec_dai, int clk_id, unsigned int freq, int dir) { struct snd_soc_codec *codec = codec_dai->codec; struct wm8990_priv *wm8990 = snd_soc_codec_get_drvdata(codec); wm8990->sysclk = freq; return 0; } /* * Set's ADC and Voice DAC format. */ static int wm8990_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec; u16 audio1, audio3; audio1 = snd_soc_read(codec, WM8990_AUDIO_INTERFACE_1); audio3 = snd_soc_read(codec, WM8990_AUDIO_INTERFACE_3); /* set master/slave audio interface */ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { case SND_SOC_DAIFMT_CBS_CFS: audio3 &= ~WM8990_AIF_MSTR1; break; case SND_SOC_DAIFMT_CBM_CFM: audio3 |= WM8990_AIF_MSTR1; break; default: return -EINVAL; } audio1 &= ~WM8990_AIF_FMT_MASK; /* interface format */ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_I2S: audio1 |= WM8990_AIF_TMF_I2S; audio1 &= ~WM8990_AIF_LRCLK_INV; break; case SND_SOC_DAIFMT_RIGHT_J: audio1 |= WM8990_AIF_TMF_RIGHTJ; audio1 &= ~WM8990_AIF_LRCLK_INV; break; case SND_SOC_DAIFMT_LEFT_J: audio1 |= WM8990_AIF_TMF_LEFTJ; audio1 &= ~WM8990_AIF_LRCLK_INV; break; case SND_SOC_DAIFMT_DSP_A: audio1 |= WM8990_AIF_TMF_DSP; audio1 &= ~WM8990_AIF_LRCLK_INV; break; case SND_SOC_DAIFMT_DSP_B: audio1 |= WM8990_AIF_TMF_DSP | WM8990_AIF_LRCLK_INV; break; default: return -EINVAL; } snd_soc_write(codec, WM8990_AUDIO_INTERFACE_1, audio1); snd_soc_write(codec, WM8990_AUDIO_INTERFACE_3, audio3); return 0; } static int wm8990_set_dai_clkdiv(struct snd_soc_dai *codec_dai, int div_id, int div) { struct snd_soc_codec *codec = codec_dai->codec; switch (div_id) { case WM8990_MCLK_DIV: snd_soc_update_bits(codec, WM8990_CLOCKING_2, WM8990_MCLK_DIV_MASK, div); break; case WM8990_DACCLK_DIV: snd_soc_update_bits(codec, WM8990_CLOCKING_2, WM8990_DAC_CLKDIV_MASK, div); break; case WM8990_ADCCLK_DIV: snd_soc_update_bits(codec, WM8990_CLOCKING_2, WM8990_ADC_CLKDIV_MASK, div); break; case WM8990_BCLK_DIV: snd_soc_update_bits(codec, WM8990_CLOCKING_1, WM8990_BCLK_DIV_MASK, div); break; default: return -EINVAL; } return 0; } /* * Set PCM DAI bit size and sample rate. */ static int wm8990_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { struct snd_soc_codec *codec = dai->codec; u16 audio1 = snd_soc_read(codec, WM8990_AUDIO_INTERFACE_1); audio1 &= ~WM8990_AIF_WL_MASK; /* bit size */ switch (params_width(params)) { case 16: break; case 20: audio1 |= WM8990_AIF_WL_20BITS; break; case 24: audio1 |= WM8990_AIF_WL_24BITS; break; case 32: audio1 |= WM8990_AIF_WL_32BITS; break; } snd_soc_write(codec, WM8990_AUDIO_INTERFACE_1, audio1); return 0; } static int wm8990_mute(struct snd_soc_dai *dai, int mute) { struct snd_soc_codec *codec = dai->codec; u16 val; val = snd_soc_read(codec, WM8990_DAC_CTRL) & ~WM8990_DAC_MUTE; if (mute) snd_soc_write(codec, WM8990_DAC_CTRL, val | WM8990_DAC_MUTE); else snd_soc_write(codec, WM8990_DAC_CTRL, val); return 0; } static int wm8990_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { struct wm8990_priv *wm8990 = snd_soc_codec_get_drvdata(codec); int ret; switch (level) { case SND_SOC_BIAS_ON: break; case SND_SOC_BIAS_PREPARE: /* VMID=2*50k */ snd_soc_update_bits(codec, WM8990_POWER_MANAGEMENT_1, WM8990_VMID_MODE_MASK, 0x2); break; case SND_SOC_BIAS_STANDBY: if (snd_soc_codec_get_bias_level(codec) == SND_SOC_BIAS_OFF) { ret = regcache_sync(wm8990->regmap); if (ret < 0) { dev_err(codec->dev, "Failed to sync cache: %d\n", ret); return ret; } /* Enable all output discharge bits */ snd_soc_write(codec, WM8990_ANTIPOP1, WM8990_DIS_LLINE | WM8990_DIS_RLINE | WM8990_DIS_OUT3 | WM8990_DIS_OUT4 | WM8990_DIS_LOUT | WM8990_DIS_ROUT); /* Enable POBCTRL, SOFT_ST, VMIDTOG and BUFDCOPEN */ snd_soc_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST | WM8990_BUFDCOPEN | WM8990_POBCTRL | WM8990_VMIDTOG); /* Delay to allow output caps to discharge */ msleep(300); /* Disable VMIDTOG */ snd_soc_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST | WM8990_BUFDCOPEN | WM8990_POBCTRL); /* disable all output discharge bits */ snd_soc_write(codec, WM8990_ANTIPOP1, 0); /* Enable outputs */ snd_soc_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1b00); msleep(50); /* Enable VMID at 2x50k */ snd_soc_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f02); msleep(100); /* Enable VREF */ snd_soc_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f03); msleep(600); /* Enable BUFIOEN */ snd_soc_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST | WM8990_BUFDCOPEN | WM8990_POBCTRL | WM8990_BUFIOEN); /* Disable outputs */ snd_soc_write(codec, WM8990_POWER_MANAGEMENT_1, 0x3); /* disable POBCTRL, SOFT_ST and BUFDCOPEN */ snd_soc_write(codec, WM8990_ANTIPOP2, WM8990_BUFIOEN); /* Enable workaround for ADC clocking issue. */ snd_soc_write(codec, WM8990_EXT_ACCESS_ENA, 0x2); snd_soc_write(codec, WM8990_EXT_CTL1, 0xa003); snd_soc_write(codec, WM8990_EXT_ACCESS_ENA, 0); } /* VMID=2*250k */ snd_soc_update_bits(codec, WM8990_POWER_MANAGEMENT_1, WM8990_VMID_MODE_MASK, 0x4); break; case SND_SOC_BIAS_OFF: /* Enable POBCTRL and SOFT_ST */ snd_soc_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST | WM8990_POBCTRL | WM8990_BUFIOEN); /* Enable POBCTRL, SOFT_ST and BUFDCOPEN */ snd_soc_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST | WM8990_BUFDCOPEN | WM8990_POBCTRL | WM8990_BUFIOEN); /* mute DAC */ snd_soc_update_bits(codec, WM8990_DAC_CTRL, WM8990_DAC_MUTE, WM8990_DAC_MUTE); /* Enable any disabled outputs */ snd_soc_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f03); /* Disable VMID */ snd_soc_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f01); msleep(300); /* Enable all output discharge bits */ snd_soc_write(codec, WM8990_ANTIPOP1, WM8990_DIS_LLINE | WM8990_DIS_RLINE | WM8990_DIS_OUT3 | WM8990_DIS_OUT4 | WM8990_DIS_LOUT | WM8990_DIS_ROUT); /* Disable VREF */ snd_soc_write(codec, WM8990_POWER_MANAGEMENT_1, 0x0); /* disable POBCTRL, SOFT_ST and BUFDCOPEN */ snd_soc_write(codec, WM8990_ANTIPOP2, 0x0); regcache_mark_dirty(wm8990->regmap); break; } return 0; } #define WM8990_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \ SNDRV_PCM_RATE_48000) #define WM8990_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) /* * The WM8990 supports 2 different and mutually exclusive DAI * configurations. * * 1. ADC/DAC on Primary Interface * 2. ADC on Primary Interface/DAC on secondary */ static const struct snd_soc_dai_ops wm8990_dai_ops = { .hw_params = wm8990_hw_params, .digital_mute = wm8990_mute, .set_fmt = wm8990_set_dai_fmt, .set_clkdiv = wm8990_set_dai_clkdiv, .set_pll = wm8990_set_dai_pll, .set_sysclk = wm8990_set_dai_sysclk, }; static struct snd_soc_dai_driver wm8990_dai = { /* ADC/DAC on primary */ .name = "wm8990-hifi", .playback = { .stream_name = "Playback", .channels_min = 1, .channels_max = 2, .rates = WM8990_RATES, .formats = WM8990_FORMATS,}, .capture = { .stream_name = "Capture", .channels_min = 1, .channels_max = 2, .rates = WM8990_RATES, .formats = WM8990_FORMATS,}, .ops = &wm8990_dai_ops, }; /* * initialise the WM8990 driver * register the mixer and dsp interfaces with the kernel */ static int wm8990_probe(struct snd_soc_codec *codec) { wm8990_reset(codec); /* charge output caps */ snd_soc_codec_force_bias_level(codec, SND_SOC_BIAS_STANDBY); snd_soc_update_bits(codec, WM8990_AUDIO_INTERFACE_4, WM8990_ALRCGPIO1, WM8990_ALRCGPIO1); snd_soc_update_bits(codec, WM8990_GPIO1_GPIO2, WM8990_GPIO1_SEL_MASK, 1); snd_soc_update_bits(codec, WM8990_POWER_MANAGEMENT_2, WM8990_OPCLK_ENA, WM8990_OPCLK_ENA); snd_soc_write(codec, WM8990_LEFT_OUTPUT_VOLUME, 0x50 | (1<<8)); snd_soc_write(codec, WM8990_RIGHT_OUTPUT_VOLUME, 0x50 | (1<<8)); return 0; } static struct snd_soc_codec_driver soc_codec_dev_wm8990 = { .probe = wm8990_probe, .set_bias_level = wm8990_set_bias_level, .suspend_bias_off = true, .controls = wm8990_snd_controls, .num_controls = ARRAY_SIZE(wm8990_snd_controls), .dapm_widgets = wm8990_dapm_widgets, .num_dapm_widgets = ARRAY_SIZE(wm8990_dapm_widgets), .dapm_routes = wm8990_dapm_routes, .num_dapm_routes = ARRAY_SIZE(wm8990_dapm_routes), }; static const struct regmap_config wm8990_regmap = { .reg_bits = 8, .val_bits = 16, .max_register = WM8990_PLL3, .volatile_reg = wm8990_volatile_register, .reg_defaults = wm8990_reg_defaults, .num_reg_defaults = ARRAY_SIZE(wm8990_reg_defaults), .cache_type = REGCACHE_RBTREE, }; static int wm8990_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { struct wm8990_priv *wm8990; int ret; wm8990 = devm_kzalloc(&i2c->dev, sizeof(struct wm8990_priv), GFP_KERNEL); if (wm8990 == NULL) return -ENOMEM; i2c_set_clientdata(i2c, wm8990); ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_wm8990, &wm8990_dai, 1); return ret; } static int wm8990_i2c_remove(struct i2c_client *client) { snd_soc_unregister_codec(&client->dev); return 0; } static const struct i2c_device_id wm8990_i2c_id[] = { { "wm8990", 0 }, { } }; MODULE_DEVICE_TABLE(i2c, wm8990_i2c_id); static struct i2c_driver wm8990_i2c_driver = { .driver = { .name = "wm8990", }, .probe = wm8990_i2c_probe, .remove = wm8990_i2c_remove, .id_table = wm8990_i2c_id, }; module_i2c_driver(wm8990_i2c_driver); MODULE_DESCRIPTION("ASoC WM8990 driver"); MODULE_AUTHOR("Liam Girdwood"); MODULE_LICENSE("GPL");