diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2010-03-01 19:58:44 +0300 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2010-03-01 19:58:44 +0300 |
commit | 524df55725217b13d5a232fb5badb5846418ea0e (patch) | |
tree | c041613577ac7e68737cfd8af16a37d1ed37e6af /sound/soc/codecs/wm8993.c | |
parent | 0f4533979473a456a8adb3869365e12c7a99cf65 (diff) | |
parent | 6679ee1870f7ccdd48e2e5c57919240b8f19a6dc (diff) | |
download | linux-524df55725217b13d5a232fb5badb5846418ea0e.tar.xz |
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound-2.6
* 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound-2.6: (252 commits)
ASoC: Check progress when reporting periods from i.MX FIQ handler
ASoC: Remove a unused variables from i.MX FIQ runtime data
ALSA: hda - Add/fix ALC269 FSC and Quanta models
ALSA: hda - Add ALC670 codec support
OMAP4: PMIC: Add support for twl6030 codec
ALSA: hda - remove unnecessary msleep on power state transitions
usb/gadget/{f_audio,gmidi}.c: follow recent changes in audio.h
ASoC: fsi: Modify over/under run error settlement
ASoC: OMAP4: Add McPDM platform driver
ASoC: OMAP4: Add support for McPDM
ASoC: OMAP: data_type and sync_mode configurable in audio dma
ALSA: hda - Add missing description in HD-Audio-Models.txt
ALSA: add support for Macbook Air 2,1 internal speaker
ALSA: usbaudio: consolidate header files
ALSA: usbmixer: bail out early when parsing audio class v2 descriptors
ALSA: usbaudio: implement basic set of class v2.0 parser
ALSA: usbaudio: introduce new types for audio class v2
ALSA: usbaudio: parse USB descriptors with structs
ALSA: hda - enable snoop for Intel Cougar Point
ALSA: hda - Remove identical definitions for macmini3 model
...
Diffstat (limited to 'sound/soc/codecs/wm8993.c')
-rw-r--r-- | sound/soc/codecs/wm8993.c | 307 |
1 files changed, 194 insertions, 113 deletions
diff --git a/sound/soc/codecs/wm8993.c b/sound/soc/codecs/wm8993.c index 2981afae842c..bf022f68b84f 100644 --- a/sound/soc/codecs/wm8993.c +++ b/sound/soc/codecs/wm8993.c @@ -1,7 +1,7 @@ /* * wm8993.c -- WM8993 ALSA SoC audio driver * - * Copyright 2009 Wolfson Microelectronics plc + * Copyright 2009, 2010 Wolfson Microelectronics plc * * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> * @@ -16,6 +16,7 @@ #include <linux/delay.h> #include <linux/pm.h> #include <linux/i2c.h> +#include <linux/regulator/consumer.h> #include <linux/spi/spi.h> #include <sound/core.h> #include <sound/pcm.h> @@ -29,6 +30,16 @@ #include "wm8993.h" #include "wm_hubs.h" +#define WM8993_NUM_SUPPLIES 6 +static const char *wm8993_supply_names[WM8993_NUM_SUPPLIES] = { + "DCVDD", + "DBVDD", + "AVDD1", + "AVDD2", + "CPVDD", + "SPKVDD", +}; + static u16 wm8993_reg_defaults[WM8993_REGISTER_COUNT] = { 0x8993, /* R0 - Software Reset */ 0x0000, /* R1 - Power Management (1) */ @@ -213,7 +224,9 @@ static struct { }; struct wm8993_priv { + struct wm_hubs_data hubs_data; u16 reg_cache[WM8993_REGISTER_COUNT]; + struct regulator_bulk_data supplies[WM8993_NUM_SUPPLIES]; struct wm8993_platform_data pdata; struct snd_soc_codec codec; int master; @@ -227,36 +240,9 @@ struct wm8993_priv { int class_w_users; unsigned int fll_fref; unsigned int fll_fout; + int fll_src; }; -static unsigned int wm8993_read_hw(struct snd_soc_codec *codec, u8 reg) -{ - struct i2c_msg xfer[2]; - u16 data; - int ret; - struct i2c_client *i2c = codec->control_data; - - /* Write register */ - xfer[0].addr = i2c->addr; - xfer[0].flags = 0; - xfer[0].len = 1; - xfer[0].buf = ® - - /* Read data */ - xfer[1].addr = i2c->addr; - xfer[1].flags = I2C_M_RD; - xfer[1].len = 2; - xfer[1].buf = (u8 *)&data; - - ret = i2c_transfer(i2c->adapter, xfer, 2); - if (ret != 2) { - dev_err(codec->dev, "Failed to read 0x%x: %d\n", reg, ret); - return 0; - } - - return (data >> 8) | ((data & 0xff) << 8); -} - static int wm8993_volatile(unsigned int reg) { switch (reg) { @@ -271,48 +257,6 @@ static int wm8993_volatile(unsigned int reg) } } -static unsigned int wm8993_read(struct snd_soc_codec *codec, - unsigned int reg) -{ - u16 *reg_cache = codec->reg_cache; - - BUG_ON(reg > WM8993_MAX_REGISTER); - - if (wm8993_volatile(reg)) - return wm8993_read_hw(codec, reg); - else - return reg_cache[reg]; -} - -static int wm8993_write(struct snd_soc_codec *codec, unsigned int reg, - unsigned int value) -{ - u16 *reg_cache = codec->reg_cache; - u8 data[3]; - int ret; - - BUG_ON(reg > WM8993_MAX_REGISTER); - - /* data is - * D15..D9 WM8993 register offset - * D8...D0 register data - */ - data[0] = reg; - data[1] = value >> 8; - data[2] = value & 0x00ff; - - if (!wm8993_volatile(reg)) - reg_cache[reg] = value; - - ret = codec->hw_write(codec->control_data, data, 3); - - if (ret == 3) - return 0; - if (ret < 0) - return ret; - return -EIO; -} - struct _fll_div { u16 fll_fratio; u16 fll_outdiv; @@ -441,9 +385,9 @@ static int wm8993_set_fll(struct snd_soc_dai *dai, int fll_id, int source, wm8993->fll_fref = 0; wm8993->fll_fout = 0; - reg1 = wm8993_read(codec, WM8993_FLL_CONTROL_1); + reg1 = snd_soc_read(codec, WM8993_FLL_CONTROL_1); reg1 &= ~WM8993_FLL_ENA; - wm8993_write(codec, WM8993_FLL_CONTROL_1, reg1); + snd_soc_write(codec, WM8993_FLL_CONTROL_1, reg1); return 0; } @@ -452,7 +396,7 @@ static int wm8993_set_fll(struct snd_soc_dai *dai, int fll_id, int source, if (ret != 0) return ret; - reg5 = wm8993_read(codec, WM8993_FLL_CONTROL_5); + reg5 = snd_soc_read(codec, WM8993_FLL_CONTROL_5); reg5 &= ~WM8993_FLL_CLK_SRC_MASK; switch (fll_id) { @@ -474,38 +418,39 @@ static int wm8993_set_fll(struct snd_soc_dai *dai, int fll_id, int source, /* Any FLL configuration change requires that the FLL be * disabled first. */ - reg1 = wm8993_read(codec, WM8993_FLL_CONTROL_1); + reg1 = snd_soc_read(codec, WM8993_FLL_CONTROL_1); reg1 &= ~WM8993_FLL_ENA; - wm8993_write(codec, WM8993_FLL_CONTROL_1, reg1); + snd_soc_write(codec, WM8993_FLL_CONTROL_1, reg1); /* Apply the configuration */ if (fll_div.k) reg1 |= WM8993_FLL_FRAC_MASK; else reg1 &= ~WM8993_FLL_FRAC_MASK; - wm8993_write(codec, WM8993_FLL_CONTROL_1, reg1); + snd_soc_write(codec, WM8993_FLL_CONTROL_1, reg1); - wm8993_write(codec, WM8993_FLL_CONTROL_2, - (fll_div.fll_outdiv << WM8993_FLL_OUTDIV_SHIFT) | - (fll_div.fll_fratio << WM8993_FLL_FRATIO_SHIFT)); - wm8993_write(codec, WM8993_FLL_CONTROL_3, fll_div.k); + snd_soc_write(codec, WM8993_FLL_CONTROL_2, + (fll_div.fll_outdiv << WM8993_FLL_OUTDIV_SHIFT) | + (fll_div.fll_fratio << WM8993_FLL_FRATIO_SHIFT)); + snd_soc_write(codec, WM8993_FLL_CONTROL_3, fll_div.k); - reg4 = wm8993_read(codec, WM8993_FLL_CONTROL_4); + reg4 = snd_soc_read(codec, WM8993_FLL_CONTROL_4); reg4 &= ~WM8993_FLL_N_MASK; reg4 |= fll_div.n << WM8993_FLL_N_SHIFT; - wm8993_write(codec, WM8993_FLL_CONTROL_4, reg4); + snd_soc_write(codec, WM8993_FLL_CONTROL_4, reg4); reg5 &= ~WM8993_FLL_CLK_REF_DIV_MASK; reg5 |= fll_div.fll_clk_ref_div << WM8993_FLL_CLK_REF_DIV_SHIFT; - wm8993_write(codec, WM8993_FLL_CONTROL_5, reg5); + snd_soc_write(codec, WM8993_FLL_CONTROL_5, reg5); /* Enable the FLL */ - wm8993_write(codec, WM8993_FLL_CONTROL_1, reg1 | WM8993_FLL_ENA); + snd_soc_write(codec, WM8993_FLL_CONTROL_1, reg1 | WM8993_FLL_ENA); dev_dbg(codec->dev, "FLL enabled at %dHz->%dHz\n", Fref, Fout); wm8993->fll_fref = Fref; wm8993->fll_fout = Fout; + wm8993->fll_src = source; return 0; } @@ -520,7 +465,7 @@ static int configure_clock(struct snd_soc_codec *codec) case WM8993_SYSCLK_MCLK: dev_dbg(codec->dev, "Using %dHz MCLK\n", wm8993->mclk_rate); - reg = wm8993_read(codec, WM8993_CLOCKING_2); + reg = snd_soc_read(codec, WM8993_CLOCKING_2); reg &= ~(WM8993_MCLK_DIV | WM8993_SYSCLK_SRC); if (wm8993->mclk_rate > 13500000) { reg |= WM8993_MCLK_DIV; @@ -529,14 +474,14 @@ static int configure_clock(struct snd_soc_codec *codec) reg &= ~WM8993_MCLK_DIV; wm8993->sysclk_rate = wm8993->mclk_rate; } - wm8993_write(codec, WM8993_CLOCKING_2, reg); + snd_soc_write(codec, WM8993_CLOCKING_2, reg); break; case WM8993_SYSCLK_FLL: dev_dbg(codec->dev, "Using %dHz FLL clock\n", wm8993->fll_fout); - reg = wm8993_read(codec, WM8993_CLOCKING_2); + reg = snd_soc_read(codec, WM8993_CLOCKING_2); reg |= WM8993_SYSCLK_SRC; if (wm8993->fll_fout > 13500000) { reg |= WM8993_MCLK_DIV; @@ -545,7 +490,7 @@ static int configure_clock(struct snd_soc_codec *codec) reg &= ~WM8993_MCLK_DIV; wm8993->sysclk_rate = wm8993->fll_fout; } - wm8993_write(codec, WM8993_CLOCKING_2, reg); + snd_soc_write(codec, WM8993_CLOCKING_2, reg); break; default: @@ -978,10 +923,33 @@ static const struct snd_soc_dapm_route routes[] = { { "Right Headphone Mux", "DAC", "DACR" }, }; +static void wm8993_cache_restore(struct snd_soc_codec *codec) +{ + u16 *cache = codec->reg_cache; + int i; + + if (!codec->cache_sync) + return; + + /* Reenable hardware writes */ + codec->cache_only = 0; + + /* Restore the register settings */ + for (i = 1; i < WM8993_MAX_REGISTER; i++) { + if (cache[i] == wm8993_reg_defaults[i]) + continue; + snd_soc_write(codec, i, cache[i]); + } + + /* We're in sync again */ + codec->cache_sync = 0; +} + static int wm8993_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { struct wm8993_priv *wm8993 = codec->private_data; + int ret; switch (level) { case SND_SOC_BIAS_ON: @@ -995,6 +963,18 @@ static int wm8993_set_bias_level(struct snd_soc_codec *codec, case SND_SOC_BIAS_STANDBY: if (codec->bias_level == SND_SOC_BIAS_OFF) { + ret = regulator_bulk_enable(ARRAY_SIZE(wm8993->supplies), + wm8993->supplies); + if (ret != 0) + return ret; + + wm8993_cache_restore(codec); + + /* Tune DC servo configuration */ + snd_soc_write(codec, 0x44, 3); + snd_soc_write(codec, 0x56, 3); + snd_soc_write(codec, 0x44, 0); + /* Bring up VMID with fast soft start */ snd_soc_update_bits(codec, WM8993_ANTIPOP2, WM8993_STARTUP_BIAS_ENA | @@ -1042,6 +1022,18 @@ static int wm8993_set_bias_level(struct snd_soc_codec *codec, snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_1, WM8993_VMID_SEL_MASK | WM8993_BIAS_ENA, 0); + +#ifdef CONFIG_REGULATOR + /* Post 2.6.34 we will be able to get a callback when + * the regulators are disabled which we can use but + * for now just assume that the power will be cut if + * the regulator API is in use. + */ + codec->cache_sync = 1; +#endif + + regulator_bulk_disable(ARRAY_SIZE(wm8993->supplies), + wm8993->supplies); break; } @@ -1075,8 +1067,8 @@ static int wm8993_set_dai_fmt(struct snd_soc_dai *dai, { struct snd_soc_codec *codec = dai->codec; struct wm8993_priv *wm8993 = codec->private_data; - unsigned int aif1 = wm8993_read(codec, WM8993_AUDIO_INTERFACE_1); - unsigned int aif4 = wm8993_read(codec, WM8993_AUDIO_INTERFACE_4); + unsigned int aif1 = snd_soc_read(codec, WM8993_AUDIO_INTERFACE_1); + unsigned int aif4 = snd_soc_read(codec, WM8993_AUDIO_INTERFACE_4); aif1 &= ~(WM8993_BCLK_DIR | WM8993_AIF_BCLK_INV | WM8993_AIF_LRCLK_INV | WM8993_AIF_FMT_MASK); @@ -1159,8 +1151,8 @@ static int wm8993_set_dai_fmt(struct snd_soc_dai *dai, return -EINVAL; } - wm8993_write(codec, WM8993_AUDIO_INTERFACE_1, aif1); - wm8993_write(codec, WM8993_AUDIO_INTERFACE_4, aif4); + snd_soc_write(codec, WM8993_AUDIO_INTERFACE_1, aif1); + snd_soc_write(codec, WM8993_AUDIO_INTERFACE_4, aif4); return 0; } @@ -1174,16 +1166,16 @@ static int wm8993_hw_params(struct snd_pcm_substream *substream, int ret, i, best, best_val, cur_val; unsigned int clocking1, clocking3, aif1, aif4; - clocking1 = wm8993_read(codec, WM8993_CLOCKING_1); + clocking1 = snd_soc_read(codec, WM8993_CLOCKING_1); clocking1 &= ~WM8993_BCLK_DIV_MASK; - clocking3 = wm8993_read(codec, WM8993_CLOCKING_3); + clocking3 = snd_soc_read(codec, WM8993_CLOCKING_3); clocking3 &= ~(WM8993_CLK_SYS_RATE_MASK | WM8993_SAMPLE_RATE_MASK); - aif1 = wm8993_read(codec, WM8993_AUDIO_INTERFACE_1); + aif1 = snd_soc_read(codec, WM8993_AUDIO_INTERFACE_1); aif1 &= ~WM8993_AIF_WL_MASK; - aif4 = wm8993_read(codec, WM8993_AUDIO_INTERFACE_4); + aif4 = snd_soc_read(codec, WM8993_AUDIO_INTERFACE_4); aif4 &= ~WM8993_LRCLK_RATE_MASK; /* What BCLK do we need? */ @@ -1276,14 +1268,14 @@ static int wm8993_hw_params(struct snd_pcm_substream *substream, dev_dbg(codec->dev, "LRCLK_RATE is %d\n", wm8993->bclk / wm8993->fs); aif4 |= wm8993->bclk / wm8993->fs; - wm8993_write(codec, WM8993_CLOCKING_1, clocking1); - wm8993_write(codec, WM8993_CLOCKING_3, clocking3); - wm8993_write(codec, WM8993_AUDIO_INTERFACE_1, aif1); - wm8993_write(codec, WM8993_AUDIO_INTERFACE_4, aif4); + snd_soc_write(codec, WM8993_CLOCKING_1, clocking1); + snd_soc_write(codec, WM8993_CLOCKING_3, clocking3); + snd_soc_write(codec, WM8993_AUDIO_INTERFACE_1, aif1); + snd_soc_write(codec, WM8993_AUDIO_INTERFACE_4, aif4); /* ReTune Mobile? */ if (wm8993->pdata.num_retune_configs) { - u16 eq1 = wm8993_read(codec, WM8993_EQ1); + u16 eq1 = snd_soc_read(codec, WM8993_EQ1); struct wm8993_retune_mobile_setting *s; best = 0; @@ -1306,7 +1298,7 @@ static int wm8993_hw_params(struct snd_pcm_substream *substream, snd_soc_update_bits(codec, WM8993_EQ1, WM8993_EQ_ENA, 0); for (i = 1; i < ARRAY_SIZE(s->config); i++) - wm8993_write(codec, WM8993_EQ1 + i, s->config[i]); + snd_soc_write(codec, WM8993_EQ1 + i, s->config[i]); snd_soc_update_bits(codec, WM8993_EQ1, WM8993_EQ_ENA, eq1); } @@ -1319,14 +1311,14 @@ static int wm8993_digital_mute(struct snd_soc_dai *codec_dai, int mute) struct snd_soc_codec *codec = codec_dai->codec; unsigned int reg; - reg = wm8993_read(codec, WM8993_DAC_CTRL); + reg = snd_soc_read(codec, WM8993_DAC_CTRL); if (mute) reg |= WM8993_DAC_MUTE; else reg &= ~WM8993_DAC_MUTE; - wm8993_write(codec, WM8993_DAC_CTRL, reg); + snd_soc_write(codec, WM8993_DAC_CTRL, reg); return 0; } @@ -1480,9 +1472,66 @@ static int wm8993_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_PM +static int wm8993_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + struct wm8993_priv *wm8993 = codec->private_data; + int fll_fout = wm8993->fll_fout; + int fll_fref = wm8993->fll_fref; + int ret; + + /* Stop the FLL in an orderly fashion */ + ret = wm8993_set_fll(codec->dai, 0, 0, 0, 0); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to stop FLL\n"); + return ret; + } + + wm8993->fll_fout = fll_fout; + wm8993->fll_fref = fll_fref; + + wm8993_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +static int wm8993_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + struct wm8993_priv *wm8993 = codec->private_data; + int ret; + + wm8993_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + /* Restart the FLL? */ + if (wm8993->fll_fout) { + int fll_fout = wm8993->fll_fout; + int fll_fref = wm8993->fll_fref; + + wm8993->fll_fref = 0; + wm8993->fll_fout = 0; + + ret = wm8993_set_fll(codec->dai, 0, wm8993->fll_src, + fll_fref, fll_fout); + if (ret != 0) + dev_err(codec->dev, "Failed to restart FLL\n"); + } + + return 0; +} +#else +#define wm8993_suspend NULL +#define wm8993_resume NULL +#endif + struct snd_soc_codec_device soc_codec_dev_wm8993 = { .probe = wm8993_probe, .remove = wm8993_remove, + .suspend = wm8993_suspend, + .resume = wm8993_resume, }; EXPORT_SYMBOL_GPL(soc_codec_dev_wm8993); @@ -1493,6 +1542,7 @@ static int wm8993_i2c_probe(struct i2c_client *i2c, struct snd_soc_codec *codec; unsigned int val; int ret; + int i; if (wm8993_codec) { dev_err(&i2c->dev, "A WM8993 is already registered\n"); @@ -1513,9 +1563,7 @@ static int wm8993_i2c_probe(struct i2c_client *i2c, INIT_LIST_HEAD(&codec->dapm_paths); codec->name = "WM8993"; - codec->read = wm8993_read; - codec->write = wm8993_write; - codec->hw_write = (hw_write_t)i2c_master_send; + codec->volatile_register = wm8993_volatile; codec->reg_cache = wm8993->reg_cache; codec->reg_cache_size = ARRAY_SIZE(wm8993->reg_cache); codec->bias_level = SND_SOC_BIAS_OFF; @@ -1524,25 +1572,53 @@ static int wm8993_i2c_probe(struct i2c_client *i2c, codec->num_dai = 1; codec->private_data = wm8993; + wm8993->hubs_data.hp_startup_mode = 1; + wm8993->hubs_data.dcs_codes = -2; + memcpy(wm8993->reg_cache, wm8993_reg_defaults, sizeof(wm8993->reg_cache)); + ret = snd_soc_codec_set_cache_io(codec, 8, 16, SND_SOC_I2C); + if (ret != 0) { + dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); + goto err; + } + i2c_set_clientdata(i2c, wm8993); codec->control_data = i2c; wm8993_codec = codec; codec->dev = &i2c->dev; - val = wm8993_read_hw(codec, WM8993_SOFTWARE_RESET); + for (i = 0; i < ARRAY_SIZE(wm8993->supplies); i++) + wm8993->supplies[i].supply = wm8993_supply_names[i]; + + ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(wm8993->supplies), + wm8993->supplies); + if (ret != 0) { + dev_err(codec->dev, "Failed to request supplies: %d\n", ret); + goto err; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(wm8993->supplies), + wm8993->supplies); + if (ret != 0) { + dev_err(codec->dev, "Failed to enable supplies: %d\n", ret); + goto err_get; + } + + val = snd_soc_read(codec, WM8993_SOFTWARE_RESET); if (val != wm8993_reg_defaults[WM8993_SOFTWARE_RESET]) { dev_err(codec->dev, "Invalid ID register value %x\n", val); ret = -EINVAL; - goto err; + goto err_enable; } - ret = wm8993_write(codec, WM8993_SOFTWARE_RESET, 0xffff); + ret = snd_soc_write(codec, WM8993_SOFTWARE_RESET, 0xffff); if (ret != 0) - goto err; + goto err_enable; + + codec->cache_only = 1; /* By default we're using the output mixers */ wm8993->class_w_users = 2; @@ -1572,7 +1648,7 @@ static int wm8993_i2c_probe(struct i2c_client *i2c, ret = wm8993_set_bias_level(codec, SND_SOC_BIAS_STANDBY); if (ret != 0) - goto err; + goto err_enable; wm8993_dai.dev = codec->dev; @@ -1586,6 +1662,10 @@ static int wm8993_i2c_probe(struct i2c_client *i2c, err_bias: wm8993_set_bias_level(codec, SND_SOC_BIAS_OFF); +err_enable: + regulator_bulk_disable(ARRAY_SIZE(wm8993->supplies), wm8993->supplies); +err_get: + regulator_bulk_free(ARRAY_SIZE(wm8993->supplies), wm8993->supplies); err: wm8993_codec = NULL; kfree(wm8993); @@ -1600,6 +1680,7 @@ static int wm8993_i2c_remove(struct i2c_client *client) snd_soc_unregister_dai(&wm8993_dai); wm8993_set_bias_level(&wm8993->codec, SND_SOC_BIAS_OFF); + regulator_bulk_free(ARRAY_SIZE(wm8993->supplies), wm8993->supplies); kfree(wm8993); return 0; |