From 38357ab2c83631728afa37a783c9b1bd474a0739 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 6 Jun 2009 19:03:23 +0100 Subject: [PATCH 01/26] ASoC: Sort DAPM power sequences while building lists In the past the DAPM power sequencing was done by iterating over the list of widgets once for each widget type and powering widgets of that type. Instead of doing that do the sorting at the time we insert the widgets into the lists of widgets to apply power changes to. This reduces the amount of computation required for seqencing still further, though the costs are generally dwarfed by the costs of the register writes implementing them. Signed-off-by: Mark Brown --- sound/soc/soc-dapm.c | 109 ++++++++++++++++++++++++++++--------------- 1 file changed, 71 insertions(+), 38 deletions(-) diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 21c69074aa17..1b38e2195596 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -52,19 +52,37 @@ /* dapm power sequences - make this per codec in the future */ static int dapm_up_seq[] = { - snd_soc_dapm_pre, snd_soc_dapm_supply, snd_soc_dapm_micbias, - snd_soc_dapm_mic, snd_soc_dapm_mux, snd_soc_dapm_value_mux, - snd_soc_dapm_dac, snd_soc_dapm_mixer, snd_soc_dapm_mixer_named_ctl, - snd_soc_dapm_pga, snd_soc_dapm_adc, snd_soc_dapm_hp, snd_soc_dapm_spk, - snd_soc_dapm_post + [snd_soc_dapm_pre] = 0, + [snd_soc_dapm_supply] = 1, + [snd_soc_dapm_micbias] = 2, + [snd_soc_dapm_mic] = 3, + [snd_soc_dapm_mux] = 4, + [snd_soc_dapm_value_mux] = 5, + [snd_soc_dapm_dac] = 6, + [snd_soc_dapm_mixer] = 7, + [snd_soc_dapm_mixer_named_ctl] = 8, + [snd_soc_dapm_pga] = 9, + [snd_soc_dapm_adc] = 10, + [snd_soc_dapm_hp] = 11, + [snd_soc_dapm_spk] = 12, + [snd_soc_dapm_post] = 13, }; static int dapm_down_seq[] = { - snd_soc_dapm_pre, snd_soc_dapm_adc, snd_soc_dapm_hp, snd_soc_dapm_spk, - snd_soc_dapm_pga, snd_soc_dapm_mixer_named_ctl, snd_soc_dapm_mixer, - snd_soc_dapm_dac, snd_soc_dapm_mic, snd_soc_dapm_micbias, - snd_soc_dapm_mux, snd_soc_dapm_value_mux, snd_soc_dapm_supply, - snd_soc_dapm_post + [snd_soc_dapm_pre] = 0, + [snd_soc_dapm_adc] = 1, + [snd_soc_dapm_hp] = 2, + [snd_soc_dapm_spk] = 3, + [snd_soc_dapm_pga] = 4, + [snd_soc_dapm_mixer_named_ctl] = 5, + [snd_soc_dapm_mixer] = 6, + [snd_soc_dapm_dac] = 7, + [snd_soc_dapm_mic] = 8, + [snd_soc_dapm_micbias] = 9, + [snd_soc_dapm_mux] = 10, + [snd_soc_dapm_value_mux] = 11, + [snd_soc_dapm_supply] = 12, + [snd_soc_dapm_post] = 13, }; static void pop_wait(u32 pop_time) @@ -738,6 +756,32 @@ static int dapm_power_widget(struct snd_soc_codec *codec, int event, } } +static int dapm_seq_compare(struct snd_soc_dapm_widget *a, + struct snd_soc_dapm_widget *b, + int sort[]) +{ + if (sort[a->id] != sort[b->id]) + return sort[a->id] - sort[b->id]; + + return 0; +} + +/* Insert a widget in order into a DAPM power sequence. */ +static void dapm_seq_insert(struct snd_soc_dapm_widget *new_widget, + struct list_head *list, + int sort[]) +{ + struct snd_soc_dapm_widget *w; + + list_for_each_entry(w, list, power_list) + if (dapm_seq_compare(new_widget, w, sort) < 0) { + list_add_tail(&new_widget->power_list, &w->power_list); + return; + } + + list_add_tail(&new_widget->power_list, list); +} + /* * Scan each dapm widget for complete audio path. * A complete path is a route that has valid endpoints i.e.:- @@ -752,7 +796,7 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) struct snd_soc_device *socdev = codec->socdev; struct snd_soc_dapm_widget *w; int ret = 0; - int i, power; + int power; int sys_power = 0; INIT_LIST_HEAD(&codec->up_list); @@ -764,10 +808,10 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) list_for_each_entry(w, &codec->dapm_widgets, list) { switch (w->id) { case snd_soc_dapm_pre: - list_add_tail(&codec->down_list, &w->power_list); + dapm_seq_insert(w, &codec->down_list, dapm_down_seq); break; case snd_soc_dapm_post: - list_add_tail(&codec->up_list, &w->power_list); + dapm_seq_insert(w, &codec->up_list, dapm_up_seq); break; default: @@ -782,10 +826,11 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) continue; if (power) - list_add_tail(&w->power_list, &codec->up_list); + dapm_seq_insert(w, &codec->up_list, + dapm_up_seq); else - list_add_tail(&w->power_list, - &codec->down_list); + dapm_seq_insert(w, &codec->down_list, + dapm_down_seq); w->power = power; break; @@ -802,31 +847,19 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) } /* Power down widgets first; try to avoid amplifying pops. */ - for (i = 0; i < ARRAY_SIZE(dapm_down_seq); i++) { - list_for_each_entry(w, &codec->down_list, power_list) { - /* is widget in stream order */ - if (w->id != dapm_down_seq[i]) - continue; - - ret = dapm_power_widget(codec, event, w); - if (ret != 0) - pr_err("Failed to power down %s: %d\n", - w->name, ret); - } + list_for_each_entry(w, &codec->down_list, power_list) { + ret = dapm_power_widget(codec, event, w); + if (ret != 0) + pr_err("Failed to power down %s: %d\n", + w->name, ret); } /* Now power up. */ - for (i = 0; i < ARRAY_SIZE(dapm_up_seq); i++) { - list_for_each_entry(w, &codec->up_list, power_list) { - /* is widget in stream order */ - if (w->id != dapm_up_seq[i]) - continue; - - ret = dapm_power_widget(codec, event, w); - if (ret != 0) - pr_err("Failed to power up %s: %d\n", - w->name, ret); - } + list_for_each_entry(w, &codec->up_list, power_list) { + ret = dapm_power_widget(codec, event, w); + if (ret != 0) + pr_err("Failed to power up %s: %d\n", + w->name, ret); } /* If we just powered the last thing off drop to standby bias */ From 163cac061c97394d4ef9c89efe5921dac937ddb8 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sun, 7 Jun 2009 10:12:52 +0100 Subject: [PATCH 02/26] ASoC: Factor out DAPM sequence execution Lump the list walk into a single function, and pull in the power application too so we can do some further refactoring. Pure code motion. Signed-off-by: Mark Brown --- sound/soc/soc-dapm.c | 122 +++++++++++++++++++++---------------------- 1 file changed, 61 insertions(+), 61 deletions(-) diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 1b38e2195596..257d4f15e00e 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -707,55 +707,6 @@ static int dapm_supply_check_power(struct snd_soc_dapm_widget *w) return power; } -/* - * Scan a single DAPM widget for a complete audio path and update the - * power status appropriately. - */ -static int dapm_power_widget(struct snd_soc_codec *codec, int event, - struct snd_soc_dapm_widget *w) -{ - int ret; - - switch (w->id) { - case snd_soc_dapm_pre: - if (!w->event) - return 0; - - if (event == SND_SOC_DAPM_STREAM_START) { - ret = w->event(w, - NULL, SND_SOC_DAPM_PRE_PMU); - if (ret < 0) - return ret; - } else if (event == SND_SOC_DAPM_STREAM_STOP) { - ret = w->event(w, - NULL, SND_SOC_DAPM_PRE_PMD); - if (ret < 0) - return ret; - } - return 0; - - case snd_soc_dapm_post: - if (!w->event) - return 0; - - if (event == SND_SOC_DAPM_STREAM_START) { - ret = w->event(w, - NULL, SND_SOC_DAPM_POST_PMU); - if (ret < 0) - return ret; - } else if (event == SND_SOC_DAPM_STREAM_STOP) { - ret = w->event(w, - NULL, SND_SOC_DAPM_POST_PMD); - if (ret < 0) - return ret; - } - return 0; - - default: - return dapm_generic_apply_power(w); - } -} - static int dapm_seq_compare(struct snd_soc_dapm_widget *a, struct snd_soc_dapm_widget *b, int sort[]) @@ -782,6 +733,65 @@ static void dapm_seq_insert(struct snd_soc_dapm_widget *new_widget, list_add_tail(&new_widget->power_list, list); } +/* Apply a DAPM power sequence */ +static void dapm_seq_run(struct snd_soc_codec *codec, struct list_head *list, + int event) +{ + struct snd_soc_dapm_widget *w; + int ret; + + list_for_each_entry(w, list, power_list) { + switch (w->id) { + case snd_soc_dapm_pre: + if (!w->event) + list_for_each_entry_continue(w, list, + power_list); + + if (event == SND_SOC_DAPM_STREAM_START) { + ret = w->event(w, + NULL, SND_SOC_DAPM_PRE_PMU); + if (ret < 0) + pr_err("PRE widget failed: %d\n", + ret); + } else if (event == SND_SOC_DAPM_STREAM_STOP) { + ret = w->event(w, + NULL, SND_SOC_DAPM_PRE_PMD); + if (ret < 0) + pr_err("PRE widget failed: %d\n", + ret); + } + break; + + case snd_soc_dapm_post: + if (!w->event) + list_for_each_entry_continue(w, list, + power_list); + + if (event == SND_SOC_DAPM_STREAM_START) { + ret = w->event(w, + NULL, SND_SOC_DAPM_POST_PMU); + if (ret < 0) + pr_err("POST widget failed: %d\n", + ret); + } else if (event == SND_SOC_DAPM_STREAM_STOP) { + ret = w->event(w, + NULL, SND_SOC_DAPM_POST_PMD); + if (ret < 0) + pr_err("POST widget failed: %d\n", + ret); + } + break; + + default: + ret = dapm_generic_apply_power(w); + if (ret < 0) + pr_err("Failed to apply widget power: %d\n", + ret); + break; + } + } +} + /* * Scan each dapm widget for complete audio path. * A complete path is a route that has valid endpoints i.e.:- @@ -847,20 +857,10 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) } /* Power down widgets first; try to avoid amplifying pops. */ - list_for_each_entry(w, &codec->down_list, power_list) { - ret = dapm_power_widget(codec, event, w); - if (ret != 0) - pr_err("Failed to power down %s: %d\n", - w->name, ret); - } + dapm_seq_run(codec, &codec->down_list, event); /* Now power up. */ - list_for_each_entry(w, &codec->up_list, power_list) { - ret = dapm_power_widget(codec, event, w); - if (ret != 0) - pr_err("Failed to power up %s: %d\n", - w->name, ret); - } + dapm_seq_run(codec, &codec->up_list, event); /* If we just powered the last thing off drop to standby bias */ if (codec->bias_level == SND_SOC_BIAS_PREPARE && !sys_power) { From 46f5822f7841697d4aedaf4672661d7a765172cd Mon Sep 17 00:00:00 2001 From: Daniel Ribeiro Date: Sun, 7 Jun 2009 02:49:11 -0300 Subject: [PATCH 03/26] ASoC: Allow 32 bit registers for DAPM Replace the remaining unsigned shorts with unsigned ints. Tested with pcap2 codec (25 bits registers). Signed-off-by: Daniel Ribeiro Signed-off-by: Mark Brown --- include/sound/soc.h | 4 ++-- sound/soc/soc-core.c | 28 ++++++++++++++-------------- sound/soc/soc-dapm.c | 16 ++++++++-------- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/include/sound/soc.h b/include/sound/soc.h index cf6111d72b17..a167b4930447 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -216,9 +216,9 @@ void snd_soc_jack_free_gpios(struct snd_soc_jack *jack, int count, /* codec register bit access */ int snd_soc_update_bits(struct snd_soc_codec *codec, unsigned short reg, - unsigned short mask, unsigned short value); + unsigned int mask, unsigned int value); int snd_soc_test_bits(struct snd_soc_codec *codec, unsigned short reg, - unsigned short mask, unsigned short value); + unsigned int mask, unsigned int value); int snd_soc_new_ac97_codec(struct snd_soc_codec *codec, struct snd_ac97_bus_ops *ops, int num); diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 3f44150d8e30..e1a920cd8953 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1264,10 +1264,10 @@ EXPORT_SYMBOL_GPL(snd_soc_free_ac97_codec); * Returns 1 for change else 0. */ int snd_soc_update_bits(struct snd_soc_codec *codec, unsigned short reg, - unsigned short mask, unsigned short value) + unsigned int mask, unsigned int value) { int change; - unsigned short old, new; + unsigned int old, new; mutex_lock(&io_mutex); old = snd_soc_read(codec, reg); @@ -1294,10 +1294,10 @@ EXPORT_SYMBOL_GPL(snd_soc_update_bits); * Returns 1 for change else 0. */ int snd_soc_test_bits(struct snd_soc_codec *codec, unsigned short reg, - unsigned short mask, unsigned short value) + unsigned int mask, unsigned int value) { int change; - unsigned short old, new; + unsigned int old, new; mutex_lock(&io_mutex); old = snd_soc_read(codec, reg); @@ -1583,7 +1583,7 @@ int snd_soc_get_enum_double(struct snd_kcontrol *kcontrol, { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; - unsigned short val, bitmask; + unsigned int val, bitmask; for (bitmask = 1; bitmask < e->max; bitmask <<= 1) ; @@ -1612,8 +1612,8 @@ int snd_soc_put_enum_double(struct snd_kcontrol *kcontrol, { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; - unsigned short val; - unsigned short mask, bitmask; + unsigned int val; + unsigned int mask, bitmask; for (bitmask = 1; bitmask < e->max; bitmask <<= 1) ; @@ -1649,7 +1649,7 @@ int snd_soc_get_value_enum_double(struct snd_kcontrol *kcontrol, { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; - unsigned short reg_val, val, mux; + unsigned int reg_val, val, mux; reg_val = snd_soc_read(codec, e->reg); val = (reg_val >> e->shift_l) & e->mask; @@ -1688,8 +1688,8 @@ int snd_soc_put_value_enum_double(struct snd_kcontrol *kcontrol, { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; - unsigned short val; - unsigned short mask; + unsigned int val; + unsigned int mask; if (ucontrol->value.enumerated.item[0] > e->max - 1) return -EINVAL; @@ -1849,7 +1849,7 @@ int snd_soc_put_volsw(struct snd_kcontrol *kcontrol, int max = mc->max; unsigned int mask = (1 << fls(max)) - 1; unsigned int invert = mc->invert; - unsigned short val, val2, val_mask; + unsigned int val, val2, val_mask; val = (ucontrol->value.integer.value[0] & mask); if (invert) @@ -1915,7 +1915,7 @@ int snd_soc_get_volsw_2r(struct snd_kcontrol *kcontrol, unsigned int reg2 = mc->rreg; unsigned int shift = mc->shift; int max = mc->max; - unsigned int mask = (1<invert; ucontrol->value.integer.value[0] = @@ -1955,7 +1955,7 @@ int snd_soc_put_volsw_2r(struct snd_kcontrol *kcontrol, unsigned int mask = (1 << fls(max)) - 1; unsigned int invert = mc->invert; int err; - unsigned short val, val2, val_mask; + unsigned int val, val2, val_mask; val_mask = mask << shift; val = (ucontrol->value.integer.value[0] & mask); @@ -2047,7 +2047,7 @@ int snd_soc_put_volsw_s8(struct snd_kcontrol *kcontrol, struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); unsigned int reg = mc->reg; int min = mc->min; - unsigned short val; + unsigned int val; val = (ucontrol->value.integer.value[0]+min) & 0xff; val |= ((ucontrol->value.integer.value[1]+min) & 0xff) << 8; diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 21c69074aa17..7ad8afa8553d 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -268,7 +268,7 @@ static int dapm_connect_mixer(struct snd_soc_codec *codec, static int dapm_update_bits(struct snd_soc_dapm_widget *widget) { int change, power; - unsigned short old, new; + unsigned int old, new; struct snd_soc_codec *codec = widget->codec; /* check for valid widgets */ @@ -1372,7 +1372,7 @@ int snd_soc_dapm_put_volsw(struct snd_kcontrol *kcontrol, int max = mc->max; unsigned int mask = (1 << fls(max)) - 1; unsigned int invert = mc->invert; - unsigned short val, val2, val_mask; + unsigned int val, val2, val_mask; int ret; val = (ucontrol->value.integer.value[0] & mask); @@ -1436,7 +1436,7 @@ int snd_soc_dapm_get_enum_double(struct snd_kcontrol *kcontrol, { struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; - unsigned short val, bitmask; + unsigned int val, bitmask; for (bitmask = 1; bitmask < e->max; bitmask <<= 1) ; @@ -1464,8 +1464,8 @@ int snd_soc_dapm_put_enum_double(struct snd_kcontrol *kcontrol, { struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; - unsigned short val, mux; - unsigned short mask, bitmask; + unsigned int val, mux; + unsigned int mask, bitmask; int ret = 0; for (bitmask = 1; bitmask < e->max; bitmask <<= 1) @@ -1523,7 +1523,7 @@ int snd_soc_dapm_get_value_enum_double(struct snd_kcontrol *kcontrol, { struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; - unsigned short reg_val, val, mux; + unsigned int reg_val, val, mux; reg_val = snd_soc_read(widget->codec, e->reg); val = (reg_val >> e->shift_l) & e->mask; @@ -1563,8 +1563,8 @@ int snd_soc_dapm_put_value_enum_double(struct snd_kcontrol *kcontrol, { struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; - unsigned short val, mux; - unsigned short mask; + unsigned int val, mux; + unsigned int mask; int ret = 0; if (ucontrol->value.enumerated.item[0] > e->max - 1) From b22ead2a510fdb30440753f90237e86fdac70fae Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sun, 7 Jun 2009 12:51:26 +0100 Subject: [PATCH 04/26] ASoC: Coalesce register writes for DAPM sequences Reduce the number of register writes we need to set the power state for a CODEC by coalescing updates to widgets with the same sequence order and same register into a single write. This can be a noticable performance improvement with slow or heavily contended control buses, such as I2C controllers with a low clock frequency, and is particularly noticable when resuming. It can also reduce the noticability of and pops and clicks by ensuring that left and right channels are powered simultaneously if they are in the same register. Currently widgets that have events are not coalesced, including PGAs which may use the volume ramping control. Signed-off-by: Mark Brown --- sound/soc/soc-dapm.c | 138 ++++++++++++++++++++++++++++++++----------- 1 file changed, 105 insertions(+), 33 deletions(-) diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 257d4f15e00e..66f07cdfb2f7 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -713,6 +713,8 @@ static int dapm_seq_compare(struct snd_soc_dapm_widget *a, { if (sort[a->id] != sort[b->id]) return sort[a->id] - sort[b->id]; + if (a->reg != b->reg) + return a->reg - b->reg; return 0; } @@ -733,63 +735,133 @@ static void dapm_seq_insert(struct snd_soc_dapm_widget *new_widget, list_add_tail(&new_widget->power_list, list); } -/* Apply a DAPM power sequence */ -static void dapm_seq_run(struct snd_soc_codec *codec, struct list_head *list, - int event) +/* Apply the coalesced changes from a DAPM sequence */ +static void dapm_seq_run_coalesced(struct snd_soc_codec *codec, + struct list_head *pending) { struct snd_soc_dapm_widget *w; + int reg, power; + unsigned int value = 0; + unsigned int mask = 0; + unsigned int cur_mask; + + reg = list_first_entry(pending, struct snd_soc_dapm_widget, + power_list)->reg; + + list_for_each_entry(w, pending, power_list) { + cur_mask = 1 << w->shift; + BUG_ON(reg != w->reg); + + if (w->invert) + power = !w->power; + else + power = w->power; + + mask |= cur_mask; + if (power) + value |= cur_mask; + + pop_dbg(codec->pop_time, + "pop test : Queue %s: reg=0x%x, 0x%x/0x%x\n", + w->name, reg, value, mask); + } + + pop_dbg(codec->pop_time, + "pop test : Applying 0x%x/0x%x to %x in %dms\n", + value, mask, reg, codec->pop_time); + pop_wait(codec->pop_time); + snd_soc_update_bits(codec, reg, mask, value); +} + +/* Apply a DAPM power sequence. + * + * We walk over a pre-sorted list of widgets to apply power to. In + * order to minimise the number of writes to the device required + * multiple widgets will be updated in a single write where possible. + * Currently anything that requires more than a single write is not + * handled. + */ +static void dapm_seq_run(struct snd_soc_codec *codec, struct list_head *list, + int event, int sort[]) +{ + struct snd_soc_dapm_widget *w, *n; + LIST_HEAD(pending); + int cur_sort = -1; + int cur_reg = SND_SOC_NOPM; int ret; - list_for_each_entry(w, list, power_list) { + list_for_each_entry_safe(w, n, list, power_list) { + ret = 0; + + /* Do we need to apply any queued changes? */ + if (sort[w->id] != cur_sort || w->reg != cur_reg) { + if (!list_empty(&pending)) + dapm_seq_run_coalesced(codec, &pending); + + INIT_LIST_HEAD(&pending); + cur_sort = -1; + cur_reg = SND_SOC_NOPM; + } + switch (w->id) { case snd_soc_dapm_pre: if (!w->event) - list_for_each_entry_continue(w, list, - power_list); + list_for_each_entry_safe_continue(w, n, list, + power_list); - if (event == SND_SOC_DAPM_STREAM_START) { + if (event == SND_SOC_DAPM_STREAM_START) ret = w->event(w, NULL, SND_SOC_DAPM_PRE_PMU); - if (ret < 0) - pr_err("PRE widget failed: %d\n", - ret); - } else if (event == SND_SOC_DAPM_STREAM_STOP) { + else if (event == SND_SOC_DAPM_STREAM_STOP) ret = w->event(w, NULL, SND_SOC_DAPM_PRE_PMD); - if (ret < 0) - pr_err("PRE widget failed: %d\n", - ret); - } break; case snd_soc_dapm_post: if (!w->event) - list_for_each_entry_continue(w, list, - power_list); + list_for_each_entry_safe_continue(w, n, list, + power_list); - if (event == SND_SOC_DAPM_STREAM_START) { + if (event == SND_SOC_DAPM_STREAM_START) ret = w->event(w, NULL, SND_SOC_DAPM_POST_PMU); - if (ret < 0) - pr_err("POST widget failed: %d\n", - ret); - } else if (event == SND_SOC_DAPM_STREAM_STOP) { + else if (event == SND_SOC_DAPM_STREAM_STOP) ret = w->event(w, NULL, SND_SOC_DAPM_POST_PMD); - if (ret < 0) - pr_err("POST widget failed: %d\n", - ret); - } + break; + + case snd_soc_dapm_input: + case snd_soc_dapm_output: + case snd_soc_dapm_hp: + case snd_soc_dapm_mic: + case snd_soc_dapm_line: + case snd_soc_dapm_spk: + /* No register support currently */ + case snd_soc_dapm_pga: + /* Don't coalsece these yet due to gain ramping */ + ret = dapm_generic_apply_power(w); break; default: - ret = dapm_generic_apply_power(w); - if (ret < 0) - pr_err("Failed to apply widget power: %d\n", - ret); - break; + /* If there's an event or an invalid register + * then run immediately, otherwise store the + * updates so that we can coalesce. */ + if (w->reg >= 0 && !w->event) { + cur_sort = sort[w->id]; + cur_reg = w->reg; + list_move(&w->power_list, &pending); + } else { + ret = dapm_generic_apply_power(w); + } } + + if (ret < 0) + pr_err("Failed to apply widget power: %d\n", + ret); } + + if (!list_empty(&pending)) + dapm_seq_run_coalesced(codec, &pending); } /* @@ -857,10 +929,10 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) } /* Power down widgets first; try to avoid amplifying pops. */ - dapm_seq_run(codec, &codec->down_list, event); + dapm_seq_run(codec, &codec->down_list, event, dapm_down_seq); /* Now power up. */ - dapm_seq_run(codec, &codec->up_list, event); + dapm_seq_run(codec, &codec->up_list, event, dapm_up_seq); /* If we just powered the last thing off drop to standby bias */ if (codec->bias_level == SND_SOC_BIAS_PREPARE && !sys_power) { From e3d4dabd2d9b74778f6f15a830eb3a0027bb3799 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sun, 7 Jun 2009 13:08:45 +0100 Subject: [PATCH 05/26] ASoC: Sort specialised mixers and muxes together The more flexible value muxes and named mixers don't need to be sorted differently from a power management point of view, they are different only in terms of the control interface and not in terms of seqencing behaviour. Signed-off-by: Mark Brown --- sound/soc/soc-dapm.c | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 66f07cdfb2f7..9187db18a042 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -57,15 +57,15 @@ static int dapm_up_seq[] = { [snd_soc_dapm_micbias] = 2, [snd_soc_dapm_mic] = 3, [snd_soc_dapm_mux] = 4, - [snd_soc_dapm_value_mux] = 5, - [snd_soc_dapm_dac] = 6, - [snd_soc_dapm_mixer] = 7, - [snd_soc_dapm_mixer_named_ctl] = 8, - [snd_soc_dapm_pga] = 9, - [snd_soc_dapm_adc] = 10, - [snd_soc_dapm_hp] = 11, - [snd_soc_dapm_spk] = 12, - [snd_soc_dapm_post] = 13, + [snd_soc_dapm_value_mux] = 4, + [snd_soc_dapm_dac] = 5, + [snd_soc_dapm_mixer] = 6, + [snd_soc_dapm_mixer_named_ctl] = 6, + [snd_soc_dapm_pga] = 7, + [snd_soc_dapm_adc] = 8, + [snd_soc_dapm_hp] = 9, + [snd_soc_dapm_spk] = 10, + [snd_soc_dapm_post] = 11, }; static int dapm_down_seq[] = { @@ -75,14 +75,14 @@ static int dapm_down_seq[] = { [snd_soc_dapm_spk] = 3, [snd_soc_dapm_pga] = 4, [snd_soc_dapm_mixer_named_ctl] = 5, - [snd_soc_dapm_mixer] = 6, - [snd_soc_dapm_dac] = 7, - [snd_soc_dapm_mic] = 8, - [snd_soc_dapm_micbias] = 9, - [snd_soc_dapm_mux] = 10, - [snd_soc_dapm_value_mux] = 11, - [snd_soc_dapm_supply] = 12, - [snd_soc_dapm_post] = 13, + [snd_soc_dapm_mixer] = 5, + [snd_soc_dapm_dac] = 6, + [snd_soc_dapm_mic] = 7, + [snd_soc_dapm_micbias] = 8, + [snd_soc_dapm_mux] = 9, + [snd_soc_dapm_value_mux] = 9, + [snd_soc_dapm_supply] = 10, + [snd_soc_dapm_post] = 11, }; static void pop_wait(u32 pop_time) From 81628103dd8527d99ea39b054a3f002d5859d7c3 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sun, 7 Jun 2009 13:21:24 +0100 Subject: [PATCH 06/26] ASoC: Coalesce power updates for DAPM widgets with events Signed-off-by: Mark Brown --- sound/soc/soc-dapm.c | 76 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 60 insertions(+), 16 deletions(-) diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 9187db18a042..3fc791c28aa8 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -740,7 +740,7 @@ static void dapm_seq_run_coalesced(struct snd_soc_codec *codec, struct list_head *pending) { struct snd_soc_dapm_widget *w; - int reg, power; + int reg, power, ret; unsigned int value = 0; unsigned int mask = 0; unsigned int cur_mask; @@ -764,13 +764,62 @@ static void dapm_seq_run_coalesced(struct snd_soc_codec *codec, pop_dbg(codec->pop_time, "pop test : Queue %s: reg=0x%x, 0x%x/0x%x\n", w->name, reg, value, mask); + + /* power up pre event */ + if (w->power && w->event && + (w->event_flags & SND_SOC_DAPM_PRE_PMU)) { + pop_dbg(codec->pop_time, "pop test : %s PRE_PMU\n", + w->name); + ret = w->event(w, NULL, SND_SOC_DAPM_PRE_PMU); + if (ret < 0) + pr_err("%s: pre event failed: %d\n", + w->name, ret); + } + + /* power down pre event */ + if (!w->power && w->event && + (w->event_flags & SND_SOC_DAPM_PRE_PMD)) { + pop_dbg(codec->pop_time, "pop test : %s PRE_PMD\n", + w->name); + ret = w->event(w, NULL, SND_SOC_DAPM_PRE_PMD); + if (ret < 0) + pr_err("%s: pre event failed: %d\n", + w->name, ret); + } } - pop_dbg(codec->pop_time, - "pop test : Applying 0x%x/0x%x to %x in %dms\n", - value, mask, reg, codec->pop_time); - pop_wait(codec->pop_time); - snd_soc_update_bits(codec, reg, mask, value); + if (reg >= 0) { + pop_dbg(codec->pop_time, + "pop test : Applying 0x%x/0x%x to %x in %dms\n", + value, mask, reg, codec->pop_time); + pop_wait(codec->pop_time); + snd_soc_update_bits(codec, reg, mask, value); + } + + list_for_each_entry(w, pending, power_list) { + /* power up post event */ + if (w->power && w->event && + (w->event_flags & SND_SOC_DAPM_POST_PMU)) { + pop_dbg(codec->pop_time, "pop test : %s POST_PMU\n", + w->name); + ret = w->event(w, + NULL, SND_SOC_DAPM_POST_PMU); + if (ret < 0) + pr_err("%s: post event failed: %d\n", + w->name, ret); + } + + /* power down post event */ + if (!w->power && w->event && + (w->event_flags & SND_SOC_DAPM_POST_PMD)) { + pop_dbg(codec->pop_time, "pop test : %s POST_PMD\n", + w->name); + ret = w->event(w, NULL, SND_SOC_DAPM_POST_PMD); + if (ret < 0) + pr_err("%s: post event failed: %d\n", + w->name, ret); + } + } } /* Apply a DAPM power sequence. @@ -843,16 +892,11 @@ static void dapm_seq_run(struct snd_soc_codec *codec, struct list_head *list, break; default: - /* If there's an event or an invalid register - * then run immediately, otherwise store the - * updates so that we can coalesce. */ - if (w->reg >= 0 && !w->event) { - cur_sort = sort[w->id]; - cur_reg = w->reg; - list_move(&w->power_list, &pending); - } else { - ret = dapm_generic_apply_power(w); - } + /* Queue it up for application */ + cur_sort = sort[w->id]; + cur_reg = w->reg; + list_move(&w->power_list, &pending); + break; } if (ret < 0) From 4f1c1923851f9734c972812121e80a3b04ab3af4 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sun, 7 Jun 2009 13:37:44 +0100 Subject: [PATCH 07/26] ASoC: Coalesce power updates for PGAs Handle gain ramping for PGAs so we can coalesce their power updates too. This is not ideal since we can't cope properly with gain ramping for stereo paths but that was the case without coalescing and gain ramping is relatively infrequently used so the effects are limited. Signed-off-by: Mark Brown --- sound/soc/soc-dapm.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 3fc791c28aa8..7299ce405b2d 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -786,6 +786,10 @@ static void dapm_seq_run_coalesced(struct snd_soc_codec *codec, pr_err("%s: pre event failed: %d\n", w->name, ret); } + + /* Lower PGA volume to reduce pops */ + if (w->id == snd_soc_dapm_pga && !w->power) + dapm_set_pga(w, w->power); } if (reg >= 0) { @@ -797,6 +801,10 @@ static void dapm_seq_run_coalesced(struct snd_soc_codec *codec, } list_for_each_entry(w, pending, power_list) { + /* Raise PGA volume to reduce pops */ + if (w->id == snd_soc_dapm_pga && w->power) + dapm_set_pga(w, w->power); + /* power up post event */ if (w->power && w->event && (w->event_flags & SND_SOC_DAPM_POST_PMU)) { @@ -886,8 +894,6 @@ static void dapm_seq_run(struct snd_soc_codec *codec, struct list_head *list, case snd_soc_dapm_line: case snd_soc_dapm_spk: /* No register support currently */ - case snd_soc_dapm_pga: - /* Don't coalsece these yet due to gain ramping */ ret = dapm_generic_apply_power(w); break; From 291f3bbcacf278726911c713e14cedb71c486b16 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sun, 7 Jun 2009 13:57:17 +0100 Subject: [PATCH 08/26] ASoC: Make DAPM power sequence lists local variables They are now only accessed within dapm_power_widgets() so can be local to that function. Signed-off-by: Mark Brown --- include/sound/soc.h | 2 -- sound/soc/soc-dapm.c | 19 ++++++++----------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/include/sound/soc.h b/include/sound/soc.h index cf6111d72b17..5964dd65bbd3 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -369,8 +369,6 @@ struct snd_soc_codec { enum snd_soc_bias_level bias_level; enum snd_soc_bias_level suspend_bias_level; struct delayed_work delayed_work; - struct list_head up_list; - struct list_head down_list; /* codec DAI's */ struct snd_soc_dai *dai; diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 7299ce405b2d..1c30da1535b5 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -927,23 +927,22 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) { struct snd_soc_device *socdev = codec->socdev; struct snd_soc_dapm_widget *w; + LIST_HEAD(up_list); + LIST_HEAD(down_list); int ret = 0; int power; int sys_power = 0; - INIT_LIST_HEAD(&codec->up_list); - INIT_LIST_HEAD(&codec->down_list); - /* Check which widgets we need to power and store them in * lists indicating if they should be powered up or down. */ list_for_each_entry(w, &codec->dapm_widgets, list) { switch (w->id) { case snd_soc_dapm_pre: - dapm_seq_insert(w, &codec->down_list, dapm_down_seq); + dapm_seq_insert(w, &down_list, dapm_down_seq); break; case snd_soc_dapm_post: - dapm_seq_insert(w, &codec->up_list, dapm_up_seq); + dapm_seq_insert(w, &up_list, dapm_up_seq); break; default: @@ -958,11 +957,9 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) continue; if (power) - dapm_seq_insert(w, &codec->up_list, - dapm_up_seq); + dapm_seq_insert(w, &up_list, dapm_up_seq); else - dapm_seq_insert(w, &codec->down_list, - dapm_down_seq); + dapm_seq_insert(w, &down_list, dapm_down_seq); w->power = power; break; @@ -979,10 +976,10 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) } /* Power down widgets first; try to avoid amplifying pops. */ - dapm_seq_run(codec, &codec->down_list, event, dapm_down_seq); + dapm_seq_run(codec, &down_list, event, dapm_down_seq); /* Now power up. */ - dapm_seq_run(codec, &codec->up_list, event, dapm_up_seq); + dapm_seq_run(codec, &up_list, event, dapm_up_seq); /* If we just powered the last thing off drop to standby bias */ if (codec->bias_level == SND_SOC_BIAS_PREPARE && !sys_power) { From fa44c077eb2a17aa6913feceb493d13f45f3fa25 Mon Sep 17 00:00:00 2001 From: Daniel Ribeiro Date: Wed, 10 Jun 2009 15:23:24 -0300 Subject: [PATCH 09/26] ASoC: remove duplicated code on pxa-ssp.c * We don't need to write the registers twice, remove the first write. * DAIFMT_INV switch is duplicated inside DAIFMT_FORMAT switch, move it out. (This patch is for Mark's for-2.6.32 branch, I have not checked if the code is duplicated on current 2.6.30) Signed-off-by: Daniel Ribeiro Signed-off-by: Mark Brown --- sound/soc/pxa/pxa-ssp.c | 50 +++++++++++++---------------------------- 1 file changed, 15 insertions(+), 35 deletions(-) diff --git a/sound/soc/pxa/pxa-ssp.c b/sound/soc/pxa/pxa-ssp.c index 19c45409d94c..e22c5cef8fec 100644 --- a/sound/soc/pxa/pxa-ssp.c +++ b/sound/soc/pxa/pxa-ssp.c @@ -457,31 +457,27 @@ static int pxa_ssp_set_dai_fmt(struct snd_soc_dai *cpu_dai, return -EINVAL; } - ssp_write_reg(ssp, SSCR0, sscr0); - ssp_write_reg(ssp, SSCR1, sscr1); - ssp_write_reg(ssp, SSPSP, sspsp); + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + sspsp |= SSPSP_SFRMP; + break; + case SND_SOC_DAIFMT_NB_IF: + break; + case SND_SOC_DAIFMT_IB_IF: + sspsp |= SSPSP_SCMODE(2); + break; + case SND_SOC_DAIFMT_IB_NF: + sspsp |= SSPSP_SCMODE(2) | SSPSP_SFRMP; + break; + default: + return -EINVAL; + } switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_I2S: sscr0 |= SSCR0_PSP; sscr1 |= SSCR1_RWOT | SSCR1_TRAIL; - /* See hw_params() */ - switch (fmt & SND_SOC_DAIFMT_INV_MASK) { - case SND_SOC_DAIFMT_NB_NF: - sspsp |= SSPSP_SFRMP; - break; - case SND_SOC_DAIFMT_NB_IF: - break; - case SND_SOC_DAIFMT_IB_IF: - sspsp |= SSPSP_SCMODE(2); - break; - case SND_SOC_DAIFMT_IB_NF: - sspsp |= SSPSP_SCMODE(2) | SSPSP_SFRMP; - break; - default: - return -EINVAL; - } break; case SND_SOC_DAIFMT_DSP_A: @@ -489,22 +485,6 @@ static int pxa_ssp_set_dai_fmt(struct snd_soc_dai *cpu_dai, case SND_SOC_DAIFMT_DSP_B: sscr0 |= SSCR0_MOD | SSCR0_PSP; sscr1 |= SSCR1_TRAIL | SSCR1_RWOT; - - switch (fmt & SND_SOC_DAIFMT_INV_MASK) { - case SND_SOC_DAIFMT_NB_NF: - sspsp |= SSPSP_SFRMP; - break; - case SND_SOC_DAIFMT_NB_IF: - break; - case SND_SOC_DAIFMT_IB_IF: - sspsp |= SSPSP_SCMODE(2); - break; - case SND_SOC_DAIFMT_IB_NF: - sspsp |= SSPSP_SCMODE(2) | SSPSP_SFRMP; - break; - default: - return -EINVAL; - } break; default: From 74dc55ed5b709e4a2a538252f98ea62358df82ce Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 9 Jun 2009 09:55:51 +0100 Subject: [PATCH 10/26] ASoC: Add WM8961 driver The WM8961 is a low power, high quality stereo CODEC designed for portable digital applications with headphone and stereo class D speaker drivers. Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/wm8961.c | 1309 +++++++++++++++++++++++++++++++++++++ sound/soc/codecs/wm8961.h | 866 ++++++++++++++++++++++++ 4 files changed, 2181 insertions(+) create mode 100644 sound/soc/codecs/wm8961.c create mode 100644 sound/soc/codecs/wm8961.h diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index bbc97fd76648..021dbdfa5b92 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -39,6 +39,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_WM8903 if I2C select SND_SOC_WM8940 if I2C select SND_SOC_WM8960 if I2C + select SND_SOC_WM8961 if I2C select SND_SOC_WM8971 if I2C select SND_SOC_WM8988 if SND_SOC_I2C_AND_SPI select SND_SOC_WM8990 if I2C @@ -156,6 +157,9 @@ config SND_SOC_WM8940 config SND_SOC_WM8960 tristate +config SND_SOC_WM8961 + tristate + config SND_SOC_WM8971 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 8b7530546f4d..e520c2b7f0e0 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -27,6 +27,7 @@ snd-soc-wm8900-objs := wm8900.o snd-soc-wm8903-objs := wm8903.o snd-soc-wm8940-objs := wm8940.o snd-soc-wm8960-objs := wm8960.o +snd-soc-wm8961-objs := wm8961.o snd-soc-wm8971-objs := wm8971.o snd-soc-wm8988-objs := wm8988.o snd-soc-wm8990-objs := wm8990.o @@ -65,6 +66,7 @@ obj-$(CONFIG_SND_SOC_WM8903) += snd-soc-wm8903.o obj-$(CONFIG_SND_SOC_WM8971) += snd-soc-wm8971.o obj-$(CONFIG_SND_SOC_WM8940) += snd-soc-wm8940.o obj-$(CONFIG_SND_SOC_WM8960) += snd-soc-wm8960.o +obj-$(CONFIG_SND_SOC_WM8961) += snd-soc-wm8961.o obj-$(CONFIG_SND_SOC_WM8988) += snd-soc-wm8988.o obj-$(CONFIG_SND_SOC_WM8990) += snd-soc-wm8990.o obj-$(CONFIG_SND_SOC_WM9081) += snd-soc-wm9081.o diff --git a/sound/soc/codecs/wm8961.c b/sound/soc/codecs/wm8961.c new file mode 100644 index 000000000000..8e78959ca409 --- /dev/null +++ b/sound/soc/codecs/wm8961.c @@ -0,0 +1,1309 @@ +/* + * wm8961.c -- WM8961 ALSA SoC Audio driver + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Currently unimplemented features: + * - ALC + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm8961.h" + +#define WM8961_MAX_REGISTER 0xFC + +static u16 wm8961_reg_defaults[] = { + 0x009F, /* R0 - Left Input volume */ + 0x009F, /* R1 - Right Input volume */ + 0x0000, /* R2 - LOUT1 volume */ + 0x0000, /* R3 - ROUT1 volume */ + 0x0020, /* R4 - Clocking1 */ + 0x0008, /* R5 - ADC & DAC Control 1 */ + 0x0000, /* R6 - ADC & DAC Control 2 */ + 0x000A, /* R7 - Audio Interface 0 */ + 0x01F4, /* R8 - Clocking2 */ + 0x0000, /* R9 - Audio Interface 1 */ + 0x00FF, /* R10 - Left DAC volume */ + 0x00FF, /* R11 - Right DAC volume */ + 0x0000, /* R12 */ + 0x0000, /* R13 */ + 0x0040, /* R14 - Audio Interface 2 */ + 0x0000, /* R15 - Software Reset */ + 0x0000, /* R16 */ + 0x007B, /* R17 - ALC1 */ + 0x0000, /* R18 - ALC2 */ + 0x0032, /* R19 - ALC3 */ + 0x0000, /* R20 - Noise Gate */ + 0x00C0, /* R21 - Left ADC volume */ + 0x00C0, /* R22 - Right ADC volume */ + 0x0120, /* R23 - Additional control(1) */ + 0x0000, /* R24 - Additional control(2) */ + 0x0000, /* R25 - Pwr Mgmt (1) */ + 0x0000, /* R26 - Pwr Mgmt (2) */ + 0x0000, /* R27 - Additional Control (3) */ + 0x0000, /* R28 - Anti-pop */ + 0x0000, /* R29 */ + 0x005F, /* R30 - Clocking 3 */ + 0x0000, /* R31 */ + 0x0000, /* R32 - ADCL signal path */ + 0x0000, /* R33 - ADCR signal path */ + 0x0000, /* R34 */ + 0x0000, /* R35 */ + 0x0000, /* R36 */ + 0x0000, /* R37 */ + 0x0000, /* R38 */ + 0x0000, /* R39 */ + 0x0000, /* R40 - LOUT2 volume */ + 0x0000, /* R41 - ROUT2 volume */ + 0x0000, /* R42 */ + 0x0000, /* R43 */ + 0x0000, /* R44 */ + 0x0000, /* R45 */ + 0x0000, /* R46 */ + 0x0000, /* R47 - Pwr Mgmt (3) */ + 0x0023, /* R48 - Additional Control (4) */ + 0x0000, /* R49 - Class D Control 1 */ + 0x0000, /* R50 */ + 0x0003, /* R51 - Class D Control 2 */ + 0x0000, /* R52 */ + 0x0000, /* R53 */ + 0x0000, /* R54 */ + 0x0000, /* R55 */ + 0x0106, /* R56 - Clocking 4 */ + 0x0000, /* R57 - DSP Sidetone 0 */ + 0x0000, /* R58 - DSP Sidetone 1 */ + 0x0000, /* R59 */ + 0x0000, /* R60 - DC Servo 0 */ + 0x0000, /* R61 - DC Servo 1 */ + 0x0000, /* R62 */ + 0x015E, /* R63 - DC Servo 3 */ + 0x0010, /* R64 */ + 0x0010, /* R65 - DC Servo 5 */ + 0x0000, /* R66 */ + 0x0001, /* R67 */ + 0x0003, /* R68 - Analogue PGA Bias */ + 0x0000, /* R69 - Analogue HP 0 */ + 0x0060, /* R70 */ + 0x01FB, /* R71 - Analogue HP 2 */ + 0x0000, /* R72 - Charge Pump 1 */ + 0x0065, /* R73 */ + 0x005F, /* R74 */ + 0x0059, /* R75 */ + 0x006B, /* R76 */ + 0x0038, /* R77 */ + 0x000C, /* R78 */ + 0x000A, /* R79 */ + 0x006B, /* R80 */ + 0x0000, /* R81 */ + 0x0000, /* R82 - Charge Pump B */ + 0x0087, /* R83 */ + 0x0000, /* R84 */ + 0x005C, /* R85 */ + 0x0000, /* R86 */ + 0x0000, /* R87 - Write Sequencer 1 */ + 0x0000, /* R88 - Write Sequencer 2 */ + 0x0000, /* R89 - Write Sequencer 3 */ + 0x0000, /* R90 - Write Sequencer 4 */ + 0x0000, /* R91 - Write Sequencer 5 */ + 0x0000, /* R92 - Write Sequencer 6 */ + 0x0000, /* R93 - Write Sequencer 7 */ + 0x0000, /* R94 */ + 0x0000, /* R95 */ + 0x0000, /* R96 */ + 0x0000, /* R97 */ + 0x0000, /* R98 */ + 0x0000, /* R99 */ + 0x0000, /* R100 */ + 0x0000, /* R101 */ + 0x0000, /* R102 */ + 0x0000, /* R103 */ + 0x0000, /* R104 */ + 0x0000, /* R105 */ + 0x0000, /* R106 */ + 0x0000, /* R107 */ + 0x0000, /* R108 */ + 0x0000, /* R109 */ + 0x0000, /* R110 */ + 0x0000, /* R111 */ + 0x0000, /* R112 */ + 0x0000, /* R113 */ + 0x0000, /* R114 */ + 0x0000, /* R115 */ + 0x0000, /* R116 */ + 0x0000, /* R117 */ + 0x0000, /* R118 */ + 0x0000, /* R119 */ + 0x0000, /* R120 */ + 0x0000, /* R121 */ + 0x0000, /* R122 */ + 0x0000, /* R123 */ + 0x0000, /* R124 */ + 0x0000, /* R125 */ + 0x0000, /* R126 */ + 0x0000, /* R127 */ + 0x0000, /* R128 */ + 0x0000, /* R129 */ + 0x0000, /* R130 */ + 0x0000, /* R131 */ + 0x0000, /* R132 */ + 0x0000, /* R133 */ + 0x0000, /* R134 */ + 0x0000, /* R135 */ + 0x0000, /* R136 */ + 0x0000, /* R137 */ + 0x0000, /* R138 */ + 0x0000, /* R139 */ + 0x0000, /* R140 */ + 0x0000, /* R141 */ + 0x0000, /* R142 */ + 0x0000, /* R143 */ + 0x0000, /* R144 */ + 0x0000, /* R145 */ + 0x0000, /* R146 */ + 0x0000, /* R147 */ + 0x0000, /* R148 */ + 0x0000, /* R149 */ + 0x0000, /* R150 */ + 0x0000, /* R151 */ + 0x0000, /* R152 */ + 0x0000, /* R153 */ + 0x0000, /* R154 */ + 0x0000, /* R155 */ + 0x0000, /* R156 */ + 0x0000, /* R157 */ + 0x0000, /* R158 */ + 0x0000, /* R159 */ + 0x0000, /* R160 */ + 0x0000, /* R161 */ + 0x0000, /* R162 */ + 0x0000, /* R163 */ + 0x0000, /* R164 */ + 0x0000, /* R165 */ + 0x0000, /* R166 */ + 0x0000, /* R167 */ + 0x0000, /* R168 */ + 0x0000, /* R169 */ + 0x0000, /* R170 */ + 0x0000, /* R171 */ + 0x0000, /* R172 */ + 0x0000, /* R173 */ + 0x0000, /* R174 */ + 0x0000, /* R175 */ + 0x0000, /* R176 */ + 0x0000, /* R177 */ + 0x0000, /* R178 */ + 0x0000, /* R179 */ + 0x0000, /* R180 */ + 0x0000, /* R181 */ + 0x0000, /* R182 */ + 0x0000, /* R183 */ + 0x0000, /* R184 */ + 0x0000, /* R185 */ + 0x0000, /* R186 */ + 0x0000, /* R187 */ + 0x0000, /* R188 */ + 0x0000, /* R189 */ + 0x0000, /* R190 */ + 0x0000, /* R191 */ + 0x0000, /* R192 */ + 0x0000, /* R193 */ + 0x0000, /* R194 */ + 0x0000, /* R195 */ + 0x0030, /* R196 */ + 0x0006, /* R197 */ + 0x0000, /* R198 */ + 0x0060, /* R199 */ + 0x0000, /* R200 */ + 0x003F, /* R201 */ + 0x0000, /* R202 */ + 0x0000, /* R203 */ + 0x0000, /* R204 */ + 0x0001, /* R205 */ + 0x0000, /* R206 */ + 0x0181, /* R207 */ + 0x0005, /* R208 */ + 0x0008, /* R209 */ + 0x0008, /* R210 */ + 0x0000, /* R211 */ + 0x013B, /* R212 */ + 0x0000, /* R213 */ + 0x0000, /* R214 */ + 0x0000, /* R215 */ + 0x0000, /* R216 */ + 0x0070, /* R217 */ + 0x0000, /* R218 */ + 0x0000, /* R219 */ + 0x0000, /* R220 */ + 0x0000, /* R221 */ + 0x0000, /* R222 */ + 0x0003, /* R223 */ + 0x0000, /* R224 */ + 0x0000, /* R225 */ + 0x0001, /* R226 */ + 0x0008, /* R227 */ + 0x0000, /* R228 */ + 0x0000, /* R229 */ + 0x0000, /* R230 */ + 0x0000, /* R231 */ + 0x0004, /* R232 */ + 0x0000, /* R233 */ + 0x0000, /* R234 */ + 0x0000, /* R235 */ + 0x0000, /* R236 */ + 0x0000, /* R237 */ + 0x0080, /* R238 */ + 0x0000, /* R239 */ + 0x0000, /* R240 */ + 0x0000, /* R241 */ + 0x0000, /* R242 */ + 0x0000, /* R243 */ + 0x0000, /* R244 */ + 0x0052, /* R245 */ + 0x0110, /* R246 */ + 0x0040, /* R247 */ + 0x0000, /* R248 */ + 0x0030, /* R249 */ + 0x0000, /* R250 */ + 0x0000, /* R251 */ + 0x0001, /* R252 - General test 1 */ +}; + +struct wm8961_priv { + struct snd_soc_codec codec; + int sysclk; + u16 reg_cache[WM8961_MAX_REGISTER]; +}; + +static int wm8961_reg_is_volatile(int reg) +{ + switch (reg) { + case WM8961_WRITE_SEQUENCER_7: + case WM8961_DC_SERVO_1: + return 1; + + default: + return 0; + } +} + +static unsigned int wm8961_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + BUG_ON(reg > WM8961_MAX_REGISTER); + return cache[reg]; +} + +static unsigned int wm8961_read_hw(struct snd_soc_codec *codec, u8 reg) +{ + struct i2c_msg xfer[2]; + u16 data; + int ret; + struct i2c_client *client = codec->control_data; + + BUG_ON(reg > WM8961_MAX_REGISTER); + + /* Write register */ + xfer[0].addr = client->addr; + xfer[0].flags = 0; + xfer[0].len = 1; + xfer[0].buf = ® + + /* Read data */ + xfer[1].addr = client->addr; + xfer[1].flags = I2C_M_RD; + xfer[1].len = 2; + xfer[1].buf = (u8 *)&data; + + ret = i2c_transfer(client->adapter, xfer, 2); + if (ret != 2) { + dev_err(&client->dev, "i2c_transfer() returned %d\n", ret); + return 0; + } + + return (data >> 8) | ((data & 0xff) << 8); +} + +static unsigned int wm8961_read(struct snd_soc_codec *codec, unsigned int reg) +{ + if (wm8961_reg_is_volatile(reg)) + return wm8961_read_hw(codec, reg); + else + return wm8961_read_reg_cache(codec, reg); +} + +static int wm8961_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u16 *cache = codec->reg_cache; + u8 data[3]; + + BUG_ON(reg > WM8961_MAX_REGISTER); + + if (!wm8961_reg_is_volatile(reg)) + cache[reg] = value; + + data[0] = reg; + data[1] = value >> 8; + data[2] = value & 0x00ff; + + if (codec->hw_write(codec->control_data, data, 3) == 3) + return 0; + else + return -EIO; +} + +static int wm8961_reset(struct snd_soc_codec *codec) +{ + return wm8961_write(codec, WM8961_SOFTWARE_RESET, 0); +} + +/* + * The headphone output supports special anti-pop sequences giving + * silent power up and power down. + */ +static int wm8961_hp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + u16 hp_reg = wm8961_read(codec, WM8961_ANALOGUE_HP_0); + u16 cp_reg = wm8961_read(codec, WM8961_CHARGE_PUMP_1); + u16 pwr_reg = wm8961_read(codec, WM8961_PWR_MGMT_2); + u16 dcs_reg = wm8961_read(codec, WM8961_DC_SERVO_1); + int timeout = 500; + + if (event & SND_SOC_DAPM_POST_PMU) { + /* Make sure the output is shorted */ + hp_reg &= ~(WM8961_HPR_RMV_SHORT | WM8961_HPL_RMV_SHORT); + wm8961_write(codec, WM8961_ANALOGUE_HP_0, hp_reg); + + /* Enable the charge pump */ + cp_reg |= WM8961_CP_ENA; + wm8961_write(codec, WM8961_CHARGE_PUMP_1, cp_reg); + mdelay(5); + + /* Enable the PGA */ + pwr_reg |= WM8961_LOUT1_PGA | WM8961_ROUT1_PGA; + wm8961_write(codec, WM8961_PWR_MGMT_2, pwr_reg); + + /* Enable the amplifier */ + hp_reg |= WM8961_HPR_ENA | WM8961_HPL_ENA; + wm8961_write(codec, WM8961_ANALOGUE_HP_0, hp_reg); + + /* Second stage enable */ + hp_reg |= WM8961_HPR_ENA_DLY | WM8961_HPL_ENA_DLY; + wm8961_write(codec, WM8961_ANALOGUE_HP_0, hp_reg); + + /* Enable the DC servo & trigger startup */ + dcs_reg |= + WM8961_DCS_ENA_CHAN_HPR | WM8961_DCS_TRIG_STARTUP_HPR | + WM8961_DCS_ENA_CHAN_HPL | WM8961_DCS_TRIG_STARTUP_HPL; + dev_dbg(codec->dev, "Enabling DC servo\n"); + + wm8961_write(codec, WM8961_DC_SERVO_1, dcs_reg); + do { + msleep(1); + dcs_reg = wm8961_read(codec, WM8961_DC_SERVO_1); + } while (--timeout && + dcs_reg & (WM8961_DCS_TRIG_STARTUP_HPR | + WM8961_DCS_TRIG_STARTUP_HPL)); + if (dcs_reg & (WM8961_DCS_TRIG_STARTUP_HPR | + WM8961_DCS_TRIG_STARTUP_HPL)) + dev_err(codec->dev, "DC servo timed out\n"); + else + dev_dbg(codec->dev, "DC servo startup complete\n"); + + /* Enable the output stage */ + hp_reg |= WM8961_HPR_ENA_OUTP | WM8961_HPL_ENA_OUTP; + wm8961_write(codec, WM8961_ANALOGUE_HP_0, hp_reg); + + /* Remove the short on the output stage */ + hp_reg |= WM8961_HPR_RMV_SHORT | WM8961_HPL_RMV_SHORT; + wm8961_write(codec, WM8961_ANALOGUE_HP_0, hp_reg); + } + + if (event & SND_SOC_DAPM_PRE_PMD) { + /* Short the output */ + hp_reg &= ~(WM8961_HPR_RMV_SHORT | WM8961_HPL_RMV_SHORT); + wm8961_write(codec, WM8961_ANALOGUE_HP_0, hp_reg); + + /* Disable the output stage */ + hp_reg &= ~(WM8961_HPR_ENA_OUTP | WM8961_HPL_ENA_OUTP); + wm8961_write(codec, WM8961_ANALOGUE_HP_0, hp_reg); + + /* Disable DC offset cancellation */ + dcs_reg &= ~(WM8961_DCS_ENA_CHAN_HPR | + WM8961_DCS_ENA_CHAN_HPL); + wm8961_write(codec, WM8961_DC_SERVO_1, dcs_reg); + + /* Finish up */ + hp_reg &= ~(WM8961_HPR_ENA_DLY | WM8961_HPR_ENA | + WM8961_HPL_ENA_DLY | WM8961_HPL_ENA); + wm8961_write(codec, WM8961_ANALOGUE_HP_0, hp_reg); + + /* Disable the PGA */ + pwr_reg &= ~(WM8961_LOUT1_PGA | WM8961_ROUT1_PGA); + wm8961_write(codec, WM8961_PWR_MGMT_2, pwr_reg); + + /* Disable the charge pump */ + dev_dbg(codec->dev, "Disabling charge pump\n"); + wm8961_write(codec, WM8961_CHARGE_PUMP_1, + cp_reg & ~WM8961_CP_ENA); + } + + return 0; +} + +static int wm8961_spk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + u16 pwr_reg = wm8961_read(codec, WM8961_PWR_MGMT_2); + u16 spk_reg = wm8961_read(codec, WM8961_CLASS_D_CONTROL_1); + + if (event & SND_SOC_DAPM_POST_PMU) { + /* Enable the PGA */ + pwr_reg |= WM8961_SPKL_PGA | WM8961_SPKR_PGA; + wm8961_write(codec, WM8961_PWR_MGMT_2, pwr_reg); + + /* Enable the amplifier */ + spk_reg |= WM8961_SPKL_ENA | WM8961_SPKR_ENA; + wm8961_write(codec, WM8961_CLASS_D_CONTROL_1, spk_reg); + } + + if (event & SND_SOC_DAPM_PRE_PMD) { + /* Enable the amplifier */ + spk_reg &= ~(WM8961_SPKL_ENA | WM8961_SPKR_ENA); + wm8961_write(codec, WM8961_CLASS_D_CONTROL_1, spk_reg); + + /* Enable the PGA */ + pwr_reg &= ~(WM8961_SPKL_PGA | WM8961_SPKR_PGA); + wm8961_write(codec, WM8961_PWR_MGMT_2, pwr_reg); + } + + return 0; +} + +static const char *adc_hpf_text[] = { + "Hi-fi", "Voice 1", "Voice 2", "Voice 3", +}; + +static const struct soc_enum adc_hpf = + SOC_ENUM_SINGLE(WM8961_ADC_DAC_CONTROL_2, 7, 4, adc_hpf_text); + +static const char *dac_deemph_text[] = { + "None", "32kHz", "44.1kHz", "48kHz", +}; + +static const struct soc_enum dac_deemph = + SOC_ENUM_SINGLE(WM8961_ADC_DAC_CONTROL_1, 1, 4, dac_deemph_text); + +static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1); +static const DECLARE_TLV_DB_SCALE(hp_sec_tlv, -700, 100, 0); +static const DECLARE_TLV_DB_SCALE(adc_tlv, -7200, 75, 1); +static const DECLARE_TLV_DB_SCALE(sidetone_tlv, -3600, 300, 0); +static unsigned int boost_tlv[] = { + TLV_DB_RANGE_HEAD(4), + 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0), + 1, 1, TLV_DB_SCALE_ITEM(13, 0, 0), + 2, 2, TLV_DB_SCALE_ITEM(20, 0, 0), + 3, 3, TLV_DB_SCALE_ITEM(29, 0, 0), +}; +static const DECLARE_TLV_DB_SCALE(pga_tlv, -2325, 75, 0); + +static const struct snd_kcontrol_new wm8961_snd_controls[] = { +SOC_DOUBLE_R_TLV("Headphone Volume", WM8961_LOUT1_VOLUME, WM8961_ROUT1_VOLUME, + 0, 127, 0, out_tlv), +SOC_DOUBLE_TLV("Headphone Secondary Volume", WM8961_ANALOGUE_HP_2, + 6, 3, 7, 0, hp_sec_tlv), +SOC_DOUBLE_R("Headphone ZC Switch", WM8961_LOUT1_VOLUME, WM8961_ROUT1_VOLUME, + 7, 1, 0), + +SOC_DOUBLE_R_TLV("Speaker Volume", WM8961_LOUT2_VOLUME, WM8961_ROUT2_VOLUME, + 0, 127, 0, out_tlv), +SOC_DOUBLE_R("Speaker ZC Switch", WM8961_LOUT2_VOLUME, WM8961_ROUT2_VOLUME, + 7, 1, 0), +SOC_SINGLE("Speaker AC Gain", WM8961_CLASS_D_CONTROL_2, 0, 7, 0), + +SOC_SINGLE("DAC x128 OSR Switch", WM8961_ADC_DAC_CONTROL_2, 0, 1, 0), +SOC_ENUM("DAC Deemphasis", dac_deemph), +SOC_SINGLE("DAC Soft Mute Switch", WM8961_ADC_DAC_CONTROL_2, 3, 1, 0), + +SOC_DOUBLE_R_TLV("Sidetone Volume", WM8961_DSP_SIDETONE_0, + WM8961_DSP_SIDETONE_1, 4, 12, 0, sidetone_tlv), + +SOC_SINGLE("ADC High Pass Filter Switch", WM8961_ADC_DAC_CONTROL_1, 0, 1, 0), +SOC_ENUM("ADC High Pass Filter Mode", adc_hpf), + +SOC_DOUBLE_R_TLV("Capture Volume", + WM8961_LEFT_ADC_VOLUME, WM8961_RIGHT_ADC_VOLUME, + 1, 119, 0, adc_tlv), +SOC_DOUBLE_R_TLV("Capture Boost Volume", + WM8961_ADCL_SIGNAL_PATH, WM8961_ADCR_SIGNAL_PATH, + 4, 3, 0, boost_tlv), +SOC_DOUBLE_R_TLV("Capture PGA Volume", + WM8961_LEFT_INPUT_VOLUME, WM8961_RIGHT_INPUT_VOLUME, + 0, 62, 0, pga_tlv), +SOC_DOUBLE_R("Capture PGA ZC Switch", + WM8961_LEFT_INPUT_VOLUME, WM8961_RIGHT_INPUT_VOLUME, + 6, 1, 1), +SOC_DOUBLE_R("Capture PGA Switch", + WM8961_LEFT_INPUT_VOLUME, WM8961_RIGHT_INPUT_VOLUME, + 7, 1, 1), +}; + +static const char *sidetone_text[] = { + "None", "Left", "Right" +}; + +static const struct soc_enum dacl_sidetone = + SOC_ENUM_SINGLE(WM8961_DSP_SIDETONE_0, 2, 3, sidetone_text); + +static const struct soc_enum dacr_sidetone = + SOC_ENUM_SINGLE(WM8961_DSP_SIDETONE_1, 2, 3, sidetone_text); + +static const struct snd_kcontrol_new dacl_mux = + SOC_DAPM_ENUM("DACL Sidetone", dacl_sidetone); + +static const struct snd_kcontrol_new dacr_mux = + SOC_DAPM_ENUM("DACR Sidetone", dacr_sidetone); + +static const struct snd_soc_dapm_widget wm8961_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("LINPUT"), +SND_SOC_DAPM_INPUT("RINPUT"), + +SND_SOC_DAPM_SUPPLY("CLK_DSP", WM8961_CLOCKING2, 4, 0, NULL, 0), + +SND_SOC_DAPM_PGA("Left Input", WM8961_PWR_MGMT_1, 5, 0, NULL, 0), +SND_SOC_DAPM_PGA("Right Input", WM8961_PWR_MGMT_1, 4, 0, NULL, 0), + +SND_SOC_DAPM_ADC("ADCL", "HiFi Capture", WM8961_PWR_MGMT_1, 3, 0), +SND_SOC_DAPM_ADC("ADCR", "HiFi Capture", WM8961_PWR_MGMT_1, 2, 0), + +SND_SOC_DAPM_MICBIAS("MICBIAS", WM8961_PWR_MGMT_1, 1, 0), + +SND_SOC_DAPM_MUX("DACL Sidetone", SND_SOC_NOPM, 0, 0, &dacl_mux), +SND_SOC_DAPM_MUX("DACR Sidetone", SND_SOC_NOPM, 0, 0, &dacr_mux), + +SND_SOC_DAPM_DAC("DACL", "HiFi Playback", WM8961_PWR_MGMT_2, 8, 0), +SND_SOC_DAPM_DAC("DACR", "HiFi Playback", WM8961_PWR_MGMT_2, 7, 0), + +/* Handle as a mono path for DCS */ +SND_SOC_DAPM_PGA_E("Headphone Output", SND_SOC_NOPM, + 4, 0, NULL, 0, wm8961_hp_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), +SND_SOC_DAPM_PGA_E("Speaker Output", SND_SOC_NOPM, + 4, 0, NULL, 0, wm8961_spk_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + +SND_SOC_DAPM_OUTPUT("HP_L"), +SND_SOC_DAPM_OUTPUT("HP_R"), +SND_SOC_DAPM_OUTPUT("SPK_LN"), +SND_SOC_DAPM_OUTPUT("SPK_LP"), +SND_SOC_DAPM_OUTPUT("SPK_RN"), +SND_SOC_DAPM_OUTPUT("SPK_RP"), +}; + + +static const struct snd_soc_dapm_route audio_paths[] = { + { "DACL", NULL, "CLK_DSP" }, + { "DACL", NULL, "DACL Sidetone" }, + { "DACR", NULL, "CLK_DSP" }, + { "DACR", NULL, "DACR Sidetone" }, + + { "DACL Sidetone", "Left", "ADCL" }, + { "DACL Sidetone", "Right", "ADCR" }, + + { "DACR Sidetone", "Left", "ADCL" }, + { "DACR Sidetone", "Right", "ADCR" }, + + { "HP_L", NULL, "Headphone Output" }, + { "HP_R", NULL, "Headphone Output" }, + { "Headphone Output", NULL, "DACL" }, + { "Headphone Output", NULL, "DACR" }, + + { "SPK_LN", NULL, "Speaker Output" }, + { "SPK_LP", NULL, "Speaker Output" }, + { "SPK_RN", NULL, "Speaker Output" }, + { "SPK_RP", NULL, "Speaker Output" }, + + { "Speaker Output", NULL, "DACL" }, + { "Speaker Output", NULL, "DACR" }, + + { "ADCL", NULL, "Left Input" }, + { "ADCL", NULL, "CLK_DSP" }, + { "ADCR", NULL, "Right Input" }, + { "ADCR", NULL, "CLK_DSP" }, + + { "Left Input", NULL, "LINPUT" }, + { "Right Input", NULL, "RINPUT" }, + +}; + +/* Values for CLK_SYS_RATE */ +static struct { + int ratio; + u16 val; +} wm8961_clk_sys_ratio[] = { + { 64, 0 }, + { 128, 1 }, + { 192, 2 }, + { 256, 3 }, + { 384, 4 }, + { 512, 5 }, + { 768, 6 }, + { 1024, 7 }, + { 1408, 8 }, + { 1536, 9 }, +}; + +/* Values for SAMPLE_RATE */ +static struct { + int rate; + u16 val; +} wm8961_srate[] = { + { 48000, 0 }, + { 44100, 0 }, + { 32000, 1 }, + { 22050, 2 }, + { 24000, 2 }, + { 16000, 3 }, + { 11250, 4 }, + { 12000, 4 }, + { 8000, 5 }, +}; + +static int wm8961_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; + struct wm8961_priv *wm8961 = codec->private_data; + int i, best, target, fs; + u16 reg; + + fs = params_rate(params); + + if (!wm8961->sysclk) { + dev_err(codec->dev, "MCLK has not been specified\n"); + return -EINVAL; + } + + /* Find the closest sample rate for the filters */ + best = 0; + for (i = 0; i < ARRAY_SIZE(wm8961_srate); i++) { + if (abs(wm8961_srate[i].rate - fs) < + abs(wm8961_srate[best].rate - fs)) + best = i; + } + reg = wm8961_read(codec, WM8961_ADDITIONAL_CONTROL_3); + reg &= ~WM8961_SAMPLE_RATE_MASK; + reg |= wm8961_srate[best].val; + wm8961_write(codec, WM8961_ADDITIONAL_CONTROL_3, reg); + dev_dbg(codec->dev, "Selected SRATE %dHz for %dHz\n", + wm8961_srate[best].rate, fs); + + /* Select a CLK_SYS/fs ratio equal to or higher than required */ + target = wm8961->sysclk / fs; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && target < 64) { + dev_err(codec->dev, + "SYSCLK must be at least 64*fs for DAC\n"); + return -EINVAL; + } + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE && target < 256) { + dev_err(codec->dev, + "SYSCLK must be at least 256*fs for ADC\n"); + return -EINVAL; + } + + for (i = 0; i < ARRAY_SIZE(wm8961_clk_sys_ratio); i++) { + if (wm8961_clk_sys_ratio[i].ratio >= target) + break; + } + if (i == ARRAY_SIZE(wm8961_clk_sys_ratio)) { + dev_err(codec->dev, "Unable to generate CLK_SYS_RATE\n"); + return -EINVAL; + } + dev_dbg(codec->dev, "Selected CLK_SYS_RATE of %d for %d/%d=%d\n", + wm8961_clk_sys_ratio[i].ratio, wm8961->sysclk, fs, + wm8961->sysclk / fs); + + reg = wm8961_read(codec, WM8961_CLOCKING_4); + reg &= ~WM8961_CLK_SYS_RATE_MASK; + reg |= wm8961_clk_sys_ratio[i].val << WM8961_CLK_SYS_RATE_SHIFT; + wm8961_write(codec, WM8961_CLOCKING_4, reg); + + reg = wm8961_read(codec, WM8961_AUDIO_INTERFACE_0); + reg &= ~WM8961_WL_MASK; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + reg |= 1 << WM8961_WL_SHIFT; + break; + case SNDRV_PCM_FORMAT_S24_LE: + reg |= 2 << WM8961_WL_SHIFT; + break; + case SNDRV_PCM_FORMAT_S32_LE: + reg |= 3 << WM8961_WL_SHIFT; + break; + default: + return -EINVAL; + } + wm8961_write(codec, WM8961_AUDIO_INTERFACE_0, reg); + + /* Sloping stop-band filter is recommended for <= 24kHz */ + reg = wm8961_read(codec, WM8961_ADC_DAC_CONTROL_2); + if (fs <= 24000) + reg |= WM8961_DACSLOPE; + else + reg &= WM8961_DACSLOPE; + wm8961_write(codec, WM8961_ADC_DAC_CONTROL_2, reg); + + return 0; +} + +static int wm8961_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, + int dir) +{ + struct snd_soc_codec *codec = dai->codec; + struct wm8961_priv *wm8961 = codec->private_data; + u16 reg = wm8961_read(codec, WM8961_CLOCKING1); + + if (freq > 33000000) { + dev_err(codec->dev, "MCLK must be <33MHz\n"); + return -EINVAL; + } + + if (freq > 16500000) { + dev_dbg(codec->dev, "Using MCLK/2 for %dHz MCLK\n", freq); + reg |= WM8961_MCLKDIV; + freq /= 2; + } else { + dev_dbg(codec->dev, "Using MCLK/1 for %dHz MCLK\n", freq); + reg &= WM8961_MCLKDIV; + } + + wm8961_write(codec, WM8961_CLOCKING1, reg); + + wm8961->sysclk = freq; + + return 0; +} + +static int wm8961_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = dai->codec; + u16 aif = wm8961_read(codec, WM8961_AUDIO_INTERFACE_0); + + aif &= ~(WM8961_BCLKINV | WM8961_LRP | + WM8961_MS | WM8961_FORMAT_MASK); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + aif |= WM8961_MS; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_RIGHT_J: + break; + + case SND_SOC_DAIFMT_LEFT_J: + aif |= 1; + break; + + case SND_SOC_DAIFMT_I2S: + aif |= 2; + break; + + case SND_SOC_DAIFMT_DSP_B: + aif |= WM8961_LRP; + case SND_SOC_DAIFMT_DSP_A: + aif |= 3; + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + case SND_SOC_DAIFMT_IB_NF: + break; + default: + return -EINVAL; + } + break; + + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_NB_IF: + aif |= WM8961_LRP; + break; + case SND_SOC_DAIFMT_IB_NF: + aif |= WM8961_BCLKINV; + break; + case SND_SOC_DAIFMT_IB_IF: + aif |= WM8961_BCLKINV | WM8961_LRP; + break; + default: + return -EINVAL; + } + + return wm8961_write(codec, WM8961_AUDIO_INTERFACE_0, aif); +} + +static int wm8961_set_tristate(struct snd_soc_dai *dai, int tristate) +{ + struct snd_soc_codec *codec = dai->codec; + u16 reg = wm8961_read(codec, WM8961_ADDITIONAL_CONTROL_2); + + if (tristate) + reg |= WM8961_TRIS; + else + reg &= ~WM8961_TRIS; + + return wm8961_write(codec, WM8961_ADDITIONAL_CONTROL_2, reg); +} + +static int wm8961_digital_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 reg = wm8961_read(codec, WM8961_ADC_DAC_CONTROL_1); + + if (mute) + reg |= WM8961_DACMU; + else + reg &= ~WM8961_DACMU; + + msleep(17); + + return wm8961_write(codec, WM8961_ADC_DAC_CONTROL_1, reg); +} + +static int wm8961_set_clkdiv(struct snd_soc_dai *dai, int div_id, int div) +{ + struct snd_soc_codec *codec = dai->codec; + u16 reg; + + switch (div_id) { + case WM8961_BCLK: + reg = wm8961_read(codec, WM8961_CLOCKING2); + reg &= ~WM8961_BCLKDIV_MASK; + reg |= div; + wm8961_write(codec, WM8961_CLOCKING2, reg); + break; + + case WM8961_LRCLK: + reg = wm8961_read(codec, WM8961_AUDIO_INTERFACE_2); + reg &= ~WM8961_LRCLK_RATE_MASK; + reg |= div; + wm8961_write(codec, WM8961_AUDIO_INTERFACE_2, reg); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int wm8961_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 reg; + + /* This is all slightly unusual since we have no bypass paths + * and the output amplifier structure means we can just slam + * the biases straight up rather than having to ramp them + * slowly. + */ + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + if (codec->bias_level == SND_SOC_BIAS_STANDBY) { + /* Enable bias generation */ + reg = wm8961_read(codec, WM8961_ANTI_POP); + reg |= WM8961_BUFIOEN | WM8961_BUFDCOPEN; + wm8961_write(codec, WM8961_ANTI_POP, reg); + + /* VMID=2*50k, VREF */ + reg = wm8961_read(codec, WM8961_PWR_MGMT_1); + reg &= ~WM8961_VMIDSEL_MASK; + reg |= (1 << WM8961_VMIDSEL_SHIFT) | WM8961_VREF; + wm8961_write(codec, WM8961_PWR_MGMT_1, reg); + } + break; + + case SND_SOC_BIAS_STANDBY: + if (codec->bias_level == SND_SOC_BIAS_PREPARE) { + /* VREF off */ + reg = wm8961_read(codec, WM8961_PWR_MGMT_1); + reg &= ~WM8961_VREF; + wm8961_write(codec, WM8961_PWR_MGMT_1, reg); + + /* Bias generation off */ + reg = wm8961_read(codec, WM8961_ANTI_POP); + reg &= ~(WM8961_BUFIOEN | WM8961_BUFDCOPEN); + wm8961_write(codec, WM8961_ANTI_POP, reg); + + /* VMID off */ + reg = wm8961_read(codec, WM8961_PWR_MGMT_1); + reg &= ~WM8961_VMIDSEL_MASK; + wm8961_write(codec, WM8961_PWR_MGMT_1, reg); + } + break; + + case SND_SOC_BIAS_OFF: + break; + } + + codec->bias_level = level; + + return 0; +} + + +#define WM8961_RATES SNDRV_PCM_RATE_8000_48000 + +#define WM8961_FORMATS \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +static struct snd_soc_dai_ops wm8961_dai_ops = { + .hw_params = wm8961_hw_params, + .set_sysclk = wm8961_set_sysclk, + .set_fmt = wm8961_set_fmt, + .digital_mute = wm8961_digital_mute, + .set_tristate = wm8961_set_tristate, + .set_clkdiv = wm8961_set_clkdiv, +}; + +struct snd_soc_dai wm8961_dai = { + .name = "WM8961", + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8961_RATES, + .formats = WM8961_FORMATS,}, + .capture = { + .stream_name = "HiFi Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8961_RATES, + .formats = WM8961_FORMATS,}, + .ops = &wm8961_dai_ops, +}; +EXPORT_SYMBOL_GPL(wm8961_dai); + + +static struct snd_soc_codec *wm8961_codec; + +static int wm8961_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + if (wm8961_codec == NULL) { + dev_err(&pdev->dev, "Codec device not registered\n"); + return -ENODEV; + } + + socdev->card->codec = wm8961_codec; + codec = wm8961_codec; + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(codec->dev, "failed to create pcms: %d\n", ret); + goto pcm_err; + } + + snd_soc_add_controls(codec, wm8961_snd_controls, + ARRAY_SIZE(wm8961_snd_controls)); + snd_soc_dapm_new_controls(codec, wm8961_dapm_widgets, + ARRAY_SIZE(wm8961_dapm_widgets)); + snd_soc_dapm_add_routes(codec, audio_paths, ARRAY_SIZE(audio_paths)); + snd_soc_dapm_new_widgets(codec); + + ret = snd_soc_init_card(socdev); + if (ret < 0) { + dev_err(codec->dev, "failed to register card: %d\n", ret); + goto card_err; + } + + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + return ret; +} + +static int wm8961_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + + return 0; +} + +#ifdef CONFIG_PM +static int wm8961_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; + + wm8961_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +static int wm8961_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + u16 *reg_cache = codec->reg_cache; + int i; + + for (i = 0; i < codec->reg_cache_size; i++) { + if (i == WM8961_SOFTWARE_RESET) + continue; + + wm8961_write(codec, i, reg_cache[i]); + } + + wm8961_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + return 0; +} +#else +#define wm8961_suspend NULL +#define wm8961_resume NULL +#endif + +struct snd_soc_codec_device soc_codec_dev_wm8961 = { + .probe = wm8961_probe, + .remove = wm8961_remove, + .suspend = wm8961_suspend, + .resume = wm8961_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8961); + +static int wm8961_register(struct wm8961_priv *wm8961) +{ + struct snd_soc_codec *codec = &wm8961->codec; + int ret; + u16 reg; + + if (wm8961_codec) { + dev_err(codec->dev, "Another WM8961 is registered\n"); + ret = -EINVAL; + goto err; + } + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + codec->private_data = wm8961; + codec->name = "WM8961"; + codec->owner = THIS_MODULE; + codec->read = wm8961_read; + codec->write = wm8961_write; + codec->dai = &wm8961_dai; + codec->num_dai = 1; + codec->reg_cache_size = ARRAY_SIZE(wm8961->reg_cache); + codec->reg_cache = &wm8961->reg_cache; + codec->bias_level = SND_SOC_BIAS_OFF; + codec->set_bias_level = wm8961_set_bias_level; + + memcpy(codec->reg_cache, wm8961_reg_defaults, + sizeof(wm8961_reg_defaults)); + + reg = wm8961_read_hw(codec, WM8961_SOFTWARE_RESET); + if (reg != 0x1801) { + dev_err(codec->dev, "Device is not a WM8961: ID=0x%x\n", reg); + ret = -EINVAL; + goto err; + } + + reg = wm8961_read_hw(codec, WM8961_RIGHT_INPUT_VOLUME); + dev_info(codec->dev, "WM8961 family %d revision %c\n", + (reg & WM8961_DEVICE_ID_MASK) >> WM8961_DEVICE_ID_SHIFT, + ((reg & WM8961_CHIP_REV_MASK) >> WM8961_CHIP_REV_SHIFT) + + 'A'); + + ret = wm8961_reset(codec); + if (ret < 0) { + dev_err(codec->dev, "Failed to issue reset\n"); + return ret; + } + + /* Enable class W */ + reg = wm8961_read(codec, WM8961_CHARGE_PUMP_B); + reg |= WM8961_CP_DYN_PWR_MASK; + wm8961_write(codec, WM8961_CHARGE_PUMP_B, reg); + + /* Latch volume update bits (right channel only, we always + * write both out) and default ZC on. */ + reg = wm8961_read(codec, WM8961_ROUT1_VOLUME); + wm8961_write(codec, WM8961_ROUT1_VOLUME, + reg | WM8961_LO1ZC | WM8961_OUT1VU); + wm8961_write(codec, WM8961_LOUT1_VOLUME, reg | WM8961_LO1ZC); + reg = wm8961_read(codec, WM8961_ROUT2_VOLUME); + wm8961_write(codec, WM8961_ROUT2_VOLUME, + reg | WM8961_SPKRZC | WM8961_SPKVU); + wm8961_write(codec, WM8961_LOUT2_VOLUME, reg | WM8961_SPKLZC); + + reg = wm8961_read(codec, WM8961_RIGHT_ADC_VOLUME); + wm8961_write(codec, WM8961_RIGHT_ADC_VOLUME, reg | WM8961_ADCVU); + reg = wm8961_read(codec, WM8961_RIGHT_INPUT_VOLUME); + wm8961_write(codec, WM8961_RIGHT_INPUT_VOLUME, reg | WM8961_IPVU); + + /* Use soft mute by default */ + reg = wm8961_read(codec, WM8961_ADC_DAC_CONTROL_2); + reg |= WM8961_DACSMM; + wm8961_write(codec, WM8961_ADC_DAC_CONTROL_2, reg); + + /* Use automatic clocking mode by default; for now this is all + * we support. + */ + reg = wm8961_read(codec, WM8961_CLOCKING_3); + reg &= ~WM8961_MANUAL_MODE; + wm8961_write(codec, WM8961_CLOCKING_3, reg); + + wm8961_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + wm8961_dai.dev = codec->dev; + + wm8961_codec = codec; + + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(codec->dev, "Failed to register codec: %d\n", ret); + return ret; + } + + ret = snd_soc_register_dai(&wm8961_dai); + if (ret != 0) { + dev_err(codec->dev, "Failed to register DAI: %d\n", ret); + snd_soc_unregister_codec(codec); + return ret; + } + + return 0; + +err: + kfree(wm8961); + return ret; +} + +static void wm8961_unregister(struct wm8961_priv *wm8961) +{ + wm8961_set_bias_level(&wm8961->codec, SND_SOC_BIAS_OFF); + snd_soc_unregister_dai(&wm8961_dai); + snd_soc_unregister_codec(&wm8961->codec); + kfree(wm8961); + wm8961_codec = NULL; +} + +static __devinit int wm8961_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8961_priv *wm8961; + struct snd_soc_codec *codec; + + wm8961 = kzalloc(sizeof(struct wm8961_priv), GFP_KERNEL); + if (wm8961 == NULL) + return -ENOMEM; + + codec = &wm8961->codec; + codec->hw_write = (hw_write_t)i2c_master_send; + + i2c_set_clientdata(i2c, wm8961); + codec->control_data = i2c; + + codec->dev = &i2c->dev; + + return wm8961_register(wm8961); +} + +static __devexit int wm8961_i2c_remove(struct i2c_client *client) +{ + struct wm8961_priv *wm8961 = i2c_get_clientdata(client); + wm8961_unregister(wm8961); + return 0; +} + +static const struct i2c_device_id wm8961_i2c_id[] = { + { "wm8961", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8961_i2c_id); + +static struct i2c_driver wm8961_i2c_driver = { + .driver = { + .name = "wm8961", + .owner = THIS_MODULE, + }, + .probe = wm8961_i2c_probe, + .remove = __devexit_p(wm8961_i2c_remove), + .id_table = wm8961_i2c_id, +}; + +static int __init wm8961_modinit(void) +{ + int ret; + + ret = i2c_add_driver(&wm8961_i2c_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register WM8961 I2C driver: %d\n", + ret); + } + + return ret; +} +module_init(wm8961_modinit); + +static void __exit wm8961_exit(void) +{ + i2c_del_driver(&wm8961_i2c_driver); +} +module_exit(wm8961_exit); + + +MODULE_DESCRIPTION("ASoC WM8961 driver"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8961.h b/sound/soc/codecs/wm8961.h new file mode 100644 index 000000000000..5513bfd720d6 --- /dev/null +++ b/sound/soc/codecs/wm8961.h @@ -0,0 +1,866 @@ +/* + * wm8961.h -- WM8961 Soc Audio driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _WM8961_H +#define _WM8961_H + +#include + +extern struct snd_soc_codec_device soc_codec_dev_wm8961; +extern struct snd_soc_dai wm8961_dai; + +#define WM8961_BCLK 1 +#define WM8961_LRCLK 2 + +#define WM8961_BCLK_DIV_1 0 +#define WM8961_BCLK_DIV_1_5 1 +#define WM8961_BCLK_DIV_2 2 +#define WM8961_BCLK_DIV_3 3 +#define WM8961_BCLK_DIV_4 4 +#define WM8961_BCLK_DIV_5_5 5 +#define WM8961_BCLK_DIV_6 6 +#define WM8961_BCLK_DIV_8 7 +#define WM8961_BCLK_DIV_11 8 +#define WM8961_BCLK_DIV_12 9 +#define WM8961_BCLK_DIV_16 10 +#define WM8961_BCLK_DIV_24 11 +#define WM8961_BCLK_DIV_32 13 + + +/* + * Register values. + */ +#define WM8961_LEFT_INPUT_VOLUME 0x00 +#define WM8961_RIGHT_INPUT_VOLUME 0x01 +#define WM8961_LOUT1_VOLUME 0x02 +#define WM8961_ROUT1_VOLUME 0x03 +#define WM8961_CLOCKING1 0x04 +#define WM8961_ADC_DAC_CONTROL_1 0x05 +#define WM8961_ADC_DAC_CONTROL_2 0x06 +#define WM8961_AUDIO_INTERFACE_0 0x07 +#define WM8961_CLOCKING2 0x08 +#define WM8961_AUDIO_INTERFACE_1 0x09 +#define WM8961_LEFT_DAC_VOLUME 0x0A +#define WM8961_RIGHT_DAC_VOLUME 0x0B +#define WM8961_AUDIO_INTERFACE_2 0x0E +#define WM8961_SOFTWARE_RESET 0x0F +#define WM8961_ALC1 0x11 +#define WM8961_ALC2 0x12 +#define WM8961_ALC3 0x13 +#define WM8961_NOISE_GATE 0x14 +#define WM8961_LEFT_ADC_VOLUME 0x15 +#define WM8961_RIGHT_ADC_VOLUME 0x16 +#define WM8961_ADDITIONAL_CONTROL_1 0x17 +#define WM8961_ADDITIONAL_CONTROL_2 0x18 +#define WM8961_PWR_MGMT_1 0x19 +#define WM8961_PWR_MGMT_2 0x1A +#define WM8961_ADDITIONAL_CONTROL_3 0x1B +#define WM8961_ANTI_POP 0x1C +#define WM8961_CLOCKING_3 0x1E +#define WM8961_ADCL_SIGNAL_PATH 0x20 +#define WM8961_ADCR_SIGNAL_PATH 0x21 +#define WM8961_LOUT2_VOLUME 0x28 +#define WM8961_ROUT2_VOLUME 0x29 +#define WM8961_PWR_MGMT_3 0x2F +#define WM8961_ADDITIONAL_CONTROL_4 0x30 +#define WM8961_CLASS_D_CONTROL_1 0x31 +#define WM8961_CLASS_D_CONTROL_2 0x33 +#define WM8961_CLOCKING_4 0x38 +#define WM8961_DSP_SIDETONE_0 0x39 +#define WM8961_DSP_SIDETONE_1 0x3A +#define WM8961_DC_SERVO_0 0x3C +#define WM8961_DC_SERVO_1 0x3D +#define WM8961_DC_SERVO_3 0x3F +#define WM8961_DC_SERVO_5 0x41 +#define WM8961_ANALOGUE_PGA_BIAS 0x44 +#define WM8961_ANALOGUE_HP_0 0x45 +#define WM8961_ANALOGUE_HP_2 0x47 +#define WM8961_CHARGE_PUMP_1 0x48 +#define WM8961_CHARGE_PUMP_B 0x52 +#define WM8961_WRITE_SEQUENCER_1 0x57 +#define WM8961_WRITE_SEQUENCER_2 0x58 +#define WM8961_WRITE_SEQUENCER_3 0x59 +#define WM8961_WRITE_SEQUENCER_4 0x5A +#define WM8961_WRITE_SEQUENCER_5 0x5B +#define WM8961_WRITE_SEQUENCER_6 0x5C +#define WM8961_WRITE_SEQUENCER_7 0x5D +#define WM8961_GENERAL_TEST_1 0xFC + + +/* + * Field Definitions. + */ + +/* + * R0 (0x00) - Left Input volume + */ +#define WM8961_IPVU 0x0100 /* IPVU */ +#define WM8961_IPVU_MASK 0x0100 /* IPVU */ +#define WM8961_IPVU_SHIFT 8 /* IPVU */ +#define WM8961_IPVU_WIDTH 1 /* IPVU */ +#define WM8961_LINMUTE 0x0080 /* LINMUTE */ +#define WM8961_LINMUTE_MASK 0x0080 /* LINMUTE */ +#define WM8961_LINMUTE_SHIFT 7 /* LINMUTE */ +#define WM8961_LINMUTE_WIDTH 1 /* LINMUTE */ +#define WM8961_LIZC 0x0040 /* LIZC */ +#define WM8961_LIZC_MASK 0x0040 /* LIZC */ +#define WM8961_LIZC_SHIFT 6 /* LIZC */ +#define WM8961_LIZC_WIDTH 1 /* LIZC */ +#define WM8961_LINVOL_MASK 0x003F /* LINVOL - [5:0] */ +#define WM8961_LINVOL_SHIFT 0 /* LINVOL - [5:0] */ +#define WM8961_LINVOL_WIDTH 6 /* LINVOL - [5:0] */ + +/* + * R1 (0x01) - Right Input volume + */ +#define WM8961_DEVICE_ID_MASK 0xF000 /* DEVICE_ID - [15:12] */ +#define WM8961_DEVICE_ID_SHIFT 12 /* DEVICE_ID - [15:12] */ +#define WM8961_DEVICE_ID_WIDTH 4 /* DEVICE_ID - [15:12] */ +#define WM8961_CHIP_REV_MASK 0x0E00 /* CHIP_REV - [11:9] */ +#define WM8961_CHIP_REV_SHIFT 9 /* CHIP_REV - [11:9] */ +#define WM8961_CHIP_REV_WIDTH 3 /* CHIP_REV - [11:9] */ +#define WM8961_IPVU 0x0100 /* IPVU */ +#define WM8961_IPVU_MASK 0x0100 /* IPVU */ +#define WM8961_IPVU_SHIFT 8 /* IPVU */ +#define WM8961_IPVU_WIDTH 1 /* IPVU */ +#define WM8961_RINMUTE 0x0080 /* RINMUTE */ +#define WM8961_RINMUTE_MASK 0x0080 /* RINMUTE */ +#define WM8961_RINMUTE_SHIFT 7 /* RINMUTE */ +#define WM8961_RINMUTE_WIDTH 1 /* RINMUTE */ +#define WM8961_RIZC 0x0040 /* RIZC */ +#define WM8961_RIZC_MASK 0x0040 /* RIZC */ +#define WM8961_RIZC_SHIFT 6 /* RIZC */ +#define WM8961_RIZC_WIDTH 1 /* RIZC */ +#define WM8961_RINVOL_MASK 0x003F /* RINVOL - [5:0] */ +#define WM8961_RINVOL_SHIFT 0 /* RINVOL - [5:0] */ +#define WM8961_RINVOL_WIDTH 6 /* RINVOL - [5:0] */ + +/* + * R2 (0x02) - LOUT1 volume + */ +#define WM8961_OUT1VU 0x0100 /* OUT1VU */ +#define WM8961_OUT1VU_MASK 0x0100 /* OUT1VU */ +#define WM8961_OUT1VU_SHIFT 8 /* OUT1VU */ +#define WM8961_OUT1VU_WIDTH 1 /* OUT1VU */ +#define WM8961_LO1ZC 0x0080 /* LO1ZC */ +#define WM8961_LO1ZC_MASK 0x0080 /* LO1ZC */ +#define WM8961_LO1ZC_SHIFT 7 /* LO1ZC */ +#define WM8961_LO1ZC_WIDTH 1 /* LO1ZC */ +#define WM8961_LOUT1VOL_MASK 0x007F /* LOUT1VOL - [6:0] */ +#define WM8961_LOUT1VOL_SHIFT 0 /* LOUT1VOL - [6:0] */ +#define WM8961_LOUT1VOL_WIDTH 7 /* LOUT1VOL - [6:0] */ + +/* + * R3 (0x03) - ROUT1 volume + */ +#define WM8961_OUT1VU 0x0100 /* OUT1VU */ +#define WM8961_OUT1VU_MASK 0x0100 /* OUT1VU */ +#define WM8961_OUT1VU_SHIFT 8 /* OUT1VU */ +#define WM8961_OUT1VU_WIDTH 1 /* OUT1VU */ +#define WM8961_RO1ZC 0x0080 /* RO1ZC */ +#define WM8961_RO1ZC_MASK 0x0080 /* RO1ZC */ +#define WM8961_RO1ZC_SHIFT 7 /* RO1ZC */ +#define WM8961_RO1ZC_WIDTH 1 /* RO1ZC */ +#define WM8961_ROUT1VOL_MASK 0x007F /* ROUT1VOL - [6:0] */ +#define WM8961_ROUT1VOL_SHIFT 0 /* ROUT1VOL - [6:0] */ +#define WM8961_ROUT1VOL_WIDTH 7 /* ROUT1VOL - [6:0] */ + +/* + * R4 (0x04) - Clocking1 + */ +#define WM8961_ADCDIV_MASK 0x01C0 /* ADCDIV - [8:6] */ +#define WM8961_ADCDIV_SHIFT 6 /* ADCDIV - [8:6] */ +#define WM8961_ADCDIV_WIDTH 3 /* ADCDIV - [8:6] */ +#define WM8961_DACDIV_MASK 0x0038 /* DACDIV - [5:3] */ +#define WM8961_DACDIV_SHIFT 3 /* DACDIV - [5:3] */ +#define WM8961_DACDIV_WIDTH 3 /* DACDIV - [5:3] */ +#define WM8961_MCLKDIV 0x0004 /* MCLKDIV */ +#define WM8961_MCLKDIV_MASK 0x0004 /* MCLKDIV */ +#define WM8961_MCLKDIV_SHIFT 2 /* MCLKDIV */ +#define WM8961_MCLKDIV_WIDTH 1 /* MCLKDIV */ + +/* + * R5 (0x05) - ADC & DAC Control 1 + */ +#define WM8961_ADCPOL_MASK 0x0060 /* ADCPOL - [6:5] */ +#define WM8961_ADCPOL_SHIFT 5 /* ADCPOL - [6:5] */ +#define WM8961_ADCPOL_WIDTH 2 /* ADCPOL - [6:5] */ +#define WM8961_DACMU 0x0008 /* DACMU */ +#define WM8961_DACMU_MASK 0x0008 /* DACMU */ +#define WM8961_DACMU_SHIFT 3 /* DACMU */ +#define WM8961_DACMU_WIDTH 1 /* DACMU */ +#define WM8961_DEEMPH_MASK 0x0006 /* DEEMPH - [2:1] */ +#define WM8961_DEEMPH_SHIFT 1 /* DEEMPH - [2:1] */ +#define WM8961_DEEMPH_WIDTH 2 /* DEEMPH - [2:1] */ +#define WM8961_ADCHPD 0x0001 /* ADCHPD */ +#define WM8961_ADCHPD_MASK 0x0001 /* ADCHPD */ +#define WM8961_ADCHPD_SHIFT 0 /* ADCHPD */ +#define WM8961_ADCHPD_WIDTH 1 /* ADCHPD */ + +/* + * R6 (0x06) - ADC & DAC Control 2 + */ +#define WM8961_ADC_HPF_CUT_MASK 0x0180 /* ADC_HPF_CUT - [8:7] */ +#define WM8961_ADC_HPF_CUT_SHIFT 7 /* ADC_HPF_CUT - [8:7] */ +#define WM8961_ADC_HPF_CUT_WIDTH 2 /* ADC_HPF_CUT - [8:7] */ +#define WM8961_DACPOL_MASK 0x0060 /* DACPOL - [6:5] */ +#define WM8961_DACPOL_SHIFT 5 /* DACPOL - [6:5] */ +#define WM8961_DACPOL_WIDTH 2 /* DACPOL - [6:5] */ +#define WM8961_DACSMM 0x0008 /* DACSMM */ +#define WM8961_DACSMM_MASK 0x0008 /* DACSMM */ +#define WM8961_DACSMM_SHIFT 3 /* DACSMM */ +#define WM8961_DACSMM_WIDTH 1 /* DACSMM */ +#define WM8961_DACMR 0x0004 /* DACMR */ +#define WM8961_DACMR_MASK 0x0004 /* DACMR */ +#define WM8961_DACMR_SHIFT 2 /* DACMR */ +#define WM8961_DACMR_WIDTH 1 /* DACMR */ +#define WM8961_DACSLOPE 0x0002 /* DACSLOPE */ +#define WM8961_DACSLOPE_MASK 0x0002 /* DACSLOPE */ +#define WM8961_DACSLOPE_SHIFT 1 /* DACSLOPE */ +#define WM8961_DACSLOPE_WIDTH 1 /* DACSLOPE */ +#define WM8961_DAC_OSR128 0x0001 /* DAC_OSR128 */ +#define WM8961_DAC_OSR128_MASK 0x0001 /* DAC_OSR128 */ +#define WM8961_DAC_OSR128_SHIFT 0 /* DAC_OSR128 */ +#define WM8961_DAC_OSR128_WIDTH 1 /* DAC_OSR128 */ + +/* + * R7 (0x07) - Audio Interface 0 + */ +#define WM8961_ALRSWAP 0x0100 /* ALRSWAP */ +#define WM8961_ALRSWAP_MASK 0x0100 /* ALRSWAP */ +#define WM8961_ALRSWAP_SHIFT 8 /* ALRSWAP */ +#define WM8961_ALRSWAP_WIDTH 1 /* ALRSWAP */ +#define WM8961_BCLKINV 0x0080 /* BCLKINV */ +#define WM8961_BCLKINV_MASK 0x0080 /* BCLKINV */ +#define WM8961_BCLKINV_SHIFT 7 /* BCLKINV */ +#define WM8961_BCLKINV_WIDTH 1 /* BCLKINV */ +#define WM8961_MS 0x0040 /* MS */ +#define WM8961_MS_MASK 0x0040 /* MS */ +#define WM8961_MS_SHIFT 6 /* MS */ +#define WM8961_MS_WIDTH 1 /* MS */ +#define WM8961_DLRSWAP 0x0020 /* DLRSWAP */ +#define WM8961_DLRSWAP_MASK 0x0020 /* DLRSWAP */ +#define WM8961_DLRSWAP_SHIFT 5 /* DLRSWAP */ +#define WM8961_DLRSWAP_WIDTH 1 /* DLRSWAP */ +#define WM8961_LRP 0x0010 /* LRP */ +#define WM8961_LRP_MASK 0x0010 /* LRP */ +#define WM8961_LRP_SHIFT 4 /* LRP */ +#define WM8961_LRP_WIDTH 1 /* LRP */ +#define WM8961_WL_MASK 0x000C /* WL - [3:2] */ +#define WM8961_WL_SHIFT 2 /* WL - [3:2] */ +#define WM8961_WL_WIDTH 2 /* WL - [3:2] */ +#define WM8961_FORMAT_MASK 0x0003 /* FORMAT - [1:0] */ +#define WM8961_FORMAT_SHIFT 0 /* FORMAT - [1:0] */ +#define WM8961_FORMAT_WIDTH 2 /* FORMAT - [1:0] */ + +/* + * R8 (0x08) - Clocking2 + */ +#define WM8961_DCLKDIV_MASK 0x01C0 /* DCLKDIV - [8:6] */ +#define WM8961_DCLKDIV_SHIFT 6 /* DCLKDIV - [8:6] */ +#define WM8961_DCLKDIV_WIDTH 3 /* DCLKDIV - [8:6] */ +#define WM8961_CLK_SYS_ENA 0x0020 /* CLK_SYS_ENA */ +#define WM8961_CLK_SYS_ENA_MASK 0x0020 /* CLK_SYS_ENA */ +#define WM8961_CLK_SYS_ENA_SHIFT 5 /* CLK_SYS_ENA */ +#define WM8961_CLK_SYS_ENA_WIDTH 1 /* CLK_SYS_ENA */ +#define WM8961_CLK_DSP_ENA 0x0010 /* CLK_DSP_ENA */ +#define WM8961_CLK_DSP_ENA_MASK 0x0010 /* CLK_DSP_ENA */ +#define WM8961_CLK_DSP_ENA_SHIFT 4 /* CLK_DSP_ENA */ +#define WM8961_CLK_DSP_ENA_WIDTH 1 /* CLK_DSP_ENA */ +#define WM8961_BCLKDIV_MASK 0x000F /* BCLKDIV - [3:0] */ +#define WM8961_BCLKDIV_SHIFT 0 /* BCLKDIV - [3:0] */ +#define WM8961_BCLKDIV_WIDTH 4 /* BCLKDIV - [3:0] */ + +/* + * R9 (0x09) - Audio Interface 1 + */ +#define WM8961_DACCOMP_MASK 0x0018 /* DACCOMP - [4:3] */ +#define WM8961_DACCOMP_SHIFT 3 /* DACCOMP - [4:3] */ +#define WM8961_DACCOMP_WIDTH 2 /* DACCOMP - [4:3] */ +#define WM8961_ADCCOMP_MASK 0x0006 /* ADCCOMP - [2:1] */ +#define WM8961_ADCCOMP_SHIFT 1 /* ADCCOMP - [2:1] */ +#define WM8961_ADCCOMP_WIDTH 2 /* ADCCOMP - [2:1] */ +#define WM8961_LOOPBACK 0x0001 /* LOOPBACK */ +#define WM8961_LOOPBACK_MASK 0x0001 /* LOOPBACK */ +#define WM8961_LOOPBACK_SHIFT 0 /* LOOPBACK */ +#define WM8961_LOOPBACK_WIDTH 1 /* LOOPBACK */ + +/* + * R10 (0x0A) - Left DAC volume + */ +#define WM8961_DACVU 0x0100 /* DACVU */ +#define WM8961_DACVU_MASK 0x0100 /* DACVU */ +#define WM8961_DACVU_SHIFT 8 /* DACVU */ +#define WM8961_DACVU_WIDTH 1 /* DACVU */ +#define WM8961_LDACVOL_MASK 0x00FF /* LDACVOL - [7:0] */ +#define WM8961_LDACVOL_SHIFT 0 /* LDACVOL - [7:0] */ +#define WM8961_LDACVOL_WIDTH 8 /* LDACVOL - [7:0] */ + +/* + * R11 (0x0B) - Right DAC volume + */ +#define WM8961_DACVU 0x0100 /* DACVU */ +#define WM8961_DACVU_MASK 0x0100 /* DACVU */ +#define WM8961_DACVU_SHIFT 8 /* DACVU */ +#define WM8961_DACVU_WIDTH 1 /* DACVU */ +#define WM8961_RDACVOL_MASK 0x00FF /* RDACVOL - [7:0] */ +#define WM8961_RDACVOL_SHIFT 0 /* RDACVOL - [7:0] */ +#define WM8961_RDACVOL_WIDTH 8 /* RDACVOL - [7:0] */ + +/* + * R14 (0x0E) - Audio Interface 2 + */ +#define WM8961_LRCLK_RATE_MASK 0x01FF /* LRCLK_RATE - [8:0] */ +#define WM8961_LRCLK_RATE_SHIFT 0 /* LRCLK_RATE - [8:0] */ +#define WM8961_LRCLK_RATE_WIDTH 9 /* LRCLK_RATE - [8:0] */ + +/* + * R15 (0x0F) - Software Reset + */ +#define WM8961_SW_RST_DEV_ID1_MASK 0xFFFF /* SW_RST_DEV_ID1 - [15:0] */ +#define WM8961_SW_RST_DEV_ID1_SHIFT 0 /* SW_RST_DEV_ID1 - [15:0] */ +#define WM8961_SW_RST_DEV_ID1_WIDTH 16 /* SW_RST_DEV_ID1 - [15:0] */ + +/* + * R17 (0x11) - ALC1 + */ +#define WM8961_ALCSEL_MASK 0x0180 /* ALCSEL - [8:7] */ +#define WM8961_ALCSEL_SHIFT 7 /* ALCSEL - [8:7] */ +#define WM8961_ALCSEL_WIDTH 2 /* ALCSEL - [8:7] */ +#define WM8961_MAXGAIN_MASK 0x0070 /* MAXGAIN - [6:4] */ +#define WM8961_MAXGAIN_SHIFT 4 /* MAXGAIN - [6:4] */ +#define WM8961_MAXGAIN_WIDTH 3 /* MAXGAIN - [6:4] */ +#define WM8961_ALCL_MASK 0x000F /* ALCL - [3:0] */ +#define WM8961_ALCL_SHIFT 0 /* ALCL - [3:0] */ +#define WM8961_ALCL_WIDTH 4 /* ALCL - [3:0] */ + +/* + * R18 (0x12) - ALC2 + */ +#define WM8961_ALCZC 0x0080 /* ALCZC */ +#define WM8961_ALCZC_MASK 0x0080 /* ALCZC */ +#define WM8961_ALCZC_SHIFT 7 /* ALCZC */ +#define WM8961_ALCZC_WIDTH 1 /* ALCZC */ +#define WM8961_MINGAIN_MASK 0x0070 /* MINGAIN - [6:4] */ +#define WM8961_MINGAIN_SHIFT 4 /* MINGAIN - [6:4] */ +#define WM8961_MINGAIN_WIDTH 3 /* MINGAIN - [6:4] */ +#define WM8961_HLD_MASK 0x000F /* HLD - [3:0] */ +#define WM8961_HLD_SHIFT 0 /* HLD - [3:0] */ +#define WM8961_HLD_WIDTH 4 /* HLD - [3:0] */ + +/* + * R19 (0x13) - ALC3 + */ +#define WM8961_ALCMODE 0x0100 /* ALCMODE */ +#define WM8961_ALCMODE_MASK 0x0100 /* ALCMODE */ +#define WM8961_ALCMODE_SHIFT 8 /* ALCMODE */ +#define WM8961_ALCMODE_WIDTH 1 /* ALCMODE */ +#define WM8961_DCY_MASK 0x00F0 /* DCY - [7:4] */ +#define WM8961_DCY_SHIFT 4 /* DCY - [7:4] */ +#define WM8961_DCY_WIDTH 4 /* DCY - [7:4] */ +#define WM8961_ATK_MASK 0x000F /* ATK - [3:0] */ +#define WM8961_ATK_SHIFT 0 /* ATK - [3:0] */ +#define WM8961_ATK_WIDTH 4 /* ATK - [3:0] */ + +/* + * R20 (0x14) - Noise Gate + */ +#define WM8961_NGTH_MASK 0x00F8 /* NGTH - [7:3] */ +#define WM8961_NGTH_SHIFT 3 /* NGTH - [7:3] */ +#define WM8961_NGTH_WIDTH 5 /* NGTH - [7:3] */ +#define WM8961_NGG 0x0002 /* NGG */ +#define WM8961_NGG_MASK 0x0002 /* NGG */ +#define WM8961_NGG_SHIFT 1 /* NGG */ +#define WM8961_NGG_WIDTH 1 /* NGG */ +#define WM8961_NGAT 0x0001 /* NGAT */ +#define WM8961_NGAT_MASK 0x0001 /* NGAT */ +#define WM8961_NGAT_SHIFT 0 /* NGAT */ +#define WM8961_NGAT_WIDTH 1 /* NGAT */ + +/* + * R21 (0x15) - Left ADC volume + */ +#define WM8961_ADCVU 0x0100 /* ADCVU */ +#define WM8961_ADCVU_MASK 0x0100 /* ADCVU */ +#define WM8961_ADCVU_SHIFT 8 /* ADCVU */ +#define WM8961_ADCVU_WIDTH 1 /* ADCVU */ +#define WM8961_LADCVOL_MASK 0x00FF /* LADCVOL - [7:0] */ +#define WM8961_LADCVOL_SHIFT 0 /* LADCVOL - [7:0] */ +#define WM8961_LADCVOL_WIDTH 8 /* LADCVOL - [7:0] */ + +/* + * R22 (0x16) - Right ADC volume + */ +#define WM8961_ADCVU 0x0100 /* ADCVU */ +#define WM8961_ADCVU_MASK 0x0100 /* ADCVU */ +#define WM8961_ADCVU_SHIFT 8 /* ADCVU */ +#define WM8961_ADCVU_WIDTH 1 /* ADCVU */ +#define WM8961_RADCVOL_MASK 0x00FF /* RADCVOL - [7:0] */ +#define WM8961_RADCVOL_SHIFT 0 /* RADCVOL - [7:0] */ +#define WM8961_RADCVOL_WIDTH 8 /* RADCVOL - [7:0] */ + +/* + * R23 (0x17) - Additional control(1) + */ +#define WM8961_TSDEN 0x0100 /* TSDEN */ +#define WM8961_TSDEN_MASK 0x0100 /* TSDEN */ +#define WM8961_TSDEN_SHIFT 8 /* TSDEN */ +#define WM8961_TSDEN_WIDTH 1 /* TSDEN */ +#define WM8961_DMONOMIX 0x0010 /* DMONOMIX */ +#define WM8961_DMONOMIX_MASK 0x0010 /* DMONOMIX */ +#define WM8961_DMONOMIX_SHIFT 4 /* DMONOMIX */ +#define WM8961_DMONOMIX_WIDTH 1 /* DMONOMIX */ +#define WM8961_TOEN 0x0001 /* TOEN */ +#define WM8961_TOEN_MASK 0x0001 /* TOEN */ +#define WM8961_TOEN_SHIFT 0 /* TOEN */ +#define WM8961_TOEN_WIDTH 1 /* TOEN */ + +/* + * R24 (0x18) - Additional control(2) + */ +#define WM8961_TRIS 0x0008 /* TRIS */ +#define WM8961_TRIS_MASK 0x0008 /* TRIS */ +#define WM8961_TRIS_SHIFT 3 /* TRIS */ +#define WM8961_TRIS_WIDTH 1 /* TRIS */ + +/* + * R25 (0x19) - Pwr Mgmt (1) + */ +#define WM8961_VMIDSEL_MASK 0x0180 /* VMIDSEL - [8:7] */ +#define WM8961_VMIDSEL_SHIFT 7 /* VMIDSEL - [8:7] */ +#define WM8961_VMIDSEL_WIDTH 2 /* VMIDSEL - [8:7] */ +#define WM8961_VREF 0x0040 /* VREF */ +#define WM8961_VREF_MASK 0x0040 /* VREF */ +#define WM8961_VREF_SHIFT 6 /* VREF */ +#define WM8961_VREF_WIDTH 1 /* VREF */ +#define WM8961_AINL 0x0020 /* AINL */ +#define WM8961_AINL_MASK 0x0020 /* AINL */ +#define WM8961_AINL_SHIFT 5 /* AINL */ +#define WM8961_AINL_WIDTH 1 /* AINL */ +#define WM8961_AINR 0x0010 /* AINR */ +#define WM8961_AINR_MASK 0x0010 /* AINR */ +#define WM8961_AINR_SHIFT 4 /* AINR */ +#define WM8961_AINR_WIDTH 1 /* AINR */ +#define WM8961_ADCL 0x0008 /* ADCL */ +#define WM8961_ADCL_MASK 0x0008 /* ADCL */ +#define WM8961_ADCL_SHIFT 3 /* ADCL */ +#define WM8961_ADCL_WIDTH 1 /* ADCL */ +#define WM8961_ADCR 0x0004 /* ADCR */ +#define WM8961_ADCR_MASK 0x0004 /* ADCR */ +#define WM8961_ADCR_SHIFT 2 /* ADCR */ +#define WM8961_ADCR_WIDTH 1 /* ADCR */ +#define WM8961_MICB 0x0002 /* MICB */ +#define WM8961_MICB_MASK 0x0002 /* MICB */ +#define WM8961_MICB_SHIFT 1 /* MICB */ +#define WM8961_MICB_WIDTH 1 /* MICB */ + +/* + * R26 (0x1A) - Pwr Mgmt (2) + */ +#define WM8961_DACL 0x0100 /* DACL */ +#define WM8961_DACL_MASK 0x0100 /* DACL */ +#define WM8961_DACL_SHIFT 8 /* DACL */ +#define WM8961_DACL_WIDTH 1 /* DACL */ +#define WM8961_DACR 0x0080 /* DACR */ +#define WM8961_DACR_MASK 0x0080 /* DACR */ +#define WM8961_DACR_SHIFT 7 /* DACR */ +#define WM8961_DACR_WIDTH 1 /* DACR */ +#define WM8961_LOUT1_PGA 0x0040 /* LOUT1_PGA */ +#define WM8961_LOUT1_PGA_MASK 0x0040 /* LOUT1_PGA */ +#define WM8961_LOUT1_PGA_SHIFT 6 /* LOUT1_PGA */ +#define WM8961_LOUT1_PGA_WIDTH 1 /* LOUT1_PGA */ +#define WM8961_ROUT1_PGA 0x0020 /* ROUT1_PGA */ +#define WM8961_ROUT1_PGA_MASK 0x0020 /* ROUT1_PGA */ +#define WM8961_ROUT1_PGA_SHIFT 5 /* ROUT1_PGA */ +#define WM8961_ROUT1_PGA_WIDTH 1 /* ROUT1_PGA */ +#define WM8961_SPKL_PGA 0x0010 /* SPKL_PGA */ +#define WM8961_SPKL_PGA_MASK 0x0010 /* SPKL_PGA */ +#define WM8961_SPKL_PGA_SHIFT 4 /* SPKL_PGA */ +#define WM8961_SPKL_PGA_WIDTH 1 /* SPKL_PGA */ +#define WM8961_SPKR_PGA 0x0008 /* SPKR_PGA */ +#define WM8961_SPKR_PGA_MASK 0x0008 /* SPKR_PGA */ +#define WM8961_SPKR_PGA_SHIFT 3 /* SPKR_PGA */ +#define WM8961_SPKR_PGA_WIDTH 1 /* SPKR_PGA */ + +/* + * R27 (0x1B) - Additional Control (3) + */ +#define WM8961_SAMPLE_RATE_MASK 0x0007 /* SAMPLE_RATE - [2:0] */ +#define WM8961_SAMPLE_RATE_SHIFT 0 /* SAMPLE_RATE - [2:0] */ +#define WM8961_SAMPLE_RATE_WIDTH 3 /* SAMPLE_RATE - [2:0] */ + +/* + * R28 (0x1C) - Anti-pop + */ +#define WM8961_BUFDCOPEN 0x0010 /* BUFDCOPEN */ +#define WM8961_BUFDCOPEN_MASK 0x0010 /* BUFDCOPEN */ +#define WM8961_BUFDCOPEN_SHIFT 4 /* BUFDCOPEN */ +#define WM8961_BUFDCOPEN_WIDTH 1 /* BUFDCOPEN */ +#define WM8961_BUFIOEN 0x0008 /* BUFIOEN */ +#define WM8961_BUFIOEN_MASK 0x0008 /* BUFIOEN */ +#define WM8961_BUFIOEN_SHIFT 3 /* BUFIOEN */ +#define WM8961_BUFIOEN_WIDTH 1 /* BUFIOEN */ +#define WM8961_SOFT_ST 0x0004 /* SOFT_ST */ +#define WM8961_SOFT_ST_MASK 0x0004 /* SOFT_ST */ +#define WM8961_SOFT_ST_SHIFT 2 /* SOFT_ST */ +#define WM8961_SOFT_ST_WIDTH 1 /* SOFT_ST */ + +/* + * R30 (0x1E) - Clocking 3 + */ +#define WM8961_CLK_TO_DIV_MASK 0x0180 /* CLK_TO_DIV - [8:7] */ +#define WM8961_CLK_TO_DIV_SHIFT 7 /* CLK_TO_DIV - [8:7] */ +#define WM8961_CLK_TO_DIV_WIDTH 2 /* CLK_TO_DIV - [8:7] */ +#define WM8961_CLK_256K_DIV_MASK 0x007E /* CLK_256K_DIV - [6:1] */ +#define WM8961_CLK_256K_DIV_SHIFT 1 /* CLK_256K_DIV - [6:1] */ +#define WM8961_CLK_256K_DIV_WIDTH 6 /* CLK_256K_DIV - [6:1] */ +#define WM8961_MANUAL_MODE 0x0001 /* MANUAL_MODE */ +#define WM8961_MANUAL_MODE_MASK 0x0001 /* MANUAL_MODE */ +#define WM8961_MANUAL_MODE_SHIFT 0 /* MANUAL_MODE */ +#define WM8961_MANUAL_MODE_WIDTH 1 /* MANUAL_MODE */ + +/* + * R32 (0x20) - ADCL signal path + */ +#define WM8961_LMICBOOST_MASK 0x0030 /* LMICBOOST - [5:4] */ +#define WM8961_LMICBOOST_SHIFT 4 /* LMICBOOST - [5:4] */ +#define WM8961_LMICBOOST_WIDTH 2 /* LMICBOOST - [5:4] */ + +/* + * R33 (0x21) - ADCR signal path + */ +#define WM8961_RMICBOOST_MASK 0x0030 /* RMICBOOST - [5:4] */ +#define WM8961_RMICBOOST_SHIFT 4 /* RMICBOOST - [5:4] */ +#define WM8961_RMICBOOST_WIDTH 2 /* RMICBOOST - [5:4] */ + +/* + * R40 (0x28) - LOUT2 volume + */ +#define WM8961_SPKVU 0x0100 /* SPKVU */ +#define WM8961_SPKVU_MASK 0x0100 /* SPKVU */ +#define WM8961_SPKVU_SHIFT 8 /* SPKVU */ +#define WM8961_SPKVU_WIDTH 1 /* SPKVU */ +#define WM8961_SPKLZC 0x0080 /* SPKLZC */ +#define WM8961_SPKLZC_MASK 0x0080 /* SPKLZC */ +#define WM8961_SPKLZC_SHIFT 7 /* SPKLZC */ +#define WM8961_SPKLZC_WIDTH 1 /* SPKLZC */ +#define WM8961_SPKLVOL_MASK 0x007F /* SPKLVOL - [6:0] */ +#define WM8961_SPKLVOL_SHIFT 0 /* SPKLVOL - [6:0] */ +#define WM8961_SPKLVOL_WIDTH 7 /* SPKLVOL - [6:0] */ + +/* + * R41 (0x29) - ROUT2 volume + */ +#define WM8961_SPKVU 0x0100 /* SPKVU */ +#define WM8961_SPKVU_MASK 0x0100 /* SPKVU */ +#define WM8961_SPKVU_SHIFT 8 /* SPKVU */ +#define WM8961_SPKVU_WIDTH 1 /* SPKVU */ +#define WM8961_SPKRZC 0x0080 /* SPKRZC */ +#define WM8961_SPKRZC_MASK 0x0080 /* SPKRZC */ +#define WM8961_SPKRZC_SHIFT 7 /* SPKRZC */ +#define WM8961_SPKRZC_WIDTH 1 /* SPKRZC */ +#define WM8961_SPKRVOL_MASK 0x007F /* SPKRVOL - [6:0] */ +#define WM8961_SPKRVOL_SHIFT 0 /* SPKRVOL - [6:0] */ +#define WM8961_SPKRVOL_WIDTH 7 /* SPKRVOL - [6:0] */ + +/* + * R47 (0x2F) - Pwr Mgmt (3) + */ +#define WM8961_TEMP_SHUT 0x0002 /* TEMP_SHUT */ +#define WM8961_TEMP_SHUT_MASK 0x0002 /* TEMP_SHUT */ +#define WM8961_TEMP_SHUT_SHIFT 1 /* TEMP_SHUT */ +#define WM8961_TEMP_SHUT_WIDTH 1 /* TEMP_SHUT */ +#define WM8961_TEMP_WARN 0x0001 /* TEMP_WARN */ +#define WM8961_TEMP_WARN_MASK 0x0001 /* TEMP_WARN */ +#define WM8961_TEMP_WARN_SHIFT 0 /* TEMP_WARN */ +#define WM8961_TEMP_WARN_WIDTH 1 /* TEMP_WARN */ + +/* + * R48 (0x30) - Additional Control (4) + */ +#define WM8961_TSENSEN 0x0002 /* TSENSEN */ +#define WM8961_TSENSEN_MASK 0x0002 /* TSENSEN */ +#define WM8961_TSENSEN_SHIFT 1 /* TSENSEN */ +#define WM8961_TSENSEN_WIDTH 1 /* TSENSEN */ +#define WM8961_MBSEL 0x0001 /* MBSEL */ +#define WM8961_MBSEL_MASK 0x0001 /* MBSEL */ +#define WM8961_MBSEL_SHIFT 0 /* MBSEL */ +#define WM8961_MBSEL_WIDTH 1 /* MBSEL */ + +/* + * R49 (0x31) - Class D Control 1 + */ +#define WM8961_SPKR_ENA 0x0080 /* SPKR_ENA */ +#define WM8961_SPKR_ENA_MASK 0x0080 /* SPKR_ENA */ +#define WM8961_SPKR_ENA_SHIFT 7 /* SPKR_ENA */ +#define WM8961_SPKR_ENA_WIDTH 1 /* SPKR_ENA */ +#define WM8961_SPKL_ENA 0x0040 /* SPKL_ENA */ +#define WM8961_SPKL_ENA_MASK 0x0040 /* SPKL_ENA */ +#define WM8961_SPKL_ENA_SHIFT 6 /* SPKL_ENA */ +#define WM8961_SPKL_ENA_WIDTH 1 /* SPKL_ENA */ + +/* + * R51 (0x33) - Class D Control 2 + */ +#define WM8961_CLASSD_ACGAIN_MASK 0x0007 /* CLASSD_ACGAIN - [2:0] */ +#define WM8961_CLASSD_ACGAIN_SHIFT 0 /* CLASSD_ACGAIN - [2:0] */ +#define WM8961_CLASSD_ACGAIN_WIDTH 3 /* CLASSD_ACGAIN - [2:0] */ + +/* + * R56 (0x38) - Clocking 4 + */ +#define WM8961_CLK_DCS_DIV_MASK 0x01E0 /* CLK_DCS_DIV - [8:5] */ +#define WM8961_CLK_DCS_DIV_SHIFT 5 /* CLK_DCS_DIV - [8:5] */ +#define WM8961_CLK_DCS_DIV_WIDTH 4 /* CLK_DCS_DIV - [8:5] */ +#define WM8961_CLK_SYS_RATE_MASK 0x001E /* CLK_SYS_RATE - [4:1] */ +#define WM8961_CLK_SYS_RATE_SHIFT 1 /* CLK_SYS_RATE - [4:1] */ +#define WM8961_CLK_SYS_RATE_WIDTH 4 /* CLK_SYS_RATE - [4:1] */ + +/* + * R57 (0x39) - DSP Sidetone 0 + */ +#define WM8961_ADCR_DAC_SVOL_MASK 0x00F0 /* ADCR_DAC_SVOL - [7:4] */ +#define WM8961_ADCR_DAC_SVOL_SHIFT 4 /* ADCR_DAC_SVOL - [7:4] */ +#define WM8961_ADCR_DAC_SVOL_WIDTH 4 /* ADCR_DAC_SVOL - [7:4] */ +#define WM8961_ADC_TO_DACR_MASK 0x000C /* ADC_TO_DACR - [3:2] */ +#define WM8961_ADC_TO_DACR_SHIFT 2 /* ADC_TO_DACR - [3:2] */ +#define WM8961_ADC_TO_DACR_WIDTH 2 /* ADC_TO_DACR - [3:2] */ + +/* + * R58 (0x3A) - DSP Sidetone 1 + */ +#define WM8961_ADCL_DAC_SVOL_MASK 0x00F0 /* ADCL_DAC_SVOL - [7:4] */ +#define WM8961_ADCL_DAC_SVOL_SHIFT 4 /* ADCL_DAC_SVOL - [7:4] */ +#define WM8961_ADCL_DAC_SVOL_WIDTH 4 /* ADCL_DAC_SVOL - [7:4] */ +#define WM8961_ADC_TO_DACL_MASK 0x000C /* ADC_TO_DACL - [3:2] */ +#define WM8961_ADC_TO_DACL_SHIFT 2 /* ADC_TO_DACL - [3:2] */ +#define WM8961_ADC_TO_DACL_WIDTH 2 /* ADC_TO_DACL - [3:2] */ + +/* + * R60 (0x3C) - DC Servo 0 + */ +#define WM8961_DCS_ENA_CHAN_INL 0x0080 /* DCS_ENA_CHAN_INL */ +#define WM8961_DCS_ENA_CHAN_INL_MASK 0x0080 /* DCS_ENA_CHAN_INL */ +#define WM8961_DCS_ENA_CHAN_INL_SHIFT 7 /* DCS_ENA_CHAN_INL */ +#define WM8961_DCS_ENA_CHAN_INL_WIDTH 1 /* DCS_ENA_CHAN_INL */ +#define WM8961_DCS_TRIG_STARTUP_INL 0x0040 /* DCS_TRIG_STARTUP_INL */ +#define WM8961_DCS_TRIG_STARTUP_INL_MASK 0x0040 /* DCS_TRIG_STARTUP_INL */ +#define WM8961_DCS_TRIG_STARTUP_INL_SHIFT 6 /* DCS_TRIG_STARTUP_INL */ +#define WM8961_DCS_TRIG_STARTUP_INL_WIDTH 1 /* DCS_TRIG_STARTUP_INL */ +#define WM8961_DCS_TRIG_SERIES_INL 0x0010 /* DCS_TRIG_SERIES_INL */ +#define WM8961_DCS_TRIG_SERIES_INL_MASK 0x0010 /* DCS_TRIG_SERIES_INL */ +#define WM8961_DCS_TRIG_SERIES_INL_SHIFT 4 /* DCS_TRIG_SERIES_INL */ +#define WM8961_DCS_TRIG_SERIES_INL_WIDTH 1 /* DCS_TRIG_SERIES_INL */ +#define WM8961_DCS_ENA_CHAN_INR 0x0008 /* DCS_ENA_CHAN_INR */ +#define WM8961_DCS_ENA_CHAN_INR_MASK 0x0008 /* DCS_ENA_CHAN_INR */ +#define WM8961_DCS_ENA_CHAN_INR_SHIFT 3 /* DCS_ENA_CHAN_INR */ +#define WM8961_DCS_ENA_CHAN_INR_WIDTH 1 /* DCS_ENA_CHAN_INR */ +#define WM8961_DCS_TRIG_STARTUP_INR 0x0004 /* DCS_TRIG_STARTUP_INR */ +#define WM8961_DCS_TRIG_STARTUP_INR_MASK 0x0004 /* DCS_TRIG_STARTUP_INR */ +#define WM8961_DCS_TRIG_STARTUP_INR_SHIFT 2 /* DCS_TRIG_STARTUP_INR */ +#define WM8961_DCS_TRIG_STARTUP_INR_WIDTH 1 /* DCS_TRIG_STARTUP_INR */ +#define WM8961_DCS_TRIG_SERIES_INR 0x0001 /* DCS_TRIG_SERIES_INR */ +#define WM8961_DCS_TRIG_SERIES_INR_MASK 0x0001 /* DCS_TRIG_SERIES_INR */ +#define WM8961_DCS_TRIG_SERIES_INR_SHIFT 0 /* DCS_TRIG_SERIES_INR */ +#define WM8961_DCS_TRIG_SERIES_INR_WIDTH 1 /* DCS_TRIG_SERIES_INR */ + +/* + * R61 (0x3D) - DC Servo 1 + */ +#define WM8961_DCS_ENA_CHAN_HPL 0x0080 /* DCS_ENA_CHAN_HPL */ +#define WM8961_DCS_ENA_CHAN_HPL_MASK 0x0080 /* DCS_ENA_CHAN_HPL */ +#define WM8961_DCS_ENA_CHAN_HPL_SHIFT 7 /* DCS_ENA_CHAN_HPL */ +#define WM8961_DCS_ENA_CHAN_HPL_WIDTH 1 /* DCS_ENA_CHAN_HPL */ +#define WM8961_DCS_TRIG_STARTUP_HPL 0x0040 /* DCS_TRIG_STARTUP_HPL */ +#define WM8961_DCS_TRIG_STARTUP_HPL_MASK 0x0040 /* DCS_TRIG_STARTUP_HPL */ +#define WM8961_DCS_TRIG_STARTUP_HPL_SHIFT 6 /* DCS_TRIG_STARTUP_HPL */ +#define WM8961_DCS_TRIG_STARTUP_HPL_WIDTH 1 /* DCS_TRIG_STARTUP_HPL */ +#define WM8961_DCS_TRIG_SERIES_HPL 0x0010 /* DCS_TRIG_SERIES_HPL */ +#define WM8961_DCS_TRIG_SERIES_HPL_MASK 0x0010 /* DCS_TRIG_SERIES_HPL */ +#define WM8961_DCS_TRIG_SERIES_HPL_SHIFT 4 /* DCS_TRIG_SERIES_HPL */ +#define WM8961_DCS_TRIG_SERIES_HPL_WIDTH 1 /* DCS_TRIG_SERIES_HPL */ +#define WM8961_DCS_ENA_CHAN_HPR 0x0008 /* DCS_ENA_CHAN_HPR */ +#define WM8961_DCS_ENA_CHAN_HPR_MASK 0x0008 /* DCS_ENA_CHAN_HPR */ +#define WM8961_DCS_ENA_CHAN_HPR_SHIFT 3 /* DCS_ENA_CHAN_HPR */ +#define WM8961_DCS_ENA_CHAN_HPR_WIDTH 1 /* DCS_ENA_CHAN_HPR */ +#define WM8961_DCS_TRIG_STARTUP_HPR 0x0004 /* DCS_TRIG_STARTUP_HPR */ +#define WM8961_DCS_TRIG_STARTUP_HPR_MASK 0x0004 /* DCS_TRIG_STARTUP_HPR */ +#define WM8961_DCS_TRIG_STARTUP_HPR_SHIFT 2 /* DCS_TRIG_STARTUP_HPR */ +#define WM8961_DCS_TRIG_STARTUP_HPR_WIDTH 1 /* DCS_TRIG_STARTUP_HPR */ +#define WM8961_DCS_TRIG_SERIES_HPR 0x0001 /* DCS_TRIG_SERIES_HPR */ +#define WM8961_DCS_TRIG_SERIES_HPR_MASK 0x0001 /* DCS_TRIG_SERIES_HPR */ +#define WM8961_DCS_TRIG_SERIES_HPR_SHIFT 0 /* DCS_TRIG_SERIES_HPR */ +#define WM8961_DCS_TRIG_SERIES_HPR_WIDTH 1 /* DCS_TRIG_SERIES_HPR */ + +/* + * R63 (0x3F) - DC Servo 3 + */ +#define WM8961_DCS_FILT_BW_SERIES_MASK 0x0030 /* DCS_FILT_BW_SERIES - [5:4] */ +#define WM8961_DCS_FILT_BW_SERIES_SHIFT 4 /* DCS_FILT_BW_SERIES - [5:4] */ +#define WM8961_DCS_FILT_BW_SERIES_WIDTH 2 /* DCS_FILT_BW_SERIES - [5:4] */ + +/* + * R65 (0x41) - DC Servo 5 + */ +#define WM8961_DCS_SERIES_NO_HP_MASK 0x007F /* DCS_SERIES_NO_HP - [6:0] */ +#define WM8961_DCS_SERIES_NO_HP_SHIFT 0 /* DCS_SERIES_NO_HP - [6:0] */ +#define WM8961_DCS_SERIES_NO_HP_WIDTH 7 /* DCS_SERIES_NO_HP - [6:0] */ + +/* + * R68 (0x44) - Analogue PGA Bias + */ +#define WM8961_HP_PGAS_BIAS_MASK 0x0007 /* HP_PGAS_BIAS - [2:0] */ +#define WM8961_HP_PGAS_BIAS_SHIFT 0 /* HP_PGAS_BIAS - [2:0] */ +#define WM8961_HP_PGAS_BIAS_WIDTH 3 /* HP_PGAS_BIAS - [2:0] */ + +/* + * R69 (0x45) - Analogue HP 0 + */ +#define WM8961_HPL_RMV_SHORT 0x0080 /* HPL_RMV_SHORT */ +#define WM8961_HPL_RMV_SHORT_MASK 0x0080 /* HPL_RMV_SHORT */ +#define WM8961_HPL_RMV_SHORT_SHIFT 7 /* HPL_RMV_SHORT */ +#define WM8961_HPL_RMV_SHORT_WIDTH 1 /* HPL_RMV_SHORT */ +#define WM8961_HPL_ENA_OUTP 0x0040 /* HPL_ENA_OUTP */ +#define WM8961_HPL_ENA_OUTP_MASK 0x0040 /* HPL_ENA_OUTP */ +#define WM8961_HPL_ENA_OUTP_SHIFT 6 /* HPL_ENA_OUTP */ +#define WM8961_HPL_ENA_OUTP_WIDTH 1 /* HPL_ENA_OUTP */ +#define WM8961_HPL_ENA_DLY 0x0020 /* HPL_ENA_DLY */ +#define WM8961_HPL_ENA_DLY_MASK 0x0020 /* HPL_ENA_DLY */ +#define WM8961_HPL_ENA_DLY_SHIFT 5 /* HPL_ENA_DLY */ +#define WM8961_HPL_ENA_DLY_WIDTH 1 /* HPL_ENA_DLY */ +#define WM8961_HPL_ENA 0x0010 /* HPL_ENA */ +#define WM8961_HPL_ENA_MASK 0x0010 /* HPL_ENA */ +#define WM8961_HPL_ENA_SHIFT 4 /* HPL_ENA */ +#define WM8961_HPL_ENA_WIDTH 1 /* HPL_ENA */ +#define WM8961_HPR_RMV_SHORT 0x0008 /* HPR_RMV_SHORT */ +#define WM8961_HPR_RMV_SHORT_MASK 0x0008 /* HPR_RMV_SHORT */ +#define WM8961_HPR_RMV_SHORT_SHIFT 3 /* HPR_RMV_SHORT */ +#define WM8961_HPR_RMV_SHORT_WIDTH 1 /* HPR_RMV_SHORT */ +#define WM8961_HPR_ENA_OUTP 0x0004 /* HPR_ENA_OUTP */ +#define WM8961_HPR_ENA_OUTP_MASK 0x0004 /* HPR_ENA_OUTP */ +#define WM8961_HPR_ENA_OUTP_SHIFT 2 /* HPR_ENA_OUTP */ +#define WM8961_HPR_ENA_OUTP_WIDTH 1 /* HPR_ENA_OUTP */ +#define WM8961_HPR_ENA_DLY 0x0002 /* HPR_ENA_DLY */ +#define WM8961_HPR_ENA_DLY_MASK 0x0002 /* HPR_ENA_DLY */ +#define WM8961_HPR_ENA_DLY_SHIFT 1 /* HPR_ENA_DLY */ +#define WM8961_HPR_ENA_DLY_WIDTH 1 /* HPR_ENA_DLY */ +#define WM8961_HPR_ENA 0x0001 /* HPR_ENA */ +#define WM8961_HPR_ENA_MASK 0x0001 /* HPR_ENA */ +#define WM8961_HPR_ENA_SHIFT 0 /* HPR_ENA */ +#define WM8961_HPR_ENA_WIDTH 1 /* HPR_ENA */ + +/* + * R71 (0x47) - Analogue HP 2 + */ +#define WM8961_HPL_VOL_MASK 0x01C0 /* HPL_VOL - [8:6] */ +#define WM8961_HPL_VOL_SHIFT 6 /* HPL_VOL - [8:6] */ +#define WM8961_HPL_VOL_WIDTH 3 /* HPL_VOL - [8:6] */ +#define WM8961_HPR_VOL_MASK 0x0038 /* HPR_VOL - [5:3] */ +#define WM8961_HPR_VOL_SHIFT 3 /* HPR_VOL - [5:3] */ +#define WM8961_HPR_VOL_WIDTH 3 /* HPR_VOL - [5:3] */ +#define WM8961_HP_BIAS_BOOST_MASK 0x0007 /* HP_BIAS_BOOST - [2:0] */ +#define WM8961_HP_BIAS_BOOST_SHIFT 0 /* HP_BIAS_BOOST - [2:0] */ +#define WM8961_HP_BIAS_BOOST_WIDTH 3 /* HP_BIAS_BOOST - [2:0] */ + +/* + * R72 (0x48) - Charge Pump 1 + */ +#define WM8961_CP_ENA 0x0001 /* CP_ENA */ +#define WM8961_CP_ENA_MASK 0x0001 /* CP_ENA */ +#define WM8961_CP_ENA_SHIFT 0 /* CP_ENA */ +#define WM8961_CP_ENA_WIDTH 1 /* CP_ENA */ + +/* + * R82 (0x52) - Charge Pump B + */ +#define WM8961_CP_DYN_PWR_MASK 0x0003 /* CP_DYN_PWR - [1:0] */ +#define WM8961_CP_DYN_PWR_SHIFT 0 /* CP_DYN_PWR - [1:0] */ +#define WM8961_CP_DYN_PWR_WIDTH 2 /* CP_DYN_PWR - [1:0] */ + +/* + * R87 (0x57) - Write Sequencer 1 + */ +#define WM8961_WSEQ_ENA 0x0020 /* WSEQ_ENA */ +#define WM8961_WSEQ_ENA_MASK 0x0020 /* WSEQ_ENA */ +#define WM8961_WSEQ_ENA_SHIFT 5 /* WSEQ_ENA */ +#define WM8961_WSEQ_ENA_WIDTH 1 /* WSEQ_ENA */ +#define WM8961_WSEQ_WRITE_INDEX_MASK 0x001F /* WSEQ_WRITE_INDEX - [4:0] */ +#define WM8961_WSEQ_WRITE_INDEX_SHIFT 0 /* WSEQ_WRITE_INDEX - [4:0] */ +#define WM8961_WSEQ_WRITE_INDEX_WIDTH 5 /* WSEQ_WRITE_INDEX - [4:0] */ + +/* + * R88 (0x58) - Write Sequencer 2 + */ +#define WM8961_WSEQ_EOS 0x0100 /* WSEQ_EOS */ +#define WM8961_WSEQ_EOS_MASK 0x0100 /* WSEQ_EOS */ +#define WM8961_WSEQ_EOS_SHIFT 8 /* WSEQ_EOS */ +#define WM8961_WSEQ_EOS_WIDTH 1 /* WSEQ_EOS */ +#define WM8961_WSEQ_ADDR_MASK 0x00FF /* WSEQ_ADDR - [7:0] */ +#define WM8961_WSEQ_ADDR_SHIFT 0 /* WSEQ_ADDR - [7:0] */ +#define WM8961_WSEQ_ADDR_WIDTH 8 /* WSEQ_ADDR - [7:0] */ + +/* + * R89 (0x59) - Write Sequencer 3 + */ +#define WM8961_WSEQ_DATA_MASK 0x00FF /* WSEQ_DATA - [7:0] */ +#define WM8961_WSEQ_DATA_SHIFT 0 /* WSEQ_DATA - [7:0] */ +#define WM8961_WSEQ_DATA_WIDTH 8 /* WSEQ_DATA - [7:0] */ + +/* + * R90 (0x5A) - Write Sequencer 4 + */ +#define WM8961_WSEQ_ABORT 0x0100 /* WSEQ_ABORT */ +#define WM8961_WSEQ_ABORT_MASK 0x0100 /* WSEQ_ABORT */ +#define WM8961_WSEQ_ABORT_SHIFT 8 /* WSEQ_ABORT */ +#define WM8961_WSEQ_ABORT_WIDTH 1 /* WSEQ_ABORT */ +#define WM8961_WSEQ_START 0x0080 /* WSEQ_START */ +#define WM8961_WSEQ_START_MASK 0x0080 /* WSEQ_START */ +#define WM8961_WSEQ_START_SHIFT 7 /* WSEQ_START */ +#define WM8961_WSEQ_START_WIDTH 1 /* WSEQ_START */ +#define WM8961_WSEQ_START_INDEX_MASK 0x003F /* WSEQ_START_INDEX - [5:0] */ +#define WM8961_WSEQ_START_INDEX_SHIFT 0 /* WSEQ_START_INDEX - [5:0] */ +#define WM8961_WSEQ_START_INDEX_WIDTH 6 /* WSEQ_START_INDEX - [5:0] */ + +/* + * R91 (0x5B) - Write Sequencer 5 + */ +#define WM8961_WSEQ_DATA_WIDTH_MASK 0x0070 /* WSEQ_DATA_WIDTH - [6:4] */ +#define WM8961_WSEQ_DATA_WIDTH_SHIFT 4 /* WSEQ_DATA_WIDTH - [6:4] */ +#define WM8961_WSEQ_DATA_WIDTH_WIDTH 3 /* WSEQ_DATA_WIDTH - [6:4] */ +#define WM8961_WSEQ_DATA_START_MASK 0x000F /* WSEQ_DATA_START - [3:0] */ +#define WM8961_WSEQ_DATA_START_SHIFT 0 /* WSEQ_DATA_START - [3:0] */ +#define WM8961_WSEQ_DATA_START_WIDTH 4 /* WSEQ_DATA_START - [3:0] */ + +/* + * R92 (0x5C) - Write Sequencer 6 + */ +#define WM8961_WSEQ_DELAY_MASK 0x000F /* WSEQ_DELAY - [3:0] */ +#define WM8961_WSEQ_DELAY_SHIFT 0 /* WSEQ_DELAY - [3:0] */ +#define WM8961_WSEQ_DELAY_WIDTH 4 /* WSEQ_DELAY - [3:0] */ + +/* + * R93 (0x5D) - Write Sequencer 7 + */ +#define WM8961_WSEQ_BUSY 0x0001 /* WSEQ_BUSY */ +#define WM8961_WSEQ_BUSY_MASK 0x0001 /* WSEQ_BUSY */ +#define WM8961_WSEQ_BUSY_SHIFT 0 /* WSEQ_BUSY */ +#define WM8961_WSEQ_BUSY_WIDTH 1 /* WSEQ_BUSY */ + +/* + * R252 (0xFC) - General test 1 + */ +#define WM8961_ARA_ENA 0x0002 /* ARA_ENA */ +#define WM8961_ARA_ENA_MASK 0x0002 /* ARA_ENA */ +#define WM8961_ARA_ENA_SHIFT 1 /* ARA_ENA */ +#define WM8961_ARA_ENA_WIDTH 1 /* ARA_ENA */ +#define WM8961_AUTO_INC 0x0001 /* AUTO_INC */ +#define WM8961_AUTO_INC_MASK 0x0001 /* AUTO_INC */ +#define WM8961_AUTO_INC_SHIFT 0 /* AUTO_INC */ +#define WM8961_AUTO_INC_WIDTH 1 /* AUTO_INC */ + +#endif From df205936d5d1dfec9a52c90af77bb54a2c9c9728 Mon Sep 17 00:00:00 2001 From: "Lopez Cruz, Misael" Date: Thu, 11 Jun 2009 17:35:27 -0500 Subject: [PATCH 11/26] ASoC: Zoom2: Add machine driver for Zoom2 board Add support for Zoom2 board. Zoom2 machine driver connects both codec DAIs (audio and voice) to omap3 McBSP ports in the following way: HiFi <-> McBSP2 Voice <-> McBSP3 The zoom2 driver has the following DAPM widgets: * Ext Mic: MAINMIC, SUBMIC (with bias) * Ext Spk: HFL, HFR * Headset Stereophone: HSOL, HSOR * Headset Mic: HSMIC (with bias) * Aux In: AUXL, AUXR Signed-off-by: Misael Lopez Cruz Signed-off-by: Mark Brown --- sound/soc/omap/Kconfig | 7 + sound/soc/omap/Makefile | 2 + sound/soc/omap/zoom2.c | 301 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 310 insertions(+) create mode 100644 sound/soc/omap/zoom2.c diff --git a/sound/soc/omap/Kconfig b/sound/soc/omap/Kconfig index b771238662b6..a5a90e594535 100644 --- a/sound/soc/omap/Kconfig +++ b/sound/soc/omap/Kconfig @@ -72,4 +72,11 @@ config SND_OMAP_SOC_OMAP3_BEAGLE help Say Y if you want to add support for SoC audio on the Beagleboard. +config SND_OMAP_SOC_ZOOM2 + tristate "SoC Audio support for Zoom2" + depends on TWL4030_CORE && SND_OMAP_SOC && MACH_OMAP_ZOOM2 + select SND_OMAP_SOC_MCBSP + select SND_SOC_TWL4030 + help + Say Y if you want to add support for Soc audio on Zoom2 board. diff --git a/sound/soc/omap/Makefile b/sound/soc/omap/Makefile index a37f49862389..fefc48f02bd3 100644 --- a/sound/soc/omap/Makefile +++ b/sound/soc/omap/Makefile @@ -14,6 +14,7 @@ snd-soc-omap3evm-objs := omap3evm.o snd-soc-sdp3430-objs := sdp3430.o snd-soc-omap3pandora-objs := omap3pandora.o snd-soc-omap3beagle-objs := omap3beagle.o +snd-soc-zoom2-objs := zoom2.o obj-$(CONFIG_SND_OMAP_SOC_N810) += snd-soc-n810.o obj-$(CONFIG_SND_OMAP_SOC_OSK5912) += snd-soc-osk5912.o @@ -23,3 +24,4 @@ obj-$(CONFIG_MACH_OMAP3EVM) += snd-soc-omap3evm.o obj-$(CONFIG_SND_OMAP_SOC_SDP3430) += snd-soc-sdp3430.o obj-$(CONFIG_SND_OMAP_SOC_OMAP3_PANDORA) += snd-soc-omap3pandora.o obj-$(CONFIG_SND_OMAP_SOC_OMAP3_BEAGLE) += snd-soc-omap3beagle.o +obj-$(CONFIG_SND_OMAP_SOC_ZOOM2) += snd-soc-zoom2.o diff --git a/sound/soc/omap/zoom2.c b/sound/soc/omap/zoom2.c new file mode 100644 index 000000000000..be2e307fdf72 --- /dev/null +++ b/sound/soc/omap/zoom2.c @@ -0,0 +1,301 @@ +/* + * zoom2.c -- SoC audio for Zoom2 + * + * Author: Misael Lopez Cruz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "omap-mcbsp.h" +#include "omap-pcm.h" +#include "../codecs/twl4030.h" + +#define ZOOM2_HEADSET_MUX_GPIO (OMAP_MAX_GPIO_LINES + 15) + +static int zoom2_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + int ret; + + /* Set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) { + printk(KERN_ERR "can't set codec DAI configuration\n"); + return ret; + } + + /* Set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) { + printk(KERN_ERR "can't set cpu DAI configuration\n"); + return ret; + } + + /* Set the codec system clock for DAC and ADC */ + ret = snd_soc_dai_set_sysclk(codec_dai, 0, 26000000, + SND_SOC_CLOCK_IN); + if (ret < 0) { + printk(KERN_ERR "can't set codec system clock\n"); + return ret; + } + + return 0; +} + +static struct snd_soc_ops zoom2_ops = { + .hw_params = zoom2_hw_params, +}; + +static int zoom2_hw_voice_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + int ret; + + /* Set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_IB_NF | + SND_SOC_DAIFMT_CBS_CFM); + if (ret) { + printk(KERN_ERR "can't set codec DAI configuration\n"); + return ret; + } + + /* Set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_IB_NF | + SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) { + printk(KERN_ERR "can't set cpu DAI configuration\n"); + return ret; + } + + /* Set the codec system clock for DAC and ADC */ + ret = snd_soc_dai_set_sysclk(codec_dai, 0, 26000000, + SND_SOC_CLOCK_IN); + if (ret < 0) { + printk(KERN_ERR "can't set codec system clock\n"); + return ret; + } + + return 0; +} + +static struct snd_soc_ops zoom2_voice_ops = { + .hw_params = zoom2_hw_voice_params, +}; + +/* Zoom2 machine DAPM */ +static const struct snd_soc_dapm_widget zoom2_twl4030_dapm_widgets[] = { + SND_SOC_DAPM_MIC("Ext Mic", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_HP("Headset Stereophone", NULL), + SND_SOC_DAPM_LINE("Aux In", NULL), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* External Mics: MAINMIC, SUBMIC with bias*/ + {"MAINMIC", NULL, "Mic Bias 1"}, + {"SUBMIC", NULL, "Mic Bias 2"}, + {"Mic Bias 1", NULL, "Ext Mic"}, + {"Mic Bias 2", NULL, "Ext Mic"}, + + /* External Speakers: HFL, HFR */ + {"Ext Spk", NULL, "HFL"}, + {"Ext Spk", NULL, "HFR"}, + + /* Headset Stereophone: HSOL, HSOR */ + {"Headset Stereophone", NULL, "HSOL"}, + {"Headset Stereophone", NULL, "HSOR"}, + + /* Headset Mic: HSMIC with bias */ + {"HSMIC", NULL, "Headset Mic Bias"}, + {"Headset Mic Bias", NULL, "Headset Mic"}, + + /* Aux In: AUXL, AUXR */ + {"Aux In", NULL, "AUXL"}, + {"Aux In", NULL, "AUXR"}, +}; + +static int zoom2_twl4030_init(struct snd_soc_codec *codec) +{ + int ret; + + /* Add Zoom2 specific widgets */ + ret = snd_soc_dapm_new_controls(codec, zoom2_twl4030_dapm_widgets, + ARRAY_SIZE(zoom2_twl4030_dapm_widgets)); + if (ret) + return ret; + + /* Set up Zoom2 specific audio path audio_map */ + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + /* Zoom2 connected pins */ + snd_soc_dapm_enable_pin(codec, "Ext Mic"); + snd_soc_dapm_enable_pin(codec, "Ext Spk"); + snd_soc_dapm_enable_pin(codec, "Headset Mic"); + snd_soc_dapm_enable_pin(codec, "Headset Stereophone"); + snd_soc_dapm_enable_pin(codec, "Aux In"); + + /* TWL4030 not connected pins */ + snd_soc_dapm_nc_pin(codec, "CARKITMIC"); + snd_soc_dapm_nc_pin(codec, "DIGIMIC0"); + snd_soc_dapm_nc_pin(codec, "DIGIMIC1"); + + snd_soc_dapm_nc_pin(codec, "OUTL"); + snd_soc_dapm_nc_pin(codec, "OUTR"); + snd_soc_dapm_nc_pin(codec, "EARPIECE"); + snd_soc_dapm_nc_pin(codec, "PREDRIVEL"); + snd_soc_dapm_nc_pin(codec, "PREDRIVER"); + snd_soc_dapm_nc_pin(codec, "CARKITL"); + snd_soc_dapm_nc_pin(codec, "CARKITR"); + + ret = snd_soc_dapm_sync(codec); + + return ret; +} + +static int zoom2_twl4030_voice_init(struct snd_soc_codec *codec) +{ + unsigned short reg; + + /* Enable voice interface */ + reg = codec->read(codec, TWL4030_REG_VOICE_IF); + reg |= TWL4030_VIF_DIN_EN | TWL4030_VIF_DOUT_EN | TWL4030_VIF_EN; + codec->write(codec, TWL4030_REG_VOICE_IF, reg); + + return 0; +} + +/* Digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link zoom2_dai[] = { + { + .name = "TWL4030 I2S", + .stream_name = "TWL4030 Audio", + .cpu_dai = &omap_mcbsp_dai[0], + .codec_dai = &twl4030_dai[TWL4030_DAI_HIFI], + .init = zoom2_twl4030_init, + .ops = &zoom2_ops, + }, + { + .name = "TWL4030 PCM", + .stream_name = "TWL4030 Voice", + .cpu_dai = &omap_mcbsp_dai[1], + .codec_dai = &twl4030_dai[TWL4030_DAI_VOICE], + .init = zoom2_twl4030_voice_init, + .ops = &zoom2_voice_ops, + }, +}; + +/* Audio machine driver */ +static struct snd_soc_card snd_soc_zoom2 = { + .name = "Zoom2", + .platform = &omap_soc_platform, + .dai_link = zoom2_dai, + .num_links = ARRAY_SIZE(zoom2_dai), +}; + +/* twl4030 setup */ +static struct twl4030_setup_data twl4030_setup = { + .ramp_delay_value = 2, /* 81 ms */ + .sysclk = 26000, +}; + +/* Audio subsystem */ +static struct snd_soc_device zoom2_snd_devdata = { + .card = &snd_soc_zoom2, + .codec_dev = &soc_codec_dev_twl4030, + .codec_data = &twl4030_setup, +}; + +static struct platform_device *zoom2_snd_device; + +static int __init zoom2_soc_init(void) +{ + int ret; + + if (!machine_is_omap_zoom2()) { + pr_debug("Not Zoom2!\n"); + return -ENODEV; + } + printk(KERN_INFO "Zoom2 SoC init\n"); + + zoom2_snd_device = platform_device_alloc("soc-audio", -1); + if (!zoom2_snd_device) { + printk(KERN_ERR "Platform device allocation failed\n"); + return -ENOMEM; + } + + platform_set_drvdata(zoom2_snd_device, &zoom2_snd_devdata); + zoom2_snd_devdata.dev = &zoom2_snd_device->dev; + *(unsigned int *)zoom2_dai[0].cpu_dai->private_data = 1; /* McBSP2 */ + *(unsigned int *)zoom2_dai[1].cpu_dai->private_data = 2; /* McBSP3 */ + + ret = platform_device_add(zoom2_snd_device); + if (ret) + goto err1; + + BUG_ON(gpio_request(ZOOM2_HEADSET_MUX_GPIO, "hs_mux") < 0); + gpio_direction_output(ZOOM2_HEADSET_MUX_GPIO, 0); + + return 0; + +err1: + printk(KERN_ERR "Unable to add platform device\n"); + platform_device_put(zoom2_snd_device); + + return ret; +} +module_init(zoom2_soc_init); + +static void __exit zoom2_soc_exit(void) +{ + gpio_free(ZOOM2_HEADSET_MUX_GPIO); + + platform_device_unregister(zoom2_snd_device); +} +module_exit(zoom2_soc_exit); + +MODULE_AUTHOR("Misael Lopez Cruz "); +MODULE_DESCRIPTION("ALSA SoC Zoom2"); +MODULE_LICENSE("GPL"); + From 9e79261f302083cbc6aa95e0f778e3583b1ab36e Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 12 Jun 2009 17:27:07 +0100 Subject: [PATCH 12/26] ASoC: Automatically control WM8903 sloping stopband filter For best performance the DAC sloping stopband filter should be enabled below 24kHz and not enabled above that so remove the user visible control for this and do it autonomously in the driver. Signed-off-by: Mark Brown --- sound/soc/codecs/wm8903.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c index d8a9222fbf74..3ebd770463e3 100644 --- a/sound/soc/codecs/wm8903.c +++ b/sound/soc/codecs/wm8903.c @@ -715,8 +715,6 @@ SOC_ENUM("DAC Soft Mute Rate", soft_mute), SOC_ENUM("DAC Mute Mode", mute_mode), SOC_SINGLE("DAC Mono Switch", WM8903_DAC_DIGITAL_1, 12, 1, 0), SOC_ENUM("DAC De-emphasis", dac_deemphasis), -SOC_SINGLE("DAC Sloping Stopband Filter Switch", - WM8903_DAC_DIGITAL_1, 11, 1, 0), SOC_ENUM("DAC Companding Mode", dac_companding), SOC_SINGLE("DAC Companding Switch", WM8903_AUDIO_INTERFACE_0, 1, 1, 0), @@ -1377,12 +1375,19 @@ static int wm8903_hw_params(struct snd_pcm_substream *substream, u16 aif3 = wm8903_read(codec, WM8903_AUDIO_INTERFACE_3); u16 clock0 = wm8903_read(codec, WM8903_CLOCK_RATES_0); u16 clock1 = wm8903_read(codec, WM8903_CLOCK_RATES_1); + u16 dac_digital1 = wm8903_read(codec, WM8903_DAC_DIGITAL_1); if (substream == wm8903->slave_substream) { dev_dbg(&i2c->dev, "Ignoring hw_params for slave substream\n"); return 0; } + /* Enable sloping stopband filter for low sample rates */ + if (fs <= 24000) + dac_digital1 |= WM8903_DAC_SB_FILT; + else + dac_digital1 &= ~WM8903_DAC_SB_FILT; + /* Configure sample rate logic for DSP - choose nearest rate */ dsp_config = 0; best_val = abs(sample_rates[dsp_config].rate - fs); @@ -1507,6 +1512,7 @@ static int wm8903_hw_params(struct snd_pcm_substream *substream, wm8903_write(codec, WM8903_AUDIO_INTERFACE_1, aif1); wm8903_write(codec, WM8903_AUDIO_INTERFACE_2, aif2); wm8903_write(codec, WM8903_AUDIO_INTERFACE_3, aif3); + wm8903_write(codec, WM8903_DAC_DIGITAL_1, dac_digital1); return 0; } From 21002e20767292d85701154cdf12a591b45f0979 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 12 Jun 2009 17:27:52 +0100 Subject: [PATCH 13/26] ASoC: Automatically manage WM8900 sloping stopband filter For best performance the DAC sloping stopband filter should be enabled below 24kHz and not enabled above that so remove the user visible control for this and do it autonomously in the driver. Signed-off-by: Mark Brown --- sound/soc/codecs/wm8900.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/sound/soc/codecs/wm8900.c b/sound/soc/codecs/wm8900.c index 3c78945244b8..de99206a0103 100644 --- a/sound/soc/codecs/wm8900.c +++ b/sound/soc/codecs/wm8900.c @@ -116,6 +116,7 @@ #define WM8900_REG_CLOCKING2_DAC_CLKDIV 0x1c #define WM8900_REG_DACCTRL_MUTE 0x004 +#define WM8900_REG_DACCTRL_DAC_SB_FILT 0x100 #define WM8900_REG_DACCTRL_AIF_LRCLKRATE 0x400 #define WM8900_REG_AUDIO3_ADCLRC_DIR 0x0800 @@ -439,7 +440,6 @@ SOC_SINGLE("DAC Soft Mute Switch", WM8900_REG_DACCTRL, 6, 1, 1), SOC_ENUM("DAC Mute Rate", dac_mute_rate), SOC_SINGLE("DAC Mono Switch", WM8900_REG_DACCTRL, 9, 1, 0), SOC_ENUM("DAC Deemphasis", dac_deemphasis), -SOC_SINGLE("DAC Sloping Stopband Filter Switch", WM8900_REG_DACCTRL, 8, 1, 0), SOC_SINGLE("DAC Sigma-Delta Modulator Clock Switch", WM8900_REG_DACCTRL, 12, 1, 0), @@ -743,6 +743,17 @@ static int wm8900_hw_params(struct snd_pcm_substream *substream, wm8900_write(codec, WM8900_REG_AUDIO1, reg); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + reg = wm8900_read(codec, WM8900_REG_DACCTRL); + + if (params_rate(params) <= 24000) + reg |= WM8900_REG_DACCTRL_DAC_SB_FILT; + else + reg &= ~WM8900_REG_DACCTRL_DAC_SB_FILT; + + wm8900_write(codec, WM8900_REG_DACCTRL, reg); + } + return 0; } From 619439998ac32953d737fbe2dc82eb67024547d0 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 12 Jun 2009 22:56:59 +0100 Subject: [PATCH 14/26] ASoC: Automatically manage WM8350 sloping stopband filter For best performance the DAC sloping stopband filter should be enabled below 24kHz and not enabled above that so remove the user visible control for this and do it autonomously in the driver. Signed-off-by: Mark Brown --- sound/soc/codecs/wm8350.c | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/sound/soc/codecs/wm8350.c b/sound/soc/codecs/wm8350.c index e7348d341b76..f6bb59951f96 100644 --- a/sound/soc/codecs/wm8350.c +++ b/sound/soc/codecs/wm8350.c @@ -406,7 +406,6 @@ static const char *wm8350_deemp[] = { "None", "32kHz", "44.1kHz", "48kHz" }; static const char *wm8350_pol[] = { "Normal", "Inv R", "Inv L", "Inv L & R" }; static const char *wm8350_dacmutem[] = { "Normal", "Soft" }; static const char *wm8350_dacmutes[] = { "Fast", "Slow" }; -static const char *wm8350_dacfilter[] = { "Normal", "Sloping" }; static const char *wm8350_adcfilter[] = { "None", "High Pass" }; static const char *wm8350_adchp[] = { "44.1kHz", "8kHz", "16kHz", "32kHz" }; static const char *wm8350_lr[] = { "Left", "Right" }; @@ -416,7 +415,6 @@ static const struct soc_enum wm8350_enum[] = { SOC_ENUM_SINGLE(WM8350_DAC_CONTROL, 0, 4, wm8350_pol), SOC_ENUM_SINGLE(WM8350_DAC_MUTE_VOLUME, 14, 2, wm8350_dacmutem), SOC_ENUM_SINGLE(WM8350_DAC_MUTE_VOLUME, 13, 2, wm8350_dacmutes), - SOC_ENUM_SINGLE(WM8350_DAC_MUTE_VOLUME, 12, 2, wm8350_dacfilter), SOC_ENUM_SINGLE(WM8350_ADC_CONTROL, 15, 2, wm8350_adcfilter), SOC_ENUM_SINGLE(WM8350_ADC_CONTROL, 8, 4, wm8350_adchp), SOC_ENUM_SINGLE(WM8350_ADC_CONTROL, 0, 4, wm8350_pol), @@ -444,10 +442,9 @@ static const struct snd_kcontrol_new wm8350_snd_controls[] = { 0, 255, 0, dac_pcm_tlv), SOC_ENUM("Playback PCM Mute Function", wm8350_enum[2]), SOC_ENUM("Playback PCM Mute Speed", wm8350_enum[3]), - SOC_ENUM("Playback PCM Filter", wm8350_enum[4]), - SOC_ENUM("Capture PCM Filter", wm8350_enum[5]), - SOC_ENUM("Capture PCM HP Filter", wm8350_enum[6]), - SOC_ENUM("Capture ADC Inversion", wm8350_enum[7]), + SOC_ENUM("Capture PCM Filter", wm8350_enum[4]), + SOC_ENUM("Capture PCM HP Filter", wm8350_enum[5]), + SOC_ENUM("Capture ADC Inversion", wm8350_enum[6]), SOC_WM8350_DOUBLE_R_TLV("Capture PCM Volume", WM8350_ADC_DIGITAL_VOLUME_L, WM8350_ADC_DIGITAL_VOLUME_R, @@ -993,6 +990,7 @@ static int wm8350_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *codec_dai) { struct snd_soc_codec *codec = codec_dai->codec; + struct wm8350 *wm8350 = codec->control_data; u16 iface = wm8350_codec_read(codec, WM8350_AI_FORMATING) & ~WM8350_AIF_WL_MASK; @@ -1012,6 +1010,19 @@ static int wm8350_pcm_hw_params(struct snd_pcm_substream *substream, } wm8350_codec_write(codec, WM8350_AI_FORMATING, iface); + + /* The sloping stopband filter is recommended for use with + * lower sample rates to improve performance. + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (params_rate(params) < 24000) + wm8350_set_bits(wm8350, WM8350_DAC_MUTE_VOLUME, + WM8350_DAC_SB_FILT); + else + wm8350_clear_bits(wm8350, WM8350_DAC_MUTE_VOLUME, + WM8350_DAC_SB_FILT); + } + return 0; } From 831dc0f10f7b2a4856094ff160c018bf19f77527 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 13 Jun 2009 19:55:02 +0100 Subject: [PATCH 15/26] ASoC: Add stub suspend and resume calls for ASoC subdevices Now that ASoC subdevices can be regular devices they can have normal suspend and resume calls from their buses. However, suspending them individually is not desirable since this can lead to problems such as pops and clicks from devices being suspended with their signals being amplified or clocks being stopped suddenly. This will be resolved by having the normal device model suspend and resume calls call into ASoC which will suspend the entire card while any of its components are suspended. At present this is not yet implemented but in order to aid the transition of drivers to the standard device model this patch adds API calls for the notifications. Signed-off-by: Mark Brown --- include/sound/soc.h | 5 +++++ sound/soc/soc-core.c | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/include/sound/soc.h b/include/sound/soc.h index 5297ba7e2c41..e6704c0a4404 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -192,6 +192,11 @@ void snd_soc_unregister_platform(struct snd_soc_platform *platform); int snd_soc_register_codec(struct snd_soc_codec *codec); void snd_soc_unregister_codec(struct snd_soc_codec *codec); +#ifdef CONFIG_PM +int snd_soc_suspend_device(struct device *dev); +int snd_soc_resume_device(struct device *dev); +#endif + /* pcm <-> DAI connect */ void snd_soc_free_pcms(struct snd_soc_device *socdev); int snd_soc_new_pcms(struct snd_soc_device *socdev, int idx, const char *xid); diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index e1a920cd8953..44141178ff4a 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -788,6 +788,45 @@ static int soc_resume(struct platform_device *pdev) return 0; } +/** + * snd_soc_suspend_device: Notify core of device suspend + * + * @dev: Device being suspended. + * + * In order to ensure that the entire audio subsystem is suspended in a + * coordinated fashion ASoC devices should suspend themselves when + * called by ASoC. When the standard kernel suspend process asks the + * device to suspend it should call this function to initiate a suspend + * of the entire ASoC card. + * + * \note Currently this function is stubbed out. + */ +int snd_soc_suspend_device(struct device *dev) +{ + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_suspend_device); + +/** + * snd_soc_resume_device: Notify core of device resume + * + * @dev: Device being resumed. + * + * In order to ensure that the entire audio subsystem is resumed in a + * coordinated fashion ASoC devices should resume themselves when called + * by ASoC. When the standard kernel resume process asks the device + * to resume it should call this function. Once all the components of + * the card have notified that they are ready to be resumed the card + * will be resumed. + * + * \note Currently this function is stubbed out. + */ +int snd_soc_resume_device(struct device *dev) +{ + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_resume_device); + #else #define soc_suspend NULL #define soc_resume NULL From b3b50b3f31775be5d2e441618bbc1c5cbee4d9f1 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 13 Jun 2009 22:30:18 +0100 Subject: [PATCH 16/26] ASoC: Add suspend and resume callbacks to Wolfson CODEC drivers Signed-off-by: Mark Brown --- sound/soc/codecs/wm8350.c | 17 +++++++++++++++++ sound/soc/codecs/wm8400.c | 17 +++++++++++++++++ sound/soc/codecs/wm8580.c | 17 +++++++++++++++++ sound/soc/codecs/wm8731.c | 39 +++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/wm8753.c | 35 +++++++++++++++++++++++++++++++++++ sound/soc/codecs/wm8900.c | 17 +++++++++++++++++ sound/soc/codecs/wm8903.c | 17 +++++++++++++++++ sound/soc/codecs/wm8940.c | 17 +++++++++++++++++ sound/soc/codecs/wm8960.c | 17 +++++++++++++++++ sound/soc/codecs/wm8988.c | 34 ++++++++++++++++++++++++++++++++++ sound/soc/codecs/wm9081.c | 17 +++++++++++++++++ 11 files changed, 244 insertions(+) diff --git a/sound/soc/codecs/wm8350.c b/sound/soc/codecs/wm8350.c index f6bb59951f96..4ded0e3a35e0 100644 --- a/sound/soc/codecs/wm8350.c +++ b/sound/soc/codecs/wm8350.c @@ -1671,6 +1671,21 @@ static int __devexit wm8350_codec_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_PM +static int wm8350_codec_suspend(struct platform_device *pdev, pm_message_t m) +{ + return snd_soc_suspend_device(&pdev->dev); +} + +static int wm8350_codec_resume(struct platform_device *pdev) +{ + return snd_soc_resume_device(&pdev->dev); +} +#else +#define wm8350_codec_suspend NULL +#define wm8350_codec_resume NULL +#endif + static struct platform_driver wm8350_codec_driver = { .driver = { .name = "wm8350-codec", @@ -1678,6 +1693,8 @@ static struct platform_driver wm8350_codec_driver = { }, .probe = wm8350_codec_probe, .remove = __devexit_p(wm8350_codec_remove), + .suspend = wm8350_codec_suspend, + .resume = wm8350_codec_resume, }; static __init int wm8350_init(void) diff --git a/sound/soc/codecs/wm8400.c b/sound/soc/codecs/wm8400.c index 502eefac1ecd..0bf903f27564 100644 --- a/sound/soc/codecs/wm8400.c +++ b/sound/soc/codecs/wm8400.c @@ -1553,6 +1553,21 @@ static int __exit wm8400_codec_remove(struct platform_device *dev) return 0; } +#ifdef CONFIG_PM +static int wm8400_pdev_suspend(struct platform_device *pdev, pm_message_t msg) +{ + return snd_soc_suspend_device(&pdev->dev); +} + +static int wm8400_pdev_resume(struct platform_device *pdev) +{ + return snd_soc_resume_device(&pdev->dev); +} +#else +#define wm8400_pdev_suspend NULL +#define wm8400_pdev_resume NULL +#endif + static struct platform_driver wm8400_codec_driver = { .driver = { .name = "wm8400-codec", @@ -1560,6 +1575,8 @@ static struct platform_driver wm8400_codec_driver = { }, .probe = wm8400_codec_probe, .remove = __exit_p(wm8400_codec_remove), + .suspend = wm8400_pdev_suspend, + .resume = wm8400_pdev_resume, }; static int __init wm8400_codec_init(void) diff --git a/sound/soc/codecs/wm8580.c b/sound/soc/codecs/wm8580.c index 86c4b24db817..261ef101d4fc 100644 --- a/sound/soc/codecs/wm8580.c +++ b/sound/soc/codecs/wm8580.c @@ -995,6 +995,21 @@ static int wm8580_i2c_remove(struct i2c_client *client) return 0; } +#ifdef CONFIG_PM +static int wm8580_i2c_suspend(struct i2c_client *client, pm_message_t msg) +{ + return snd_soc_suspend_device(&client->dev); +} + +static int wm8580_i2c_resume(struct i2c_client *client) +{ + return snd_soc_resume_device(&client->dev); +} +#else +#define wm8580_i2c_suspend NULL +#define wm8580_i2c_resume NULL +#endif + static const struct i2c_device_id wm8580_i2c_id[] = { { "wm8580", 0 }, { } @@ -1008,6 +1023,8 @@ static struct i2c_driver wm8580_i2c_driver = { }, .probe = wm8580_i2c_probe, .remove = wm8580_i2c_remove, + .suspend = wm8580_i2c_suspend, + .resume = wm8580_i2c_resume, .id_table = wm8580_i2c_id, }; #endif diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c index 7a205876ef4f..d7f4788f7ace 100644 --- a/sound/soc/codecs/wm8731.c +++ b/sound/soc/codecs/wm8731.c @@ -460,6 +460,7 @@ struct snd_soc_dai wm8731_dai = { }; EXPORT_SYMBOL_GPL(wm8731_dai); +#ifdef CONFIG_PM static int wm8731_suspend(struct platform_device *pdev, pm_message_t state) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); @@ -488,6 +489,10 @@ static int wm8731_resume(struct platform_device *pdev) wm8731_set_bias_level(codec, codec->suspend_bias_level); return 0; } +#else +#define wm8731_suspend NULL +#define wm8731_resume NULL +#endif static int wm8731_probe(struct platform_device *pdev) { @@ -680,6 +685,21 @@ static int __devexit wm8731_spi_remove(struct spi_device *spi) return 0; } +#ifdef CONFIG_PM +static int wm8731_spi_suspend(struct spi_device *spi, pm_message_t msg) +{ + return snd_soc_suspend_device(&spi->dev); +} + +static int wm8731_spi_resume(struct spi_device *spi) +{ + return snd_soc_resume_device(&spi->dev); +} +#else +#define wm8731_spi_suspend NULL +#define wm8731_spi_resume NULL +#endif + static struct spi_driver wm8731_spi_driver = { .driver = { .name = "wm8731", @@ -687,6 +707,8 @@ static struct spi_driver wm8731_spi_driver = { .owner = THIS_MODULE, }, .probe = wm8731_spi_probe, + .suspend = wm8731_spi_suspend, + .resume = wm8731_spi_resume, .remove = __devexit_p(wm8731_spi_remove), }; #endif /* CONFIG_SPI_MASTER */ @@ -720,6 +742,21 @@ static __devexit int wm8731_i2c_remove(struct i2c_client *client) return 0; } +#ifdef CONFIG_PM +static int wm8731_i2c_suspend(struct i2c_client *i2c, pm_message_t msg) +{ + return snd_soc_suspend_device(&i2c->dev); +} + +static int wm8731_i2c_resume(struct i2c_client *i2c) +{ + return snd_soc_resume_device(&i2c->dev); +} +#else +#define wm8731_i2c_suspend NULL +#define wm8731_i2c_resume NULL +#endif + static const struct i2c_device_id wm8731_i2c_id[] = { { "wm8731", 0 }, { } @@ -733,6 +770,8 @@ static struct i2c_driver wm8731_i2c_driver = { }, .probe = wm8731_i2c_probe, .remove = __devexit_p(wm8731_i2c_remove), + .suspend = wm8731_i2c_suspend, + .resume = wm8731_i2c_resume, .id_table = wm8731_i2c_id, }; #endif diff --git a/sound/soc/codecs/wm8753.c b/sound/soc/codecs/wm8753.c index d28eeaceb857..370f7df03628 100644 --- a/sound/soc/codecs/wm8753.c +++ b/sound/soc/codecs/wm8753.c @@ -1766,6 +1766,21 @@ static int wm8753_i2c_remove(struct i2c_client *client) return 0; } +#ifdef CONFIG_PM +static int wm8753_i2c_suspend(struct i2c_client *client, pm_message_t msg) +{ + return snd_soc_suspend_device(&client->dev); +} + +static int wm8753_i2c_resume(struct i2c_client *client) +{ + return snd_soc_resume_device(&client->dev); +} +#else +#define wm8753_i2c_suspend NULL +#define wm8753_i2c_resume NULL +#endif + static const struct i2c_device_id wm8753_i2c_id[] = { { "wm8753", 0 }, { } @@ -1779,6 +1794,8 @@ static struct i2c_driver wm8753_i2c_driver = { }, .probe = wm8753_i2c_probe, .remove = wm8753_i2c_remove, + .suspend = wm8753_i2c_suspend, + .resume = wm8753_i2c_resume, .id_table = wm8753_i2c_id, }; #endif @@ -1834,6 +1851,22 @@ static int __devexit wm8753_spi_remove(struct spi_device *spi) return 0; } +#ifdef CONFIG_PM +static int wm8753_spi_suspend(struct spi_device *spi, pm_message_t msg) +{ + return snd_soc_suspend_device(&spi->dev); +} + +static int wm8753_spi_resume(struct spi_device *spi) +{ + return snd_soc_resume_device(&spi->dev); +} + +#else +#define wm8753_spi_suspend NULL +#define wm8753_spi_resume NULL +#endif + static struct spi_driver wm8753_spi_driver = { .driver = { .name = "wm8753", @@ -1842,6 +1875,8 @@ static struct spi_driver wm8753_spi_driver = { }, .probe = wm8753_spi_probe, .remove = __devexit_p(wm8753_spi_remove), + .suspend = wm8753_spi_suspend, + .resume = wm8753_spi_resume, }; #endif diff --git a/sound/soc/codecs/wm8900.c b/sound/soc/codecs/wm8900.c index de99206a0103..ac308993ac5a 100644 --- a/sound/soc/codecs/wm8900.c +++ b/sound/soc/codecs/wm8900.c @@ -1399,6 +1399,21 @@ static __devexit int wm8900_i2c_remove(struct i2c_client *client) return 0; } +#ifdef CONFIG_PM +static int wm8900_i2c_suspend(struct i2c_client *client, pm_message_t msg) +{ + return snd_soc_suspend_device(&client->dev); +} + +static int wm8900_i2c_resume(struct i2c_client *client) +{ + return snd_soc_resume_device(&client->dev); +} +#else +#define wm8900_i2c_suspend NULL +#define wm8900_i2c_resume NULL +#endif + static const struct i2c_device_id wm8900_i2c_id[] = { { "wm8900", 0 }, { } @@ -1412,6 +1427,8 @@ static struct i2c_driver wm8900_i2c_driver = { }, .probe = wm8900_i2c_probe, .remove = __devexit_p(wm8900_i2c_remove), + .suspend = wm8900_i2c_suspend, + .resume = wm8900_i2c_resume, .id_table = wm8900_i2c_id, }; diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c index 3ebd770463e3..6239af8fdf71 100644 --- a/sound/soc/codecs/wm8903.c +++ b/sound/soc/codecs/wm8903.c @@ -1731,6 +1731,21 @@ static __devexit int wm8903_i2c_remove(struct i2c_client *client) return 0; } +#ifdef CONFIG_PM +static int wm8903_i2c_suspend(struct i2c_client *client, pm_message_t msg) +{ + return snd_soc_suspend_device(&client->dev); +} + +static int wm8903_i2c_resume(struct i2c_client *client) +{ + return snd_soc_resume_device(&client->dev); +} +#else +#define wm8903_i2c_suspend NULL +#define wm8903_i2c_resume NULL +#endif + /* i2c codec control layer */ static const struct i2c_device_id wm8903_i2c_id[] = { { "wm8903", 0 }, @@ -1745,6 +1760,8 @@ static struct i2c_driver wm8903_i2c_driver = { }, .probe = wm8903_i2c_probe, .remove = __devexit_p(wm8903_i2c_remove), + .suspend = wm8903_i2c_suspend, + .resume = wm8903_i2c_resume, .id_table = wm8903_i2c_id, }; diff --git a/sound/soc/codecs/wm8940.c b/sound/soc/codecs/wm8940.c index b8e17d6bc1f7..b69210a77423 100644 --- a/sound/soc/codecs/wm8940.c +++ b/sound/soc/codecs/wm8940.c @@ -916,6 +916,21 @@ static int __devexit wm8940_i2c_remove(struct i2c_client *client) return 0; } +#ifdef CONFIG_PM +static int wm8940_i2c_suspend(struct i2c_client *client, pm_message_t msg) +{ + return snd_soc_suspend_device(&client->dev); +} + +static int wm8940_i2c_resume(struct i2c_client *client) +{ + return snd_soc_resume_device(&client->dev); +} +#else +#define wm8940_i2c_suspend NULL +#define wm8940_i2c_resume NULL +#endif + static const struct i2c_device_id wm8940_i2c_id[] = { { "wm8940", 0 }, { } @@ -929,6 +944,8 @@ static struct i2c_driver wm8940_i2c_driver = { }, .probe = wm8940_i2c_probe, .remove = __devexit_p(wm8940_i2c_remove), + .suspend = wm8940_i2c_suspend, + .resume = wm8940_i2c_resume, .id_table = wm8940_i2c_id, }; diff --git a/sound/soc/codecs/wm8960.c b/sound/soc/codecs/wm8960.c index e224d8add170..b7894d6dffc0 100644 --- a/sound/soc/codecs/wm8960.c +++ b/sound/soc/codecs/wm8960.c @@ -927,6 +927,21 @@ static __devexit int wm8960_i2c_remove(struct i2c_client *client) return 0; } +#ifdef CONFIG_PM +static int wm8960_i2c_suspend(struct i2c_client *client, pm_message_t msg) +{ + return snd_soc_suspend_device(&client->dev); +} + +static int wm8960_i2c_resume(struct i2c_client *client) +{ + return snd_soc_resume_device(&client->dev); +} +#else +#define wm8960_i2c_suspend NULL +#define wm8960_i2c_resume NULL +#endif + static const struct i2c_device_id wm8960_i2c_id[] = { { "wm8960", 0 }, { } @@ -940,6 +955,8 @@ static struct i2c_driver wm8960_i2c_driver = { }, .probe = wm8960_i2c_probe, .remove = __devexit_p(wm8960_i2c_remove), + .suspend = wm8960_i2c_suspend, + .resume = wm8960_i2c_resume, .id_table = wm8960_i2c_id, }; diff --git a/sound/soc/codecs/wm8988.c b/sound/soc/codecs/wm8988.c index c05f71803aa8..03fac6a0f805 100644 --- a/sound/soc/codecs/wm8988.c +++ b/sound/soc/codecs/wm8988.c @@ -981,6 +981,21 @@ static int wm8988_i2c_remove(struct i2c_client *client) return 0; } +#ifdef CONFIG_PM +static int wm8988_i2c_suspend(struct i2c_client *client, pm_message_t msg) +{ + return snd_soc_suspend_device(&client->dev); +} + +static int wm8988_i2c_resume(struct i2c_client *client) +{ + return snd_soc_resume_device(&client->dev); +} +#else +#define wm8988_i2c_suspend NULL +#define wm8988_i2c_resume NULL +#endif + static const struct i2c_device_id wm8988_i2c_id[] = { { "wm8988", 0 }, { } @@ -994,6 +1009,8 @@ static struct i2c_driver wm8988_i2c_driver = { }, .probe = wm8988_i2c_probe, .remove = wm8988_i2c_remove, + .suspend = wm8988_i2c_suspend, + .resume = wm8988_i2c_resume, .id_table = wm8988_i2c_id, }; #endif @@ -1051,6 +1068,21 @@ static int __devexit wm8988_spi_remove(struct spi_device *spi) return 0; } +#ifdef CONFIG_PM +static int wm8988_spi_suspend(struct spi_device *spi, pm_message_t msg) +{ + return snd_soc_suspend_device(&spi->dev); +} + +static int wm8988_spi_resume(struct spi_device *spi) +{ + return snd_soc_resume_device(&spi->dev); +} +#else +#define wm8988_spi_suspend NULL +#define wm8988_spi_resume NULL +#endif + static struct spi_driver wm8988_spi_driver = { .driver = { .name = "wm8988", @@ -1059,6 +1091,8 @@ static struct spi_driver wm8988_spi_driver = { }, .probe = wm8988_spi_probe, .remove = __devexit_p(wm8988_spi_remove), + .suspend = wm8988_spi_suspend, + .resume = wm8988_spi_resume, }; #endif diff --git a/sound/soc/codecs/wm9081.c b/sound/soc/codecs/wm9081.c index 86fc57e25f97..dbe20597d872 100644 --- a/sound/soc/codecs/wm9081.c +++ b/sound/soc/codecs/wm9081.c @@ -1492,6 +1492,21 @@ static __devexit int wm9081_i2c_remove(struct i2c_client *client) return 0; } +#ifdef CONFIG_PM +static int wm9081_i2c_suspend(struct i2c_client *client, pm_message_t msg) +{ + return snd_soc_suspend_device(&client->dev); +} + +static int wm9081_i2c_resume(struct i2c_client *client) +{ + return snd_soc_resume_device(&client->dev); +} +#else +#define wm9081_i2c_suspend NULL +#define wm9081_i2c_resume NULL +#endif + static const struct i2c_device_id wm9081_i2c_id[] = { { "wm9081", 0 }, { } @@ -1505,6 +1520,8 @@ static struct i2c_driver wm9081_i2c_driver = { }, .probe = wm9081_i2c_probe, .remove = __devexit_p(wm9081_i2c_remove), + .suspend = wm9081_i2c_suspend, + .resume = wm9081_i2c_resume, .id_table = wm9081_i2c_id, }; From 1abd91849990ed61d6468ffa8b7fc1ae61db4b1a Mon Sep 17 00:00:00 2001 From: Philipp Zabel Date: Mon, 15 Jun 2009 22:18:23 +0200 Subject: [PATCH 17/26] ASoC: UDA1380: refactor device registration This patch mostly follows commit 5998102b9095fdb7c67755812038612afea315c5 "ASoC: Refactor WM8731 device registration" to make UDA1380 use standard device instantiation. Similarly, the I2C device registration temporarily moves into the magician machine driver before it will find its final resting place in the board file. At the same time, platform specific configuration is moved to platform data and common power/reset GPIO handling moves into the codec driver. Signed-off-by: Philipp Zabel Signed-off-by: Mark Brown --- include/sound/uda1380.h | 22 +++ sound/soc/codecs/uda1380.c | 335 ++++++++++++++++++++----------------- sound/soc/codecs/uda1380.h | 8 - sound/soc/pxa/magician.c | 54 +++--- 4 files changed, 232 insertions(+), 187 deletions(-) create mode 100644 include/sound/uda1380.h diff --git a/include/sound/uda1380.h b/include/sound/uda1380.h new file mode 100644 index 000000000000..381319c7000c --- /dev/null +++ b/include/sound/uda1380.h @@ -0,0 +1,22 @@ +/* + * UDA1380 ALSA SoC Codec driver + * + * Copyright 2009 Philipp Zabel + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __UDA1380_H +#define __UDA1380_H + +struct uda1380_platform_data { + int gpio_power; + int gpio_reset; + int dac_clk; +#define UDA1380_DAC_CLK_SYSCLK 0 +#define UDA1380_DAC_CLK_WSPLL 1 +}; + +#endif /* __UDA1380_H */ diff --git a/sound/soc/codecs/uda1380.c b/sound/soc/codecs/uda1380.c index 5b21594e0e58..92ec03442154 100644 --- a/sound/soc/codecs/uda1380.c +++ b/sound/soc/codecs/uda1380.c @@ -5,9 +5,7 @@ * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * - * Copyright (c) 2007 Philipp Zabel - * Improved support for DAPM and audio routing/mixing capabilities, - * added TLV support. + * Copyright (c) 2007-2009 Philipp Zabel * * Modified by Richard Purdie to fit into SoC * codec model. @@ -19,26 +17,32 @@ #include #include #include -#include #include #include -#include +#include #include #include #include #include #include #include -#include #include #include #include +#include #include "uda1380.h" -static struct work_struct uda1380_work; static struct snd_soc_codec *uda1380_codec; +/* codec private data */ +struct uda1380_priv { + struct snd_soc_codec codec; + u16 reg_cache[UDA1380_CACHEREGNUM]; + unsigned int dac_clk; + struct work_struct work; +}; + /* * uda1380 register cache */ @@ -473,6 +477,7 @@ static int uda1380_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_codec *codec = socdev->card->codec; + struct uda1380_priv *uda1380 = codec->private_data; int mixer = uda1380_read_reg_cache(codec, UDA1380_MIXER); switch (cmd) { @@ -480,13 +485,13 @@ static int uda1380_trigger(struct snd_pcm_substream *substream, int cmd, case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: uda1380_write_reg_cache(codec, UDA1380_MIXER, mixer & ~R14_SILENCE); - schedule_work(&uda1380_work); + schedule_work(&uda1380->work); break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: uda1380_write_reg_cache(codec, UDA1380_MIXER, mixer | R14_SILENCE); - schedule_work(&uda1380_work); + schedule_work(&uda1380->work); break; } return 0; @@ -670,44 +675,33 @@ static int uda1380_resume(struct platform_device *pdev) return 0; } -/* - * initialise the UDA1380 driver - * register mixer and dsp interfaces with the kernel - */ -static int uda1380_init(struct snd_soc_device *socdev, int dac_clk) +static int uda1380_probe(struct platform_device *pdev) { - struct snd_soc_codec *codec = socdev->card->codec; + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + struct uda1380_platform_data *pdata; int ret = 0; - codec->name = "UDA1380"; - codec->owner = THIS_MODULE; - codec->read = uda1380_read_reg_cache; - codec->write = uda1380_write; - codec->set_bias_level = uda1380_set_bias_level; - codec->dai = uda1380_dai; - codec->num_dai = ARRAY_SIZE(uda1380_dai); - codec->reg_cache = kmemdup(uda1380_reg, sizeof(uda1380_reg), - GFP_KERNEL); - if (codec->reg_cache == NULL) - return -ENOMEM; - codec->reg_cache_size = ARRAY_SIZE(uda1380_reg); - codec->reg_cache_step = 1; - uda1380_reset(codec); + if (uda1380_codec == NULL) { + dev_err(&pdev->dev, "Codec device not registered\n"); + return -ENODEV; + } - uda1380_codec = codec; - INIT_WORK(&uda1380_work, uda1380_flush_work); + socdev->card->codec = uda1380_codec; + codec = uda1380_codec; + pdata = codec->dev->platform_data; /* register pcms */ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); if (ret < 0) { - pr_err("uda1380: failed to create pcms\n"); + dev_err(codec->dev, "failed to create pcms: %d\n", ret); goto pcm_err; } /* power on device */ uda1380_set_bias_level(codec, SND_SOC_BIAS_STANDBY); /* set clock input */ - switch (dac_clk) { + switch (pdata->dac_clk) { case UDA1380_DAC_CLK_SYSCLK: uda1380_write(codec, UDA1380_CLK, 0); break; @@ -716,13 +710,12 @@ static int uda1380_init(struct snd_soc_device *socdev, int dac_clk) break; } - /* uda1380 init */ snd_soc_add_controls(codec, uda1380_snd_controls, ARRAY_SIZE(uda1380_snd_controls)); uda1380_add_widgets(codec); ret = snd_soc_init_card(socdev); if (ret < 0) { - pr_err("uda1380: failed to register card\n"); + dev_err(codec->dev, "failed to register card: %d\n", ret); goto card_err; } @@ -732,36 +725,164 @@ card_err: snd_soc_free_pcms(socdev); snd_soc_dapm_free(socdev); pcm_err: - kfree(codec->reg_cache); return ret; } -static struct snd_soc_device *uda1380_socdev; +/* power down chip */ +static int uda1380_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + if (codec->control_data) + uda1380_set_bias_level(codec, SND_SOC_BIAS_OFF); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_uda1380 = { + .probe = uda1380_probe, + .remove = uda1380_remove, + .suspend = uda1380_suspend, + .resume = uda1380_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_uda1380); + +static int uda1380_register(struct uda1380_priv *uda1380) +{ + int ret, i; + struct snd_soc_codec *codec = &uda1380->codec; + struct uda1380_platform_data *pdata = codec->dev->platform_data; + + if (uda1380_codec) { + dev_err(codec->dev, "Another UDA1380 is registered\n"); + return -EINVAL; + } + + if (!pdata || !pdata->gpio_power || !pdata->gpio_reset) + return -EINVAL; + + ret = gpio_request(pdata->gpio_power, "uda1380 power"); + if (ret) + goto err_out; + ret = gpio_request(pdata->gpio_reset, "uda1380 reset"); + if (ret) + goto err_gpio; + + gpio_direction_output(pdata->gpio_power, 1); + + /* we may need to have the clock running here - pH5 */ + gpio_direction_output(pdata->gpio_reset, 1); + udelay(5); + gpio_set_value(pdata->gpio_reset, 0); + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + codec->private_data = uda1380; + codec->name = "UDA1380"; + codec->owner = THIS_MODULE; + codec->read = uda1380_read_reg_cache; + codec->write = uda1380_write; + codec->bias_level = SND_SOC_BIAS_OFF; + codec->set_bias_level = uda1380_set_bias_level; + codec->dai = uda1380_dai; + codec->num_dai = ARRAY_SIZE(uda1380_dai); + codec->reg_cache_size = ARRAY_SIZE(uda1380_reg); + codec->reg_cache = &uda1380->reg_cache; + codec->reg_cache_step = 1; + + memcpy(codec->reg_cache, uda1380_reg, sizeof(uda1380_reg)); + + ret = uda1380_reset(codec); + if (ret < 0) { + dev_err(codec->dev, "Failed to issue reset\n"); + goto err_reset; + } + + INIT_WORK(&uda1380->work, uda1380_flush_work); + + for (i = 0; i < ARRAY_SIZE(uda1380_dai); i++) + uda1380_dai[i].dev = codec->dev; + + uda1380_codec = codec; + + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(codec->dev, "Failed to register codec: %d\n", ret); + goto err_reset; + } + + ret = snd_soc_register_dais(uda1380_dai, ARRAY_SIZE(uda1380_dai)); + if (ret != 0) { + dev_err(codec->dev, "Failed to register DAIs: %d\n", ret); + goto err_dai; + } + + return 0; + +err_dai: + snd_soc_unregister_codec(codec); +err_reset: + gpio_set_value(pdata->gpio_power, 0); + gpio_free(pdata->gpio_reset); +err_gpio: + gpio_free(pdata->gpio_power); +err_out: + return ret; +} + +static void uda1380_unregister(struct uda1380_priv *uda1380) +{ + struct snd_soc_codec *codec = &uda1380->codec; + struct uda1380_platform_data *pdata = codec->dev->platform_data; + + snd_soc_unregister_dais(uda1380_dai, ARRAY_SIZE(uda1380_dai)); + snd_soc_unregister_codec(&uda1380->codec); + + gpio_set_value(pdata->gpio_power, 0); + gpio_free(pdata->gpio_reset); + gpio_free(pdata->gpio_power); + + kfree(uda1380); + uda1380_codec = NULL; +} #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) - -static int uda1380_i2c_probe(struct i2c_client *i2c, - const struct i2c_device_id *id) +static __devinit int uda1380_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) { - struct snd_soc_device *socdev = uda1380_socdev; - struct uda1380_setup_data *setup = socdev->codec_data; - struct snd_soc_codec *codec = socdev->card->codec; + struct uda1380_priv *uda1380; + struct snd_soc_codec *codec; int ret; - i2c_set_clientdata(i2c, codec); + uda1380 = kzalloc(sizeof(struct uda1380_priv), GFP_KERNEL); + if (uda1380 == NULL) + return -ENOMEM; + + codec = &uda1380->codec; + codec->hw_write = (hw_write_t)i2c_master_send; + + i2c_set_clientdata(i2c, uda1380); codec->control_data = i2c; - ret = uda1380_init(socdev, setup->dac_clk); - if (ret < 0) - pr_err("uda1380: failed to initialise UDA1380\n"); + codec->dev = &i2c->dev; + + ret = uda1380_register(uda1380); + if (ret != 0) + kfree(uda1380); return ret; } -static int uda1380_i2c_remove(struct i2c_client *client) +static int __devexit uda1380_i2c_remove(struct i2c_client *i2c) { - struct snd_soc_codec *codec = i2c_get_clientdata(client); - kfree(codec->reg_cache); + struct uda1380_priv *uda1380 = i2c_get_clientdata(i2c); + uda1380_unregister(uda1380); return 0; } @@ -777,120 +898,28 @@ static struct i2c_driver uda1380_i2c_driver = { .owner = THIS_MODULE, }, .probe = uda1380_i2c_probe, - .remove = uda1380_i2c_remove, + .remove = __devexit_p(uda1380_i2c_remove), .id_table = uda1380_i2c_id, }; - -static int uda1380_add_i2c_device(struct platform_device *pdev, - const struct uda1380_setup_data *setup) -{ - struct i2c_board_info info; - struct i2c_adapter *adapter; - struct i2c_client *client; - int ret; - - ret = i2c_add_driver(&uda1380_i2c_driver); - if (ret != 0) { - dev_err(&pdev->dev, "can't add i2c driver\n"); - return ret; - } - - memset(&info, 0, sizeof(struct i2c_board_info)); - info.addr = setup->i2c_address; - strlcpy(info.type, "uda1380", I2C_NAME_SIZE); - - adapter = i2c_get_adapter(setup->i2c_bus); - if (!adapter) { - dev_err(&pdev->dev, "can't get i2c adapter %d\n", - setup->i2c_bus); - goto err_driver; - } - - client = i2c_new_device(adapter, &info); - i2c_put_adapter(adapter); - if (!client) { - dev_err(&pdev->dev, "can't add i2c device at 0x%x\n", - (unsigned int)info.addr); - goto err_driver; - } - - return 0; - -err_driver: - i2c_del_driver(&uda1380_i2c_driver); - return -ENODEV; -} #endif -static int uda1380_probe(struct platform_device *pdev) -{ - struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct uda1380_setup_data *setup; - struct snd_soc_codec *codec; - int ret; - - setup = socdev->codec_data; - codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); - if (codec == NULL) - return -ENOMEM; - - socdev->card->codec = codec; - mutex_init(&codec->mutex); - INIT_LIST_HEAD(&codec->dapm_widgets); - INIT_LIST_HEAD(&codec->dapm_paths); - - uda1380_socdev = socdev; - ret = -ENODEV; - -#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) - if (setup->i2c_address) { - codec->hw_write = (hw_write_t)i2c_master_send; - ret = uda1380_add_i2c_device(pdev, setup); - } -#endif - - if (ret != 0) - kfree(codec); - return ret; -} - -/* power down chip */ -static int uda1380_remove(struct platform_device *pdev) -{ - struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->card->codec; - - if (codec->control_data) - uda1380_set_bias_level(codec, SND_SOC_BIAS_OFF); - - snd_soc_free_pcms(socdev); - snd_soc_dapm_free(socdev); -#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) - i2c_unregister_device(codec->control_data); - i2c_del_driver(&uda1380_i2c_driver); -#endif - kfree(codec); - - return 0; -} - -struct snd_soc_codec_device soc_codec_dev_uda1380 = { - .probe = uda1380_probe, - .remove = uda1380_remove, - .suspend = uda1380_suspend, - .resume = uda1380_resume, -}; -EXPORT_SYMBOL_GPL(soc_codec_dev_uda1380); - static int __init uda1380_modinit(void) { - return snd_soc_register_dais(uda1380_dai, ARRAY_SIZE(uda1380_dai)); + int ret; +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + ret = i2c_add_driver(&uda1380_i2c_driver); + if (ret != 0) + pr_err("Failed to register UDA1380 I2C driver: %d\n", ret); +#endif + return 0; } module_init(uda1380_modinit); static void __exit uda1380_exit(void) { - snd_soc_unregister_dais(uda1380_dai, ARRAY_SIZE(uda1380_dai)); +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_del_driver(&uda1380_i2c_driver); +#endif } module_exit(uda1380_exit); diff --git a/sound/soc/codecs/uda1380.h b/sound/soc/codecs/uda1380.h index c55c17a52a12..9cefa8a54770 100644 --- a/sound/soc/codecs/uda1380.h +++ b/sound/soc/codecs/uda1380.h @@ -72,14 +72,6 @@ #define R22_SKIP_DCFIL 0x0002 #define R23_AGC_EN 0x0001 -struct uda1380_setup_data { - int i2c_bus; - unsigned short i2c_address; - int dac_clk; -#define UDA1380_DAC_CLK_SYSCLK 0 -#define UDA1380_DAC_CLK_WSPLL 1 -}; - #define UDA1380_DAI_DUPLEX 0 /* playback and capture on single DAI */ #define UDA1380_DAI_PLAYBACK 1 /* playback DAI */ #define UDA1380_DAI_CAPTURE 2 /* capture DAI */ diff --git a/sound/soc/pxa/magician.c b/sound/soc/pxa/magician.c index c89a3cdf31e4..9fe4ad20615c 100644 --- a/sound/soc/pxa/magician.c +++ b/sound/soc/pxa/magician.c @@ -20,12 +20,14 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include @@ -447,34 +449,47 @@ static struct snd_soc_card snd_soc_card_magician = { .platform = &pxa2xx_soc_platform, }; -/* magician audio private data */ -static struct uda1380_setup_data magician_uda1380_setup = { - .i2c_address = 0x18, - .dac_clk = UDA1380_DAC_CLK_WSPLL, -}; - /* magician audio subsystem */ static struct snd_soc_device magician_snd_devdata = { .card = &snd_soc_card_magician, .codec_dev = &soc_codec_dev_uda1380, - .codec_data = &magician_uda1380_setup, }; static struct platform_device *magician_snd_device; +/* + * FIXME: move into magician board file once merged into the pxa tree + */ +static struct uda1380_platform_data uda1380_info = { + .gpio_power = EGPIO_MAGICIAN_CODEC_POWER, + .gpio_reset = EGPIO_MAGICIAN_CODEC_RESET, + .dac_clk = UDA1380_DAC_CLK_WSPLL, +}; + +static struct i2c_board_info i2c_board_info[] = { + { + I2C_BOARD_INFO("uda1380", 0x18), + .platform_data = &uda1380_info, + }, +}; + static int __init magician_init(void) { int ret; + struct i2c_adapter *adapter; + struct i2c_client *client; if (!machine_is_magician()) return -ENODEV; - ret = gpio_request(EGPIO_MAGICIAN_CODEC_POWER, "CODEC_POWER"); - if (ret) - goto err_request_power; - ret = gpio_request(EGPIO_MAGICIAN_CODEC_RESET, "CODEC_RESET"); - if (ret) - goto err_request_reset; + adapter = i2c_get_adapter(0); + if (!adapter) + return -ENODEV; + client = i2c_new_device(adapter, i2c_board_info); + i2c_put_adapter(adapter); + if (!client) + return -ENODEV; + ret = gpio_request(EGPIO_MAGICIAN_SPK_POWER, "SPK_POWER"); if (ret) goto err_request_spk; @@ -491,14 +506,8 @@ static int __init magician_init(void) if (ret) goto err_request_in_sel1; - gpio_set_value(EGPIO_MAGICIAN_CODEC_POWER, 1); gpio_set_value(EGPIO_MAGICIAN_IN_SEL0, 0); - /* we may need to have the clock running here - pH5 */ - gpio_set_value(EGPIO_MAGICIAN_CODEC_RESET, 1); - udelay(5); - gpio_set_value(EGPIO_MAGICIAN_CODEC_RESET, 0); - magician_snd_device = platform_device_alloc("soc-audio", -1); if (!magician_snd_device) { ret = -ENOMEM; @@ -526,10 +535,6 @@ err_request_mic: err_request_ep: gpio_free(EGPIO_MAGICIAN_SPK_POWER); err_request_spk: - gpio_free(EGPIO_MAGICIAN_CODEC_RESET); -err_request_reset: - gpio_free(EGPIO_MAGICIAN_CODEC_POWER); -err_request_power: return ret; } @@ -540,15 +545,12 @@ static void __exit magician_exit(void) gpio_set_value(EGPIO_MAGICIAN_SPK_POWER, 0); gpio_set_value(EGPIO_MAGICIAN_EP_POWER, 0); gpio_set_value(EGPIO_MAGICIAN_MIC_POWER, 0); - gpio_set_value(EGPIO_MAGICIAN_CODEC_POWER, 0); gpio_free(EGPIO_MAGICIAN_IN_SEL1); gpio_free(EGPIO_MAGICIAN_IN_SEL0); gpio_free(EGPIO_MAGICIAN_MIC_POWER); gpio_free(EGPIO_MAGICIAN_EP_POWER); gpio_free(EGPIO_MAGICIAN_SPK_POWER); - gpio_free(EGPIO_MAGICIAN_CODEC_RESET); - gpio_free(EGPIO_MAGICIAN_CODEC_POWER); } module_init(magician_init); From a583cd53478f0c55b92f084bdbe3b66d2b4496df Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 17 Jun 2009 17:30:14 +0100 Subject: [PATCH 18/26] ASoC: Regulator support for WM8580 Add basic support for integration with the regulator API to WM8580. Since the core cannot yet disable biases when the CODEC is idle we simply request and enable the regulators for the entire time the driver is active. Signed-off-by: Mark Brown --- sound/soc/codecs/wm8580.c | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/sound/soc/codecs/wm8580.c b/sound/soc/codecs/wm8580.c index 261ef101d4fc..97b9ed95d289 100644 --- a/sound/soc/codecs/wm8580.c +++ b/sound/soc/codecs/wm8580.c @@ -24,6 +24,8 @@ #include #include #include +#include + #include #include #include @@ -187,15 +189,22 @@ struct pll_state { unsigned int out; }; +#define WM8580_NUM_SUPPLIES 3 +static const char *wm8580_supply_names[WM8580_NUM_SUPPLIES] = { + "AVDD", + "DVDD", + "PVDD", +}; + /* codec private data */ struct wm8580_priv { struct snd_soc_codec codec; + struct regulator_bulk_data supplies[WM8580_NUM_SUPPLIES]; u16 reg_cache[WM8580_MAX_REGISTER + 1]; struct pll_state a; struct pll_state b; }; - /* * read wm8580 register cache */ @@ -922,11 +931,28 @@ static int wm8580_register(struct wm8580_priv *wm8580) memcpy(codec->reg_cache, wm8580_reg, sizeof(wm8580_reg)); + for (i = 0; i < ARRAY_SIZE(wm8580->supplies); i++) + wm8580->supplies[i].supply = wm8580_supply_names[i]; + + ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(wm8580->supplies), + wm8580->supplies); + if (ret != 0) { + dev_err(codec->dev, "Failed to request supplies: %d\n", ret); + goto err; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(wm8580->supplies), + wm8580->supplies); + if (ret != 0) { + dev_err(codec->dev, "Failed to enable supplies: %d\n", ret); + goto err_regulator_get; + } + /* Get the codec into a known state */ ret = wm8580_write(codec, WM8580_RESET, 0); if (ret != 0) { dev_err(codec->dev, "Failed to reset codec: %d\n", ret); - goto err; + goto err_regulator_enable; } for (i = 0; i < ARRAY_SIZE(wm8580_dai); i++) @@ -939,7 +965,7 @@ static int wm8580_register(struct wm8580_priv *wm8580) ret = snd_soc_register_codec(codec); if (ret != 0) { dev_err(codec->dev, "Failed to register codec: %d\n", ret); - goto err; + goto err_regulator_enable; } ret = snd_soc_register_dais(wm8580_dai, ARRAY_SIZE(wm8580_dai)); @@ -952,6 +978,10 @@ static int wm8580_register(struct wm8580_priv *wm8580) err_codec: snd_soc_unregister_codec(codec); +err_regulator_enable: + regulator_bulk_disable(ARRAY_SIZE(wm8580->supplies), wm8580->supplies); +err_regulator_get: + regulator_bulk_free(ARRAY_SIZE(wm8580->supplies), wm8580->supplies); err: kfree(wm8580); return ret; @@ -962,6 +992,8 @@ static void wm8580_unregister(struct wm8580_priv *wm8580) wm8580_set_bias_level(&wm8580->codec, SND_SOC_BIAS_OFF); snd_soc_unregister_dais(wm8580_dai, ARRAY_SIZE(wm8580_dai)); snd_soc_unregister_codec(&wm8580->codec); + regulator_bulk_disable(ARRAY_SIZE(wm8580->supplies), wm8580->supplies); + regulator_bulk_free(ARRAY_SIZE(wm8580->supplies), wm8580->supplies); kfree(wm8580); wm8580_codec = NULL; } From b53109db5e016425b767e8e33669a5f41257e6e5 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 20 Jun 2009 13:53:20 +0100 Subject: [PATCH 19/26] ASoC: Fix shadowed variables in twl4030 No need to define second copies of mode and format, they're declared with exactly the same type at the head of the function and there's no conflict in their use. Signed-off-by: Mark Brown --- sound/soc/codecs/twl4030.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index 4dbb853eef5a..a5062ccd28ee 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -1609,8 +1609,6 @@ static int twl4030_hw_params(struct snd_pcm_substream *substream, /* If the substream has 4 channel, do the necessary setup */ if (params_channels(params) == 4) { - u8 format, mode; - format = twl4030_read_reg_cache(codec, TWL4030_REG_AUDIO_IF); mode = twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE); From 423c238d7185a0a8f1b28ddb0be44e0286927909 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 20 Jun 2009 13:54:02 +0100 Subject: [PATCH 20/26] ASoC: Staticise put_twl4030_opmode_enum_double() It's an operation for a control and doesn't need to be exported. Signed-off-by: Mark Brown --- sound/soc/codecs/twl4030.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index a5062ccd28ee..4275eea31450 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -924,7 +924,7 @@ static const struct soc_enum twl4030_op_modes_enum = ARRAY_SIZE(twl4030_op_modes_texts), twl4030_op_modes_texts); -int snd_soc_put_twl4030_opmode_enum_double(struct snd_kcontrol *kcontrol, +static int snd_soc_put_twl4030_opmode_enum_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); From f274143f127dd9420e6def7dc47340b55f29c885 Mon Sep 17 00:00:00 2001 From: Barry Song Date: Sat, 20 Jun 2009 11:29:56 -0400 Subject: [PATCH 21/26] ASoC: Blackfin: convert internal names from bf52x to bf5xx These drivers aren't BF52x specific, so don't use bf52x in the names. Signed-off-by: Barry Song Signed-off-by: Mike Frysinger Signed-off-by: Mark Brown --- sound/soc/blackfin/bf5xx-ad73311.c | 16 ++++++++-------- sound/soc/blackfin/bf5xx-ssm2602.c | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/sound/soc/blackfin/bf5xx-ad73311.c b/sound/soc/blackfin/bf5xx-ad73311.c index edfbdc024e66..9825b71d0e28 100644 --- a/sound/soc/blackfin/bf5xx-ad73311.c +++ b/sound/soc/blackfin/bf5xx-ad73311.c @@ -203,23 +203,23 @@ static struct snd_soc_device bf5xx_ad73311_snd_devdata = { .codec_dev = &soc_codec_dev_ad73311, }; -static struct platform_device *bf52x_ad73311_snd_device; +static struct platform_device *bf5xx_ad73311_snd_device; static int __init bf5xx_ad73311_init(void) { int ret; pr_debug("%s enter\n", __func__); - bf52x_ad73311_snd_device = platform_device_alloc("soc-audio", -1); - if (!bf52x_ad73311_snd_device) + bf5xx_ad73311_snd_device = platform_device_alloc("soc-audio", -1); + if (!bf5xx_ad73311_snd_device) return -ENOMEM; - platform_set_drvdata(bf52x_ad73311_snd_device, &bf5xx_ad73311_snd_devdata); - bf5xx_ad73311_snd_devdata.dev = &bf52x_ad73311_snd_device->dev; - ret = platform_device_add(bf52x_ad73311_snd_device); + platform_set_drvdata(bf5xx_ad73311_snd_device, &bf5xx_ad73311_snd_devdata); + bf5xx_ad73311_snd_devdata.dev = &bf5xx_ad73311_snd_device->dev; + ret = platform_device_add(bf5xx_ad73311_snd_device); if (ret) - platform_device_put(bf52x_ad73311_snd_device); + platform_device_put(bf5xx_ad73311_snd_device); return ret; } @@ -227,7 +227,7 @@ static int __init bf5xx_ad73311_init(void) static void __exit bf5xx_ad73311_exit(void) { pr_debug("%s enter\n", __func__); - platform_device_unregister(bf52x_ad73311_snd_device); + platform_device_unregister(bf5xx_ad73311_snd_device); } module_init(bf5xx_ad73311_init); diff --git a/sound/soc/blackfin/bf5xx-ssm2602.c b/sound/soc/blackfin/bf5xx-ssm2602.c index bc0cdded7116..3a00fa4dbe6d 100644 --- a/sound/soc/blackfin/bf5xx-ssm2602.c +++ b/sound/soc/blackfin/bf5xx-ssm2602.c @@ -148,24 +148,24 @@ static struct snd_soc_device bf5xx_ssm2602_snd_devdata = { .codec_data = &bf5xx_ssm2602_setup, }; -static struct platform_device *bf52x_ssm2602_snd_device; +static struct platform_device *bf5xx_ssm2602_snd_device; static int __init bf5xx_ssm2602_init(void) { int ret; pr_debug("%s enter\n", __func__); - bf52x_ssm2602_snd_device = platform_device_alloc("soc-audio", -1); - if (!bf52x_ssm2602_snd_device) + bf5xx_ssm2602_snd_device = platform_device_alloc("soc-audio", -1); + if (!bf5xx_ssm2602_snd_device) return -ENOMEM; - platform_set_drvdata(bf52x_ssm2602_snd_device, + platform_set_drvdata(bf5xx_ssm2602_snd_device, &bf5xx_ssm2602_snd_devdata); - bf5xx_ssm2602_snd_devdata.dev = &bf52x_ssm2602_snd_device->dev; - ret = platform_device_add(bf52x_ssm2602_snd_device); + bf5xx_ssm2602_snd_devdata.dev = &bf5xx_ssm2602_snd_device->dev; + ret = platform_device_add(bf5xx_ssm2602_snd_device); if (ret) - platform_device_put(bf52x_ssm2602_snd_device); + platform_device_put(bf5xx_ssm2602_snd_device); return ret; } @@ -173,7 +173,7 @@ static int __init bf5xx_ssm2602_init(void) static void __exit bf5xx_ssm2602_exit(void) { pr_debug("%s enter\n", __func__); - platform_device_unregister(bf52x_ssm2602_snd_device); + platform_device_unregister(bf5xx_ssm2602_snd_device); } module_init(bf5xx_ssm2602_init); From c264301c777840b2df130e042b7f5a0c1041646f Mon Sep 17 00:00:00 2001 From: "Lopez Cruz, Misael" Date: Fri, 19 Jun 2009 03:23:42 -0500 Subject: [PATCH 22/26] ASoC: TWL4030: Fix voice interface clock masters Voice interface of twl4030 codec supports: CBM_CFM and CBS_CFS. It doesn't support CBS_CFM. Signed-off-by: Misael Lopez Cruz Acked-By: Peter Ujfalusi Signed-off-by: Mark Brown --- sound/soc/codecs/twl4030.c | 2 +- sound/soc/omap/sdp3430.c | 2 +- sound/soc/omap/zoom2.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index 4275eea31450..091125c888ed 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -1946,7 +1946,7 @@ static int twl4030_voice_set_dai_fmt(struct snd_soc_dai *codec_dai, /* set master/slave audio interface */ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBS_CFM: + case SND_SOC_DAIFMT_CBM_CFM: format &= ~(TWL4030_VIF_SLAVE_EN); break; case SND_SOC_DAIFMT_CBS_CFS: diff --git a/sound/soc/omap/sdp3430.c b/sound/soc/omap/sdp3430.c index b719e5db4f57..c51594d8fd13 100644 --- a/sound/soc/omap/sdp3430.c +++ b/sound/soc/omap/sdp3430.c @@ -96,7 +96,7 @@ static int sdp3430_hw_voice_params(struct snd_pcm_substream *substream, ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_IB_NF | - SND_SOC_DAIFMT_CBS_CFM); + SND_SOC_DAIFMT_CBM_CFM); if (ret) { printk(KERN_ERR "can't set codec DAI configuration\n"); return ret; diff --git a/sound/soc/omap/zoom2.c b/sound/soc/omap/zoom2.c index be2e307fdf72..3de6d2bd3903 100644 --- a/sound/soc/omap/zoom2.c +++ b/sound/soc/omap/zoom2.c @@ -92,7 +92,7 @@ static int zoom2_hw_voice_params(struct snd_pcm_substream *substream, ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_IB_NF | - SND_SOC_DAIFMT_CBS_CFM); + SND_SOC_DAIFMT_CBM_CFM); if (ret) { printk(KERN_ERR "can't set codec DAI configuration\n"); return ret; From 328d0a138e3d7761f4db53fabf82279b90ea66dd Mon Sep 17 00:00:00 2001 From: "Lopez Cruz, Misael" Date: Mon, 22 Jun 2009 10:51:52 -0500 Subject: [PATCH 23/26] ASoC: TWL4030: Add AVADC Clock Priority AVDAC clk priority allows to determine the path ADC must be connected when the codec is in option2 and both HiFi and Voice paths are enabled. Signed-off-by: Misael Lopez Cruz Acked-by: Peter Ujfalusi Signed-off-by: Mark Brown --- sound/soc/codecs/twl4030.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index 091125c888ed..f916a9a46712 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -1005,6 +1005,16 @@ static DECLARE_TLV_DB_SCALE(digital_capture_tlv, 0, 100, 0); */ static DECLARE_TLV_DB_SCALE(input_gain_tlv, 0, 600, 0); +/* AVADC clock priority */ +static const char *twl4030_avadc_clk_priority_texts[] = { + "Voice high priority", "HiFi high priority" +}; + +static const struct soc_enum twl4030_avadc_clk_priority_enum = + SOC_ENUM_SINGLE(TWL4030_REG_AVADC_CTL, 2, + ARRAY_SIZE(twl4030_avadc_clk_priority_texts), + twl4030_avadc_clk_priority_texts); + static const char *twl4030_rampdelay_texts[] = { "27/20/14 ms", "55/40/27 ms", "109/81/55 ms", "218/161/109 ms", "437/323/218 ms", "874/645/437 ms", "1748/1291/874 ms", @@ -1106,6 +1116,8 @@ static const struct snd_kcontrol_new twl4030_snd_controls[] = { SOC_DOUBLE_TLV("Analog Capture Volume", TWL4030_REG_ANAMIC_GAIN, 0, 3, 5, 0, input_gain_tlv), + SOC_ENUM("AVADC Clock Priority", twl4030_avadc_clk_priority_enum), + SOC_ENUM("HS ramp delay", twl4030_rampdelay_enum), SOC_ENUM("Vibra H-bridge mode", twl4030_vibradirmode_enum), From 30808ca751c3b8d81e948efb8fed7451a8321010 Mon Sep 17 00:00:00 2001 From: "Lopez Cruz, Misael" Date: Mon, 22 Jun 2009 19:34:07 -0500 Subject: [PATCH 24/26] ASoC: TWL4030: Correct bypass event for voice sidetone Event for voice sidetone was being interpreted as an analog HiFi bypass event because VSTPGA register offset is less than ARXR2_APGA_CTL offset. Reordering the register checks allows to handle voice digital bypass event properly. Signed-off-by: Misael Lopez Cruz Acked-by: Peter Ujfalusi Signed-off-by: Mark Brown --- sound/soc/codecs/twl4030.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index f916a9a46712..df42fa2abd91 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -712,7 +712,19 @@ static int bypass_event(struct snd_soc_dapm_widget *w, reg = twl4030_read_reg_cache(w->codec, m->reg); - if (m->reg <= TWL4030_REG_ARXR2_APGA_CTL) { + /* + * bypass_state[0:3] - analog HiFi bypass + * bypass_state[4] - analog voice bypass + * bypass_state[5] - digital voice bypass + * bypass_state[6:7] - digital HiFi bypass + */ + if (m->reg == TWL4030_REG_VSTPGA) { + /* Voice digital bypass */ + if (reg) + twl4030->bypass_state |= (1 << 5); + else + twl4030->bypass_state &= ~(1 << 5); + } else if (m->reg <= TWL4030_REG_ARXR2_APGA_CTL) { /* Analog bypass */ if (reg & (1 << m->shift)) twl4030->bypass_state |= @@ -726,12 +738,6 @@ static int bypass_event(struct snd_soc_dapm_widget *w, twl4030->bypass_state |= (1 << 4); else twl4030->bypass_state &= ~(1 << 4); - } else if (m->reg == TWL4030_REG_VSTPGA) { - /* Voice digital bypass */ - if (reg) - twl4030->bypass_state |= (1 << 5); - else - twl4030->bypass_state &= ~(1 << 5); } else { /* Digital bypass */ if (reg & (0x7 << m->shift)) From 517374704da44c1ba77c1600714fe214524af286 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 22 Jun 2009 13:16:51 +0100 Subject: [PATCH 25/26] ASoC: Add a shutdown callback Ensure that the audio subsystem is powered down cleanly when the system shuts down by providing a shutdown operation. This ensures that all the components have been returned to an off state cleanly which should avoid audio issues from partially charged capacitors or noise on digital inputs if the system is restarted quickly. Signed-off-by: Mark Brown Tested-by: Ben Dooks --- include/sound/soc-dapm.h | 1 + sound/soc/soc-core.c | 16 ++++++++++++++++ sound/soc/soc-dapm.c | 29 +++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+) diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h index ec8a45f9a069..35814ced2d22 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -279,6 +279,7 @@ int snd_soc_dapm_add_routes(struct snd_soc_codec *codec, /* dapm events */ int snd_soc_dapm_stream_event(struct snd_soc_codec *codec, char *stream, int event); +void snd_soc_dapm_shutdown(struct snd_soc_device *socdev); /* dapm sys fs - used by the core */ int snd_soc_dapm_sys_add(struct device *dev); diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 44141178ff4a..55d45c43ba16 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1020,6 +1020,21 @@ static int soc_remove(struct platform_device *pdev) return 0; } +static void soc_shutdown(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_card *card = socdev->card; + + if (!card->instantiated) + return; + + /* Flush out pmdown_time work - we actually do want to run it + * now, we're shutting down so no imminent restart. */ + run_delayed_work(&card->delayed_work); + + snd_soc_dapm_shutdown(socdev); +} + /* ASoC platform driver */ static struct platform_driver soc_driver = { .driver = { @@ -1030,6 +1045,7 @@ static struct platform_driver soc_driver = { .remove = soc_remove, .suspend = soc_suspend, .resume = soc_resume, + .shutdown = soc_shutdown, }; /* create a new pcm */ diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 653435930ad8..b9129efeedf3 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -2032,6 +2032,35 @@ void snd_soc_dapm_free(struct snd_soc_device *socdev) } EXPORT_SYMBOL_GPL(snd_soc_dapm_free); +/* + * snd_soc_dapm_shutdown - callback for system shutdown + */ +void snd_soc_dapm_shutdown(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->card->codec; + struct snd_soc_dapm_widget *w; + LIST_HEAD(down_list); + int powerdown = 0; + + list_for_each_entry(w, &codec->dapm_widgets, list) { + if (w->power) { + dapm_seq_insert(w, &down_list, dapm_down_seq); + powerdown = 1; + } + } + + /* If there were no widgets to power down we're already in + * standby. + */ + if (powerdown) { + snd_soc_dapm_set_bias_level(socdev, SND_SOC_BIAS_PREPARE); + dapm_seq_run(codec, &down_list, 0, dapm_down_seq); + snd_soc_dapm_set_bias_level(socdev, SND_SOC_BIAS_STANDBY); + } + + snd_soc_dapm_set_bias_level(socdev, SND_SOC_BIAS_OFF); +} + /* Module information */ MODULE_AUTHOR("Liam Girdwood, lrg@slimlogic.co.uk"); MODULE_DESCRIPTION("Dynamic Audio Power Management core for ALSA SoC"); From e2f551dacbdff8e40365a989ab66104b03316f4d Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 25 Jun 2009 13:57:59 +0100 Subject: [PATCH 26/26] ASoC: Add core suspend and resume callbacks to WM8961 Signed-off-by: Mark Brown --- sound/soc/codecs/wm8961.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/sound/soc/codecs/wm8961.c b/sound/soc/codecs/wm8961.c index 8e78959ca409..1af2d10702f4 100644 --- a/sound/soc/codecs/wm8961.c +++ b/sound/soc/codecs/wm8961.c @@ -1267,6 +1267,21 @@ static __devexit int wm8961_i2c_remove(struct i2c_client *client) return 0; } +#ifdef CONFIG_PM +static int wm8961_i2c_suspend(struct i2c_client *client) +{ + return snd_soc_suspend_device(&client->dev); +} + +static int wm8961_i2c_resume(struct i2c_client *client) +{ + return snd_soc_resume_device(&client->dev); +} +#else +#define wm8961_i2c_suspend NULL +#define wm8961_i2c_resume NULL +#endif + static const struct i2c_device_id wm8961_i2c_id[] = { { "wm8961", 0 }, { } @@ -1280,6 +1295,8 @@ static struct i2c_driver wm8961_i2c_driver = { }, .probe = wm8961_i2c_probe, .remove = __devexit_p(wm8961_i2c_remove), + .suspend = wm8961_i2c_suspend, + .resume = wm8961_i2c_resume, .id_table = wm8961_i2c_id, };