140e0aa64SRichard Purdie /* 240e0aa64SRichard Purdie * wm8731.c -- WM8731 ALSA SoC Audio driver 340e0aa64SRichard Purdie * 440e0aa64SRichard Purdie * Copyright 2005 Openedhand Ltd. 540e0aa64SRichard Purdie * 640e0aa64SRichard Purdie * Author: Richard Purdie <richard@openedhand.com> 740e0aa64SRichard Purdie * 840e0aa64SRichard Purdie * Based on wm8753.c by Liam Girdwood 940e0aa64SRichard Purdie * 1040e0aa64SRichard Purdie * This program is free software; you can redistribute it and/or modify 1140e0aa64SRichard Purdie * it under the terms of the GNU General Public License version 2 as 1240e0aa64SRichard Purdie * published by the Free Software Foundation. 1340e0aa64SRichard Purdie */ 1440e0aa64SRichard Purdie 1540e0aa64SRichard Purdie #include <linux/module.h> 1640e0aa64SRichard Purdie #include <linux/moduleparam.h> 1740e0aa64SRichard Purdie #include <linux/init.h> 1840e0aa64SRichard Purdie #include <linux/delay.h> 1940e0aa64SRichard Purdie #include <linux/pm.h> 2040e0aa64SRichard Purdie #include <linux/i2c.h> 215a0e3ad6STejun Heo #include <linux/slab.h> 2240e0aa64SRichard Purdie #include <linux/platform_device.h> 237dea7c01SMark Brown #include <linux/regulator/consumer.h> 24d2a40355SCliff Cai #include <linux/spi/spi.h> 25a7f96e4dSMark Brown #include <linux/of_device.h> 2640e0aa64SRichard Purdie #include <sound/core.h> 2740e0aa64SRichard Purdie #include <sound/pcm.h> 2840e0aa64SRichard Purdie #include <sound/pcm_params.h> 2940e0aa64SRichard Purdie #include <sound/soc.h> 3040e0aa64SRichard Purdie #include <sound/initval.h> 31d00efa64SMark Brown #include <sound/tlv.h> 3240e0aa64SRichard Purdie 3340e0aa64SRichard Purdie #include "wm8731.h" 3440e0aa64SRichard Purdie 357dea7c01SMark Brown #define WM8731_NUM_SUPPLIES 4 367dea7c01SMark Brown static const char *wm8731_supply_names[WM8731_NUM_SUPPLIES] = { 377dea7c01SMark Brown "AVDD", 387dea7c01SMark Brown "HPVDD", 397dea7c01SMark Brown "DCVDD", 407dea7c01SMark Brown "DBVDD", 417dea7c01SMark Brown }; 427dea7c01SMark Brown 43b36d61d4SFrank Mandarino /* codec private data */ 44b36d61d4SFrank Mandarino struct wm8731_priv { 45f0fba2adSLiam Girdwood enum snd_soc_control_type control_type; 467dea7c01SMark Brown struct regulator_bulk_data supplies[WM8731_NUM_SUPPLIES]; 47b36d61d4SFrank Mandarino unsigned int sysclk; 489745e824SMark Brown int sysclk_type; 49dd31b310SMark Brown int playback_fs; 50dd31b310SMark Brown bool deemph; 51b36d61d4SFrank Mandarino }; 52b36d61d4SFrank Mandarino 53a8035c8fSMark Brown 5440e0aa64SRichard Purdie /* 5540e0aa64SRichard Purdie * wm8731 register cache 5640e0aa64SRichard Purdie * We can't read the WM8731 register space when we are 5740e0aa64SRichard Purdie * using 2 wire for device control, so we cache them instead. 5840e0aa64SRichard Purdie * There is no point in caching the reset register 5940e0aa64SRichard Purdie */ 6040e0aa64SRichard Purdie static const u16 wm8731_reg[WM8731_CACHEREGNUM] = { 6140e0aa64SRichard Purdie 0x0097, 0x0097, 0x0079, 0x0079, 6240e0aa64SRichard Purdie 0x000a, 0x0008, 0x009f, 0x000a, 6340e0aa64SRichard Purdie 0x0000, 0x0000 6440e0aa64SRichard Purdie }; 6540e0aa64SRichard Purdie 6617a52fd6SMark Brown #define wm8731_reset(c) snd_soc_write(c, WM8731_RESET, 0) 6740e0aa64SRichard Purdie 6840e0aa64SRichard Purdie static const char *wm8731_input_select[] = {"Line In", "Mic"}; 6959f72970SMark Brown 7059f72970SMark Brown static const struct soc_enum wm8731_insel_enum = 7159f72970SMark Brown SOC_ENUM_SINGLE(WM8731_APANA, 2, 2, wm8731_input_select); 7259f72970SMark Brown 73dd31b310SMark Brown static int wm8731_deemph[] = { 0, 32000, 44100, 48000 }; 7459f72970SMark Brown 75dd31b310SMark Brown static int wm8731_set_deemph(struct snd_soc_codec *codec) 76dd31b310SMark Brown { 77dd31b310SMark Brown struct wm8731_priv *wm8731 = snd_soc_codec_get_drvdata(codec); 78dd31b310SMark Brown int val, i, best; 79dd31b310SMark Brown 80dd31b310SMark Brown /* If we're using deemphasis select the nearest available sample 81dd31b310SMark Brown * rate. 82dd31b310SMark Brown */ 83dd31b310SMark Brown if (wm8731->deemph) { 84dd31b310SMark Brown best = 1; 85dd31b310SMark Brown for (i = 2; i < ARRAY_SIZE(wm8731_deemph); i++) { 86dd31b310SMark Brown if (abs(wm8731_deemph[i] - wm8731->playback_fs) < 87dd31b310SMark Brown abs(wm8731_deemph[best] - wm8731->playback_fs)) 88dd31b310SMark Brown best = i; 89dd31b310SMark Brown } 90dd31b310SMark Brown 91dd31b310SMark Brown val = best << 1; 92dd31b310SMark Brown } else { 93dd31b310SMark Brown best = 0; 94dd31b310SMark Brown val = 0; 95dd31b310SMark Brown } 96dd31b310SMark Brown 97dd31b310SMark Brown dev_dbg(codec->dev, "Set deemphasis %d (%dHz)\n", 98dd31b310SMark Brown best, wm8731_deemph[best]); 99dd31b310SMark Brown 100dd31b310SMark Brown return snd_soc_update_bits(codec, WM8731_APDIGI, 0x6, val); 101dd31b310SMark Brown } 102dd31b310SMark Brown 103dd31b310SMark Brown static int wm8731_get_deemph(struct snd_kcontrol *kcontrol, 104dd31b310SMark Brown struct snd_ctl_elem_value *ucontrol) 105dd31b310SMark Brown { 106dd31b310SMark Brown struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); 107dd31b310SMark Brown struct wm8731_priv *wm8731 = snd_soc_codec_get_drvdata(codec); 108dd31b310SMark Brown 109dd31b310SMark Brown ucontrol->value.enumerated.item[0] = wm8731->deemph; 110dd31b310SMark Brown 111dd31b310SMark Brown return 0; 112dd31b310SMark Brown } 113dd31b310SMark Brown 114dd31b310SMark Brown static int wm8731_put_deemph(struct snd_kcontrol *kcontrol, 115dd31b310SMark Brown struct snd_ctl_elem_value *ucontrol) 116dd31b310SMark Brown { 117dd31b310SMark Brown struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); 118dd31b310SMark Brown struct wm8731_priv *wm8731 = snd_soc_codec_get_drvdata(codec); 119dd31b310SMark Brown int deemph = ucontrol->value.enumerated.item[0]; 120dd31b310SMark Brown int ret = 0; 121dd31b310SMark Brown 122dd31b310SMark Brown if (deemph > 1) 123dd31b310SMark Brown return -EINVAL; 124dd31b310SMark Brown 125dd31b310SMark Brown mutex_lock(&codec->mutex); 126dd31b310SMark Brown if (wm8731->deemph != deemph) { 127dd31b310SMark Brown wm8731->deemph = deemph; 128dd31b310SMark Brown 129dd31b310SMark Brown wm8731_set_deemph(codec); 130dd31b310SMark Brown 131dd31b310SMark Brown ret = 1; 132dd31b310SMark Brown } 133dd31b310SMark Brown mutex_unlock(&codec->mutex); 134dd31b310SMark Brown 135dd31b310SMark Brown return ret; 136dd31b310SMark Brown } 13740e0aa64SRichard Purdie 138d00efa64SMark Brown static const DECLARE_TLV_DB_SCALE(in_tlv, -3450, 150, 0); 139d00efa64SMark Brown static const DECLARE_TLV_DB_SCALE(sidetone_tlv, -1500, 300, 0); 140d00efa64SMark Brown static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1); 141d921184eSMark Brown static const DECLARE_TLV_DB_SCALE(mic_tlv, 0, 2000, 0); 142d00efa64SMark Brown 14340e0aa64SRichard Purdie static const struct snd_kcontrol_new wm8731_snd_controls[] = { 14440e0aa64SRichard Purdie 145d00efa64SMark Brown SOC_DOUBLE_R_TLV("Master Playback Volume", WM8731_LOUT1V, WM8731_ROUT1V, 146d00efa64SMark Brown 0, 127, 0, out_tlv), 147bd903b6eSLiam Girdwood SOC_DOUBLE_R("Master Playback ZC Switch", WM8731_LOUT1V, WM8731_ROUT1V, 148bd903b6eSLiam Girdwood 7, 1, 0), 14940e0aa64SRichard Purdie 150d00efa64SMark Brown SOC_DOUBLE_R_TLV("Capture Volume", WM8731_LINVOL, WM8731_RINVOL, 0, 31, 0, 151d00efa64SMark Brown in_tlv), 15240e0aa64SRichard Purdie SOC_DOUBLE_R("Line Capture Switch", WM8731_LINVOL, WM8731_RINVOL, 7, 1, 1), 15340e0aa64SRichard Purdie 154d921184eSMark Brown SOC_SINGLE_TLV("Mic Boost Volume", WM8731_APANA, 0, 1, 0, mic_tlv), 155ef38ed88SMark Brown SOC_SINGLE("Mic Capture Switch", WM8731_APANA, 1, 1, 1), 15640e0aa64SRichard Purdie 157d00efa64SMark Brown SOC_SINGLE_TLV("Sidetone Playback Volume", WM8731_APANA, 6, 3, 1, 158d00efa64SMark Brown sidetone_tlv), 15940e0aa64SRichard Purdie 16040e0aa64SRichard Purdie SOC_SINGLE("ADC High Pass Filter Switch", WM8731_APDIGI, 0, 1, 1), 16140e0aa64SRichard Purdie SOC_SINGLE("Store DC Offset Switch", WM8731_APDIGI, 4, 1, 0), 16240e0aa64SRichard Purdie 163dd31b310SMark Brown SOC_SINGLE_BOOL_EXT("Playback Deemphasis Switch", 0, 164dd31b310SMark Brown wm8731_get_deemph, wm8731_put_deemph), 16540e0aa64SRichard Purdie }; 16640e0aa64SRichard Purdie 16740e0aa64SRichard Purdie /* Output Mixer */ 16840e0aa64SRichard Purdie static const struct snd_kcontrol_new wm8731_output_mixer_controls[] = { 16940e0aa64SRichard Purdie SOC_DAPM_SINGLE("Line Bypass Switch", WM8731_APANA, 3, 1, 0), 17040e0aa64SRichard Purdie SOC_DAPM_SINGLE("Mic Sidetone Switch", WM8731_APANA, 5, 1, 0), 17140e0aa64SRichard Purdie SOC_DAPM_SINGLE("HiFi Playback Switch", WM8731_APANA, 4, 1, 0), 17240e0aa64SRichard Purdie }; 17340e0aa64SRichard Purdie 17440e0aa64SRichard Purdie /* Input mux */ 17540e0aa64SRichard Purdie static const struct snd_kcontrol_new wm8731_input_mux_controls = 17659f72970SMark Brown SOC_DAPM_ENUM("Input Select", wm8731_insel_enum); 17740e0aa64SRichard Purdie 17840e0aa64SRichard Purdie static const struct snd_soc_dapm_widget wm8731_dapm_widgets[] = { 1798a27bd9aSMark Brown SND_SOC_DAPM_SUPPLY("ACTIVE",WM8731_ACTIVE, 0, 0, NULL, 0), 1809745e824SMark Brown SND_SOC_DAPM_SUPPLY("OSC", WM8731_PWR, 5, 1, NULL, 0), 18140e0aa64SRichard Purdie SND_SOC_DAPM_MIXER("Output Mixer", WM8731_PWR, 4, 1, 18240e0aa64SRichard Purdie &wm8731_output_mixer_controls[0], 18340e0aa64SRichard Purdie ARRAY_SIZE(wm8731_output_mixer_controls)), 18440e0aa64SRichard Purdie SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8731_PWR, 3, 1), 18540e0aa64SRichard Purdie SND_SOC_DAPM_OUTPUT("LOUT"), 18640e0aa64SRichard Purdie SND_SOC_DAPM_OUTPUT("LHPOUT"), 18740e0aa64SRichard Purdie SND_SOC_DAPM_OUTPUT("ROUT"), 18840e0aa64SRichard Purdie SND_SOC_DAPM_OUTPUT("RHPOUT"), 18940e0aa64SRichard Purdie SND_SOC_DAPM_ADC("ADC", "HiFi Capture", WM8731_PWR, 2, 1), 19040e0aa64SRichard Purdie SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0, &wm8731_input_mux_controls), 19140e0aa64SRichard Purdie SND_SOC_DAPM_PGA("Line Input", WM8731_PWR, 0, 1, NULL, 0), 19240e0aa64SRichard Purdie SND_SOC_DAPM_MICBIAS("Mic Bias", WM8731_PWR, 1, 1), 19340e0aa64SRichard Purdie SND_SOC_DAPM_INPUT("MICIN"), 19440e0aa64SRichard Purdie SND_SOC_DAPM_INPUT("RLINEIN"), 19540e0aa64SRichard Purdie SND_SOC_DAPM_INPUT("LLINEIN"), 19640e0aa64SRichard Purdie }; 19740e0aa64SRichard Purdie 1989745e824SMark Brown static int wm8731_check_osc(struct snd_soc_dapm_widget *source, 1999745e824SMark Brown struct snd_soc_dapm_widget *sink) 2009745e824SMark Brown { 2019745e824SMark Brown struct wm8731_priv *wm8731 = snd_soc_codec_get_drvdata(source->codec); 2029745e824SMark Brown 2035a195b44SNicolas Ferre return wm8731->sysclk_type == WM8731_SYSCLK_XTAL; 2049745e824SMark Brown } 2059745e824SMark Brown 2065e251aecSMark Brown static const struct snd_soc_dapm_route wm8731_intercon[] = { 2079745e824SMark Brown {"DAC", NULL, "OSC", wm8731_check_osc}, 2089745e824SMark Brown {"ADC", NULL, "OSC", wm8731_check_osc}, 2098a27bd9aSMark Brown {"DAC", NULL, "ACTIVE"}, 2108a27bd9aSMark Brown {"ADC", NULL, "ACTIVE"}, 2119745e824SMark Brown 21240e0aa64SRichard Purdie /* output mixer */ 21340e0aa64SRichard Purdie {"Output Mixer", "Line Bypass Switch", "Line Input"}, 21440e0aa64SRichard Purdie {"Output Mixer", "HiFi Playback Switch", "DAC"}, 21540e0aa64SRichard Purdie {"Output Mixer", "Mic Sidetone Switch", "Mic Bias"}, 21640e0aa64SRichard Purdie 21740e0aa64SRichard Purdie /* outputs */ 21840e0aa64SRichard Purdie {"RHPOUT", NULL, "Output Mixer"}, 21940e0aa64SRichard Purdie {"ROUT", NULL, "Output Mixer"}, 22040e0aa64SRichard Purdie {"LHPOUT", NULL, "Output Mixer"}, 22140e0aa64SRichard Purdie {"LOUT", NULL, "Output Mixer"}, 22240e0aa64SRichard Purdie 22340e0aa64SRichard Purdie /* input mux */ 22440e0aa64SRichard Purdie {"Input Mux", "Line In", "Line Input"}, 22540e0aa64SRichard Purdie {"Input Mux", "Mic", "Mic Bias"}, 22640e0aa64SRichard Purdie {"ADC", NULL, "Input Mux"}, 22740e0aa64SRichard Purdie 22840e0aa64SRichard Purdie /* inputs */ 22940e0aa64SRichard Purdie {"Line Input", NULL, "LLINEIN"}, 23040e0aa64SRichard Purdie {"Line Input", NULL, "RLINEIN"}, 23140e0aa64SRichard Purdie {"Mic Bias", NULL, "MICIN"}, 23240e0aa64SRichard Purdie }; 23340e0aa64SRichard Purdie 23440e0aa64SRichard Purdie struct _coeff_div { 23540e0aa64SRichard Purdie u32 mclk; 23640e0aa64SRichard Purdie u32 rate; 23740e0aa64SRichard Purdie u16 fs; 23840e0aa64SRichard Purdie u8 sr:4; 23940e0aa64SRichard Purdie u8 bosr:1; 24040e0aa64SRichard Purdie u8 usb:1; 24140e0aa64SRichard Purdie }; 24240e0aa64SRichard Purdie 24340e0aa64SRichard Purdie /* codec mclk clock divider coefficients */ 24440e0aa64SRichard Purdie static const struct _coeff_div coeff_div[] = { 24540e0aa64SRichard Purdie /* 48k */ 24640e0aa64SRichard Purdie {12288000, 48000, 256, 0x0, 0x0, 0x0}, 24740e0aa64SRichard Purdie {18432000, 48000, 384, 0x0, 0x1, 0x0}, 24840e0aa64SRichard Purdie {12000000, 48000, 250, 0x0, 0x0, 0x1}, 24940e0aa64SRichard Purdie 25040e0aa64SRichard Purdie /* 32k */ 25140e0aa64SRichard Purdie {12288000, 32000, 384, 0x6, 0x0, 0x0}, 25240e0aa64SRichard Purdie {18432000, 32000, 576, 0x6, 0x1, 0x0}, 253298a2c75SFrank Mandarino {12000000, 32000, 375, 0x6, 0x0, 0x1}, 25440e0aa64SRichard Purdie 25540e0aa64SRichard Purdie /* 8k */ 25640e0aa64SRichard Purdie {12288000, 8000, 1536, 0x3, 0x0, 0x0}, 25740e0aa64SRichard Purdie {18432000, 8000, 2304, 0x3, 0x1, 0x0}, 25840e0aa64SRichard Purdie {11289600, 8000, 1408, 0xb, 0x0, 0x0}, 25940e0aa64SRichard Purdie {16934400, 8000, 2112, 0xb, 0x1, 0x0}, 26040e0aa64SRichard Purdie {12000000, 8000, 1500, 0x3, 0x0, 0x1}, 26140e0aa64SRichard Purdie 26240e0aa64SRichard Purdie /* 96k */ 26340e0aa64SRichard Purdie {12288000, 96000, 128, 0x7, 0x0, 0x0}, 26440e0aa64SRichard Purdie {18432000, 96000, 192, 0x7, 0x1, 0x0}, 26540e0aa64SRichard Purdie {12000000, 96000, 125, 0x7, 0x0, 0x1}, 26640e0aa64SRichard Purdie 26740e0aa64SRichard Purdie /* 44.1k */ 26840e0aa64SRichard Purdie {11289600, 44100, 256, 0x8, 0x0, 0x0}, 26940e0aa64SRichard Purdie {16934400, 44100, 384, 0x8, 0x1, 0x0}, 27040e0aa64SRichard Purdie {12000000, 44100, 272, 0x8, 0x1, 0x1}, 27140e0aa64SRichard Purdie 27240e0aa64SRichard Purdie /* 88.2k */ 27340e0aa64SRichard Purdie {11289600, 88200, 128, 0xf, 0x0, 0x0}, 27440e0aa64SRichard Purdie {16934400, 88200, 192, 0xf, 0x1, 0x0}, 27540e0aa64SRichard Purdie {12000000, 88200, 136, 0xf, 0x1, 0x1}, 27640e0aa64SRichard Purdie }; 27740e0aa64SRichard Purdie 27840e0aa64SRichard Purdie static inline int get_coeff(int mclk, int rate) 27940e0aa64SRichard Purdie { 28040e0aa64SRichard Purdie int i; 28140e0aa64SRichard Purdie 28240e0aa64SRichard Purdie for (i = 0; i < ARRAY_SIZE(coeff_div); i++) { 28340e0aa64SRichard Purdie if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk) 28440e0aa64SRichard Purdie return i; 28540e0aa64SRichard Purdie } 28640e0aa64SRichard Purdie return 0; 28740e0aa64SRichard Purdie } 28840e0aa64SRichard Purdie 289b36d61d4SFrank Mandarino static int wm8731_hw_params(struct snd_pcm_substream *substream, 290dee89c4dSMark Brown struct snd_pcm_hw_params *params, 291dee89c4dSMark Brown struct snd_soc_dai *dai) 29240e0aa64SRichard Purdie { 293f0fba2adSLiam Girdwood struct snd_soc_codec *codec = dai->codec; 294b2c812e2SMark Brown struct wm8731_priv *wm8731 = snd_soc_codec_get_drvdata(codec); 29517a52fd6SMark Brown u16 iface = snd_soc_read(codec, WM8731_IFACE) & 0xfff3; 296b36d61d4SFrank Mandarino int i = get_coeff(wm8731->sysclk, params_rate(params)); 297b36d61d4SFrank Mandarino u16 srate = (coeff_div[i].sr << 2) | 298b36d61d4SFrank Mandarino (coeff_div[i].bosr << 1) | coeff_div[i].usb; 29940e0aa64SRichard Purdie 300dd31b310SMark Brown wm8731->playback_fs = params_rate(params); 301dd31b310SMark Brown 30217a52fd6SMark Brown snd_soc_write(codec, WM8731_SRATE, srate); 30340e0aa64SRichard Purdie 304b36d61d4SFrank Mandarino /* bit size */ 305b36d61d4SFrank Mandarino switch (params_format(params)) { 306b36d61d4SFrank Mandarino case SNDRV_PCM_FORMAT_S16_LE: 307b36d61d4SFrank Mandarino break; 308b36d61d4SFrank Mandarino case SNDRV_PCM_FORMAT_S20_3LE: 309b36d61d4SFrank Mandarino iface |= 0x0004; 310b36d61d4SFrank Mandarino break; 311b36d61d4SFrank Mandarino case SNDRV_PCM_FORMAT_S24_LE: 312b36d61d4SFrank Mandarino iface |= 0x0008; 313b36d61d4SFrank Mandarino break; 314b36d61d4SFrank Mandarino } 315b36d61d4SFrank Mandarino 316dd31b310SMark Brown wm8731_set_deemph(codec); 317dd31b310SMark Brown 31817a52fd6SMark Brown snd_soc_write(codec, WM8731_IFACE, iface); 319b36d61d4SFrank Mandarino return 0; 32040e0aa64SRichard Purdie } 32140e0aa64SRichard Purdie 322e550e17fSLiam Girdwood static int wm8731_mute(struct snd_soc_dai *dai, int mute) 32340e0aa64SRichard Purdie { 324b36d61d4SFrank Mandarino struct snd_soc_codec *codec = dai->codec; 32517a52fd6SMark Brown u16 mute_reg = snd_soc_read(codec, WM8731_APDIGI) & 0xfff7; 326b36d61d4SFrank Mandarino 32740e0aa64SRichard Purdie if (mute) 32817a52fd6SMark Brown snd_soc_write(codec, WM8731_APDIGI, mute_reg | 0x8); 32940e0aa64SRichard Purdie else 33017a52fd6SMark Brown snd_soc_write(codec, WM8731_APDIGI, mute_reg); 33140e0aa64SRichard Purdie return 0; 33240e0aa64SRichard Purdie } 33340e0aa64SRichard Purdie 334e550e17fSLiam Girdwood static int wm8731_set_dai_sysclk(struct snd_soc_dai *codec_dai, 335b36d61d4SFrank Mandarino int clk_id, unsigned int freq, int dir) 336b36d61d4SFrank Mandarino { 337b36d61d4SFrank Mandarino struct snd_soc_codec *codec = codec_dai->codec; 338b2c812e2SMark Brown struct wm8731_priv *wm8731 = snd_soc_codec_get_drvdata(codec); 339b36d61d4SFrank Mandarino 3409745e824SMark Brown switch (clk_id) { 3419745e824SMark Brown case WM8731_SYSCLK_XTAL: 3429745e824SMark Brown case WM8731_SYSCLK_MCLK: 3439745e824SMark Brown wm8731->sysclk_type = clk_id; 3449745e824SMark Brown break; 3459745e824SMark Brown default: 3469745e824SMark Brown return -EINVAL; 3479745e824SMark Brown } 3489745e824SMark Brown 349b36d61d4SFrank Mandarino switch (freq) { 350b36d61d4SFrank Mandarino case 11289600: 351b36d61d4SFrank Mandarino case 12000000: 352b36d61d4SFrank Mandarino case 12288000: 353b36d61d4SFrank Mandarino case 16934400: 354b36d61d4SFrank Mandarino case 18432000: 355b36d61d4SFrank Mandarino wm8731->sysclk = freq; 3569745e824SMark Brown break; 3579745e824SMark Brown default: 358b36d61d4SFrank Mandarino return -EINVAL; 359b36d61d4SFrank Mandarino } 360b36d61d4SFrank Mandarino 361ce6120ccSLiam Girdwood snd_soc_dapm_sync(&codec->dapm); 3629745e824SMark Brown 3639745e824SMark Brown return 0; 3649745e824SMark Brown } 3659745e824SMark Brown 366b36d61d4SFrank Mandarino 367e550e17fSLiam Girdwood static int wm8731_set_dai_fmt(struct snd_soc_dai *codec_dai, 368b36d61d4SFrank Mandarino unsigned int fmt) 369b36d61d4SFrank Mandarino { 370b36d61d4SFrank Mandarino struct snd_soc_codec *codec = codec_dai->codec; 371b36d61d4SFrank Mandarino u16 iface = 0; 372b36d61d4SFrank Mandarino 373b36d61d4SFrank Mandarino /* set master/slave audio interface */ 374b36d61d4SFrank Mandarino switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { 375b36d61d4SFrank Mandarino case SND_SOC_DAIFMT_CBM_CFM: 376b36d61d4SFrank Mandarino iface |= 0x0040; 377b36d61d4SFrank Mandarino break; 378b36d61d4SFrank Mandarino case SND_SOC_DAIFMT_CBS_CFS: 379b36d61d4SFrank Mandarino break; 380b36d61d4SFrank Mandarino default: 381b36d61d4SFrank Mandarino return -EINVAL; 382b36d61d4SFrank Mandarino } 383b36d61d4SFrank Mandarino 384b36d61d4SFrank Mandarino /* interface format */ 385b36d61d4SFrank Mandarino switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { 386b36d61d4SFrank Mandarino case SND_SOC_DAIFMT_I2S: 387b36d61d4SFrank Mandarino iface |= 0x0002; 388b36d61d4SFrank Mandarino break; 389b36d61d4SFrank Mandarino case SND_SOC_DAIFMT_RIGHT_J: 390b36d61d4SFrank Mandarino break; 391b36d61d4SFrank Mandarino case SND_SOC_DAIFMT_LEFT_J: 392b36d61d4SFrank Mandarino iface |= 0x0001; 393b36d61d4SFrank Mandarino break; 394b36d61d4SFrank Mandarino case SND_SOC_DAIFMT_DSP_A: 395b36d61d4SFrank Mandarino iface |= 0x0003; 396b36d61d4SFrank Mandarino break; 397b36d61d4SFrank Mandarino case SND_SOC_DAIFMT_DSP_B: 398b36d61d4SFrank Mandarino iface |= 0x0013; 399b36d61d4SFrank Mandarino break; 400b36d61d4SFrank Mandarino default: 401b36d61d4SFrank Mandarino return -EINVAL; 402b36d61d4SFrank Mandarino } 403b36d61d4SFrank Mandarino 404b36d61d4SFrank Mandarino /* clock inversion */ 405b36d61d4SFrank Mandarino switch (fmt & SND_SOC_DAIFMT_INV_MASK) { 406b36d61d4SFrank Mandarino case SND_SOC_DAIFMT_NB_NF: 407b36d61d4SFrank Mandarino break; 408b36d61d4SFrank Mandarino case SND_SOC_DAIFMT_IB_IF: 409b36d61d4SFrank Mandarino iface |= 0x0090; 410b36d61d4SFrank Mandarino break; 411b36d61d4SFrank Mandarino case SND_SOC_DAIFMT_IB_NF: 412b36d61d4SFrank Mandarino iface |= 0x0080; 413b36d61d4SFrank Mandarino break; 414b36d61d4SFrank Mandarino case SND_SOC_DAIFMT_NB_IF: 415b36d61d4SFrank Mandarino iface |= 0x0010; 416b36d61d4SFrank Mandarino break; 417b36d61d4SFrank Mandarino default: 418b36d61d4SFrank Mandarino return -EINVAL; 419b36d61d4SFrank Mandarino } 420b36d61d4SFrank Mandarino 421b36d61d4SFrank Mandarino /* set iface */ 42217a52fd6SMark Brown snd_soc_write(codec, WM8731_IFACE, iface); 423b36d61d4SFrank Mandarino return 0; 424b36d61d4SFrank Mandarino } 425b36d61d4SFrank Mandarino 4260be9898aSMark Brown static int wm8731_set_bias_level(struct snd_soc_codec *codec, 4270be9898aSMark Brown enum snd_soc_bias_level level) 42840e0aa64SRichard Purdie { 42906ae9988SMark Brown struct wm8731_priv *wm8731 = snd_soc_codec_get_drvdata(codec); 4309bf311feSAxel Lin int ret; 43122d22ee5SMark Brown u16 reg; 43240e0aa64SRichard Purdie 4330be9898aSMark Brown switch (level) { 4340be9898aSMark Brown case SND_SOC_BIAS_ON: 43540e0aa64SRichard Purdie break; 4360be9898aSMark Brown case SND_SOC_BIAS_PREPARE: 43740e0aa64SRichard Purdie break; 4380be9898aSMark Brown case SND_SOC_BIAS_STANDBY: 439ce6120ccSLiam Girdwood if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) { 44006ae9988SMark Brown ret = regulator_bulk_enable(ARRAY_SIZE(wm8731->supplies), 44106ae9988SMark Brown wm8731->supplies); 44206ae9988SMark Brown if (ret != 0) 44306ae9988SMark Brown return ret; 44406ae9988SMark Brown 4459bf311feSAxel Lin snd_soc_cache_sync(codec); 44606ae9988SMark Brown } 44706ae9988SMark Brown 44822d22ee5SMark Brown /* Clear PWROFF, gate CLKOUT, everything else as-is */ 44917a52fd6SMark Brown reg = snd_soc_read(codec, WM8731_PWR) & 0xff7f; 45017a52fd6SMark Brown snd_soc_write(codec, WM8731_PWR, reg | 0x0040); 45140e0aa64SRichard Purdie break; 4520be9898aSMark Brown case SND_SOC_BIAS_OFF: 45317a52fd6SMark Brown snd_soc_write(codec, WM8731_PWR, 0xffff); 45406ae9988SMark Brown regulator_bulk_disable(ARRAY_SIZE(wm8731->supplies), 45506ae9988SMark Brown wm8731->supplies); 456ed3e80c4SMark Brown codec->cache_sync = 1; 45740e0aa64SRichard Purdie break; 45840e0aa64SRichard Purdie } 459ce6120ccSLiam Girdwood codec->dapm.bias_level = level; 46040e0aa64SRichard Purdie return 0; 46140e0aa64SRichard Purdie } 46240e0aa64SRichard Purdie 463e135443eSBill Gatliff #define WM8731_RATES SNDRV_PCM_RATE_8000_96000 464b36d61d4SFrank Mandarino 465b36d61d4SFrank Mandarino #define WM8731_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ 466b36d61d4SFrank Mandarino SNDRV_PCM_FMTBIT_S24_LE) 467b36d61d4SFrank Mandarino 468*85e7652dSLars-Peter Clausen static const struct snd_soc_dai_ops wm8731_dai_ops = { 4696335d055SEric Miao .hw_params = wm8731_hw_params, 4706335d055SEric Miao .digital_mute = wm8731_mute, 4716335d055SEric Miao .set_sysclk = wm8731_set_dai_sysclk, 4726335d055SEric Miao .set_fmt = wm8731_set_dai_fmt, 4736335d055SEric Miao }; 4746335d055SEric Miao 475f0fba2adSLiam Girdwood static struct snd_soc_dai_driver wm8731_dai = { 476f0fba2adSLiam Girdwood .name = "wm8731-hifi", 47740e0aa64SRichard Purdie .playback = { 47840e0aa64SRichard Purdie .stream_name = "Playback", 47940e0aa64SRichard Purdie .channels_min = 1, 48040e0aa64SRichard Purdie .channels_max = 2, 481b36d61d4SFrank Mandarino .rates = WM8731_RATES, 482b36d61d4SFrank Mandarino .formats = WM8731_FORMATS,}, 48340e0aa64SRichard Purdie .capture = { 48440e0aa64SRichard Purdie .stream_name = "Capture", 48540e0aa64SRichard Purdie .channels_min = 1, 48640e0aa64SRichard Purdie .channels_max = 2, 487b36d61d4SFrank Mandarino .rates = WM8731_RATES, 488b36d61d4SFrank Mandarino .formats = WM8731_FORMATS,}, 4896335d055SEric Miao .ops = &wm8731_dai_ops, 4904934482dSMark Brown .symmetric_rates = 1, 49140e0aa64SRichard Purdie }; 49240e0aa64SRichard Purdie 493b3b50b3fSMark Brown #ifdef CONFIG_PM 494f0fba2adSLiam Girdwood static int wm8731_suspend(struct snd_soc_codec *codec, pm_message_t state) 49540e0aa64SRichard Purdie { 4960be9898aSMark Brown wm8731_set_bias_level(codec, SND_SOC_BIAS_OFF); 49706ae9988SMark Brown 49840e0aa64SRichard Purdie return 0; 49940e0aa64SRichard Purdie } 50040e0aa64SRichard Purdie 501f0fba2adSLiam Girdwood static int wm8731_resume(struct snd_soc_codec *codec) 50240e0aa64SRichard Purdie { 5030be9898aSMark Brown wm8731_set_bias_level(codec, SND_SOC_BIAS_STANDBY); 5047dea7c01SMark Brown 50540e0aa64SRichard Purdie return 0; 50640e0aa64SRichard Purdie } 507b3b50b3fSMark Brown #else 508b3b50b3fSMark Brown #define wm8731_suspend NULL 509b3b50b3fSMark Brown #define wm8731_resume NULL 510b3b50b3fSMark Brown #endif 51140e0aa64SRichard Purdie 512f0fba2adSLiam Girdwood static int wm8731_probe(struct snd_soc_codec *codec) 51340e0aa64SRichard Purdie { 514f0fba2adSLiam Girdwood struct wm8731_priv *wm8731 = snd_soc_codec_get_drvdata(codec); 515f0fba2adSLiam Girdwood int ret = 0, i; 51640e0aa64SRichard Purdie 517f0fba2adSLiam Girdwood ret = snd_soc_codec_set_cache_io(codec, 7, 9, wm8731->control_type); 51817a52fd6SMark Brown if (ret < 0) { 51917a52fd6SMark Brown dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); 520f0fba2adSLiam Girdwood return ret; 52117a52fd6SMark Brown } 52217a52fd6SMark Brown 5237dea7c01SMark Brown for (i = 0; i < ARRAY_SIZE(wm8731->supplies); i++) 5247dea7c01SMark Brown wm8731->supplies[i].supply = wm8731_supply_names[i]; 5257dea7c01SMark Brown 5267dea7c01SMark Brown ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(wm8731->supplies), 5277dea7c01SMark Brown wm8731->supplies); 5287dea7c01SMark Brown if (ret != 0) { 5297dea7c01SMark Brown dev_err(codec->dev, "Failed to request supplies: %d\n", ret); 530f0fba2adSLiam Girdwood return ret; 5317dea7c01SMark Brown } 5327dea7c01SMark Brown 5337dea7c01SMark Brown ret = regulator_bulk_enable(ARRAY_SIZE(wm8731->supplies), 5347dea7c01SMark Brown wm8731->supplies); 5357dea7c01SMark Brown if (ret != 0) { 5367dea7c01SMark Brown dev_err(codec->dev, "Failed to enable supplies: %d\n", ret); 5377dea7c01SMark Brown goto err_regulator_get; 5387dea7c01SMark Brown } 5397dea7c01SMark Brown 540519cf2dfSMark Brown ret = wm8731_reset(codec); 541519cf2dfSMark Brown if (ret < 0) { 542fe5422fcSMark Brown dev_err(codec->dev, "Failed to issue reset: %d\n", ret); 5437dea7c01SMark Brown goto err_regulator_enable; 544519cf2dfSMark Brown } 545519cf2dfSMark Brown 5465998102bSMark Brown wm8731_set_bias_level(codec, SND_SOC_BIAS_STANDBY); 5475998102bSMark Brown 5485998102bSMark Brown /* Latch the update bits */ 54917a52fd6SMark Brown snd_soc_update_bits(codec, WM8731_LOUT1V, 0x100, 0); 55017a52fd6SMark Brown snd_soc_update_bits(codec, WM8731_ROUT1V, 0x100, 0); 55117a52fd6SMark Brown snd_soc_update_bits(codec, WM8731_LINVOL, 0x100, 0); 55217a52fd6SMark Brown snd_soc_update_bits(codec, WM8731_RINVOL, 0x100, 0); 5535998102bSMark Brown 554ce3bdaa8SMark Brown /* Disable bypass path by default */ 5552062ea52SDimitris Papastamos snd_soc_update_bits(codec, WM8731_APANA, 0x8, 0); 556ce3bdaa8SMark Brown 55706ae9988SMark Brown /* Regulators will have been enabled by bias management */ 55806ae9988SMark Brown regulator_bulk_disable(ARRAY_SIZE(wm8731->supplies), wm8731->supplies); 55906ae9988SMark Brown 560a8035c8fSMark Brown return 0; 561fe5422fcSMark Brown 5627dea7c01SMark Brown err_regulator_enable: 5637dea7c01SMark Brown regulator_bulk_disable(ARRAY_SIZE(wm8731->supplies), wm8731->supplies); 5647dea7c01SMark Brown err_regulator_get: 5657dea7c01SMark Brown regulator_bulk_free(ARRAY_SIZE(wm8731->supplies), wm8731->supplies); 566f0fba2adSLiam Girdwood 567fe5422fcSMark Brown return ret; 568a8035c8fSMark Brown } 569a8035c8fSMark Brown 570f0fba2adSLiam Girdwood /* power down chip */ 571f0fba2adSLiam Girdwood static int wm8731_remove(struct snd_soc_codec *codec) 5725998102bSMark Brown { 573f0fba2adSLiam Girdwood struct wm8731_priv *wm8731 = snd_soc_codec_get_drvdata(codec); 574f0fba2adSLiam Girdwood 575f0fba2adSLiam Girdwood wm8731_set_bias_level(codec, SND_SOC_BIAS_OFF); 576f0fba2adSLiam Girdwood 577f0fba2adSLiam Girdwood regulator_bulk_disable(ARRAY_SIZE(wm8731->supplies), wm8731->supplies); 5787dea7c01SMark Brown regulator_bulk_free(ARRAY_SIZE(wm8731->supplies), wm8731->supplies); 579f0fba2adSLiam Girdwood 580f0fba2adSLiam Girdwood return 0; 5815998102bSMark Brown } 582a8035c8fSMark Brown 583f0fba2adSLiam Girdwood static struct snd_soc_codec_driver soc_codec_dev_wm8731 = { 584f0fba2adSLiam Girdwood .probe = wm8731_probe, 585f0fba2adSLiam Girdwood .remove = wm8731_remove, 586f0fba2adSLiam Girdwood .suspend = wm8731_suspend, 587f0fba2adSLiam Girdwood .resume = wm8731_resume, 588f0fba2adSLiam Girdwood .set_bias_level = wm8731_set_bias_level, 589e5eec34cSDimitris Papastamos .reg_cache_size = ARRAY_SIZE(wm8731_reg), 590f0fba2adSLiam Girdwood .reg_word_size = sizeof(u16), 591f0fba2adSLiam Girdwood .reg_cache_default = wm8731_reg, 5925e251aecSMark Brown .dapm_widgets = wm8731_dapm_widgets, 5935e251aecSMark Brown .num_dapm_widgets = ARRAY_SIZE(wm8731_dapm_widgets), 5945e251aecSMark Brown .dapm_routes = wm8731_intercon, 5955e251aecSMark Brown .num_dapm_routes = ARRAY_SIZE(wm8731_intercon), 596cb555318SMark Brown .controls = wm8731_snd_controls, 597cb555318SMark Brown .num_controls = ARRAY_SIZE(wm8731_snd_controls), 598f0fba2adSLiam Girdwood }; 599f0fba2adSLiam Girdwood 600a7f96e4dSMark Brown static const struct of_device_id wm8731_of_match[] = { 601a7f96e4dSMark Brown { .compatible = "wlf,wm8731", }, 602a7f96e4dSMark Brown { } 603a7f96e4dSMark Brown }; 604a7f96e4dSMark Brown 605a7f96e4dSMark Brown MODULE_DEVICE_TABLE(of, wm8731_of_match); 606a7f96e4dSMark Brown 6075998102bSMark Brown #if defined(CONFIG_SPI_MASTER) 6085998102bSMark Brown static int __devinit wm8731_spi_probe(struct spi_device *spi) 6095998102bSMark Brown { 6105998102bSMark Brown struct wm8731_priv *wm8731; 611f0fba2adSLiam Girdwood int ret; 6125998102bSMark Brown 6135998102bSMark Brown wm8731 = kzalloc(sizeof(struct wm8731_priv), GFP_KERNEL); 6145998102bSMark Brown if (wm8731 == NULL) 6155998102bSMark Brown return -ENOMEM; 6165998102bSMark Brown 617f0fba2adSLiam Girdwood wm8731->control_type = SND_SOC_SPI; 618f0fba2adSLiam Girdwood spi_set_drvdata(spi, wm8731); 6195998102bSMark Brown 620f0fba2adSLiam Girdwood ret = snd_soc_register_codec(&spi->dev, 621f0fba2adSLiam Girdwood &soc_codec_dev_wm8731, &wm8731_dai, 1); 622f0fba2adSLiam Girdwood if (ret < 0) 623f0fba2adSLiam Girdwood kfree(wm8731); 624f0fba2adSLiam Girdwood return ret; 6255998102bSMark Brown } 6265998102bSMark Brown 6275998102bSMark Brown static int __devexit wm8731_spi_remove(struct spi_device *spi) 6285998102bSMark Brown { 629f0fba2adSLiam Girdwood snd_soc_unregister_codec(&spi->dev); 630f0fba2adSLiam Girdwood kfree(spi_get_drvdata(spi)); 6315998102bSMark Brown return 0; 6325998102bSMark Brown } 6335998102bSMark Brown 6345998102bSMark Brown static struct spi_driver wm8731_spi_driver = { 6355998102bSMark Brown .driver = { 63699b59f3cSMark Brown .name = "wm8731", 6375998102bSMark Brown .owner = THIS_MODULE, 638a7f96e4dSMark Brown .of_match_table = wm8731_of_match, 6395998102bSMark Brown }, 6405998102bSMark Brown .probe = wm8731_spi_probe, 6415998102bSMark Brown .remove = __devexit_p(wm8731_spi_remove), 6425998102bSMark Brown }; 643a8035c8fSMark Brown #endif /* CONFIG_SPI_MASTER */ 644a8035c8fSMark Brown 645a8035c8fSMark Brown #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) 646c6f29811SMark Brown static __devinit int wm8731_i2c_probe(struct i2c_client *i2c, 647a8035c8fSMark Brown const struct i2c_device_id *id) 648a8035c8fSMark Brown { 6495998102bSMark Brown struct wm8731_priv *wm8731; 650f0fba2adSLiam Girdwood int ret; 651a8035c8fSMark Brown 6525998102bSMark Brown wm8731 = kzalloc(sizeof(struct wm8731_priv), GFP_KERNEL); 6535998102bSMark Brown if (wm8731 == NULL) 6545998102bSMark Brown return -ENOMEM; 6555998102bSMark Brown 6565998102bSMark Brown i2c_set_clientdata(i2c, wm8731); 657f0fba2adSLiam Girdwood wm8731->control_type = SND_SOC_I2C; 658a8035c8fSMark Brown 659f0fba2adSLiam Girdwood ret = snd_soc_register_codec(&i2c->dev, 660f0fba2adSLiam Girdwood &soc_codec_dev_wm8731, &wm8731_dai, 1); 661f0fba2adSLiam Girdwood if (ret < 0) 662f0fba2adSLiam Girdwood kfree(wm8731); 663f0fba2adSLiam Girdwood return ret; 664a8035c8fSMark Brown } 665a8035c8fSMark Brown 666c6f29811SMark Brown static __devexit int wm8731_i2c_remove(struct i2c_client *client) 667a8035c8fSMark Brown { 668f0fba2adSLiam Girdwood snd_soc_unregister_codec(&client->dev); 669f0fba2adSLiam Girdwood kfree(i2c_get_clientdata(client)); 670a8035c8fSMark Brown return 0; 671a8035c8fSMark Brown } 672a8035c8fSMark Brown 673a8035c8fSMark Brown static const struct i2c_device_id wm8731_i2c_id[] = { 674a8035c8fSMark Brown { "wm8731", 0 }, 675a8035c8fSMark Brown { } 676a8035c8fSMark Brown }; 677a8035c8fSMark Brown MODULE_DEVICE_TABLE(i2c, wm8731_i2c_id); 678a8035c8fSMark Brown 679a8035c8fSMark Brown static struct i2c_driver wm8731_i2c_driver = { 680a8035c8fSMark Brown .driver = { 68199b59f3cSMark Brown .name = "wm8731", 682a8035c8fSMark Brown .owner = THIS_MODULE, 683a7f96e4dSMark Brown .of_match_table = wm8731_of_match, 684a8035c8fSMark Brown }, 685a8035c8fSMark Brown .probe = wm8731_i2c_probe, 686c6f29811SMark Brown .remove = __devexit_p(wm8731_i2c_remove), 687a8035c8fSMark Brown .id_table = wm8731_i2c_id, 688a8035c8fSMark Brown }; 689a8035c8fSMark Brown #endif 690a8035c8fSMark Brown 691c9b3a40fSTakashi Iwai static int __init wm8731_modinit(void) 69264089b84SMark Brown { 693f0fba2adSLiam Girdwood int ret = 0; 6945998102bSMark Brown #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) 6955998102bSMark Brown ret = i2c_add_driver(&wm8731_i2c_driver); 6965998102bSMark Brown if (ret != 0) { 6975998102bSMark Brown printk(KERN_ERR "Failed to register WM8731 I2C driver: %d\n", 6985998102bSMark Brown ret); 6995998102bSMark Brown } 7005998102bSMark Brown #endif 7015998102bSMark Brown #if defined(CONFIG_SPI_MASTER) 7025998102bSMark Brown ret = spi_register_driver(&wm8731_spi_driver); 7035998102bSMark Brown if (ret != 0) { 7045998102bSMark Brown printk(KERN_ERR "Failed to register WM8731 SPI driver: %d\n", 7055998102bSMark Brown ret); 7065998102bSMark Brown } 7075998102bSMark Brown #endif 708f0fba2adSLiam Girdwood return ret; 70964089b84SMark Brown } 71064089b84SMark Brown module_init(wm8731_modinit); 71164089b84SMark Brown 71264089b84SMark Brown static void __exit wm8731_exit(void) 71364089b84SMark Brown { 7145998102bSMark Brown #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) 7155998102bSMark Brown i2c_del_driver(&wm8731_i2c_driver); 7165998102bSMark Brown #endif 7175998102bSMark Brown #if defined(CONFIG_SPI_MASTER) 7185998102bSMark Brown spi_unregister_driver(&wm8731_spi_driver); 7195998102bSMark Brown #endif 72064089b84SMark Brown } 72164089b84SMark Brown module_exit(wm8731_exit); 72264089b84SMark Brown 72340e0aa64SRichard Purdie MODULE_DESCRIPTION("ASoC WM8731 driver"); 72440e0aa64SRichard Purdie MODULE_AUTHOR("Richard Purdie"); 72540e0aa64SRichard Purdie MODULE_LICENSE("GPL"); 726