"DRVDD", /* ADC Analog and Output Driver Voltage */
};
+struct aic3x_priv;
+
+struct aic3x_disable_nb {
+ struct notifier_block nb;
+ struct aic3x_priv *aic3x;
+};
+
/* codec private data */
struct aic3x_priv {
+ struct snd_soc_codec *codec;
struct regulator_bulk_data supplies[AIC3X_NUM_SUPPLIES];
+ struct aic3x_disable_nb disable_nb[AIC3X_NUM_SUPPLIES];
enum snd_soc_control_type control_type;
struct aic3x_setup_data *setup;
void *control_data;
unsigned int sysclk;
int master;
int gpio_reset;
+ int power;
#define AIC3X_MODEL_3X 0
#define AIC3X_MODEL_33 1
#define AIC3X_MODEL_3007 2
{
u8 *cache = codec->reg_cache;
+ if (codec->cache_only)
+ return -EINVAL;
if (reg >= AIC3X_CACHEREGNUM)
return -1;
SND_SOC_DAPM_INPUT("LINE1R"),
SND_SOC_DAPM_INPUT("LINE2L"),
SND_SOC_DAPM_INPUT("LINE2R"),
+
+ /*
+ * Virtual output pin to detection block inside codec. This can be
+ * used to keep codec bias on if gpio or detection features are needed.
+ * Force pin on or construct a path with an input jack and mic bias
+ * widgets.
+ */
+ SND_SOC_DAPM_OUTPUT("Detection"),
};
static const struct snd_soc_dapm_widget aic3007_dapm_widgets[] = {
return 0;
}
+static int aic3x_init_3007(struct snd_soc_codec *codec)
+{
+ u8 tmp1, tmp2, *cache = codec->reg_cache;
+
+ /*
+ * There is no need to cache writes to undocumented page 0xD but
+ * respective page 0 register cache entries must be preserved
+ */
+ tmp1 = cache[0xD];
+ tmp2 = cache[0x8];
+ /* Class-D speaker driver init; datasheet p. 46 */
+ snd_soc_write(codec, AIC3X_PAGE_SELECT, 0x0D);
+ snd_soc_write(codec, 0xD, 0x0D);
+ snd_soc_write(codec, 0x8, 0x5C);
+ snd_soc_write(codec, 0x8, 0x5D);
+ snd_soc_write(codec, 0x8, 0x5C);
+ snd_soc_write(codec, AIC3X_PAGE_SELECT, 0x00);
+ cache[0xD] = tmp1;
+ cache[0x8] = tmp2;
+
+ return 0;
+}
+
+static int aic3x_regulator_event(struct notifier_block *nb,
+ unsigned long event, void *data)
+{
+ struct aic3x_disable_nb *disable_nb =
+ container_of(nb, struct aic3x_disable_nb, nb);
+ struct aic3x_priv *aic3x = disable_nb->aic3x;
+
+ if (event & REGULATOR_EVENT_DISABLE) {
+ /*
+ * Put codec to reset and require cache sync as at least one
+ * of the supplies was disabled
+ */
+ if (aic3x->gpio_reset >= 0)
+ gpio_set_value(aic3x->gpio_reset, 0);
+ aic3x->codec->cache_sync = 1;
+ }
+
+ return 0;
+}
+
+static int aic3x_set_power(struct snd_soc_codec *codec, int power)
+{
+ struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec);
+ int i, ret;
+ u8 *cache = codec->reg_cache;
+
+ if (power) {
+ ret = regulator_bulk_enable(ARRAY_SIZE(aic3x->supplies),
+ aic3x->supplies);
+ if (ret)
+ goto out;
+ aic3x->power = 1;
+ /*
+ * Reset release and cache sync is necessary only if some
+ * supply was off or if there were cached writes
+ */
+ if (!codec->cache_sync)
+ goto out;
+
+ if (aic3x->gpio_reset >= 0) {
+ udelay(1);
+ gpio_set_value(aic3x->gpio_reset, 1);
+ }
+
+ /* Sync reg_cache with the hardware */
+ codec->cache_only = 0;
+ for (i = 0; i < ARRAY_SIZE(aic3x_reg); i++)
+ snd_soc_write(codec, i, cache[i]);
+ if (aic3x->model == AIC3X_MODEL_3007)
+ aic3x_init_3007(codec);
+ codec->cache_sync = 0;
+ } else {
+ aic3x->power = 0;
+ /* HW writes are needless when bias is off */
+ codec->cache_only = 1;
+ ret = regulator_bulk_disable(ARRAY_SIZE(aic3x->supplies),
+ aic3x->supplies);
+ }
+out:
+ return ret;
+}
+
static int aic3x_set_bias_level(struct snd_soc_codec *codec,
enum snd_soc_bias_level level)
{
}
break;
case SND_SOC_BIAS_STANDBY:
+ if (!aic3x->power)
+ aic3x_set_power(codec, 1);
if (codec->bias_level == SND_SOC_BIAS_PREPARE &&
aic3x->master) {
/* disable pll */
}
break;
case SND_SOC_BIAS_OFF:
+ if (aic3x->power)
+ aic3x_set_power(codec, 0);
break;
}
codec->bias_level = level;
static int aic3x_resume(struct snd_soc_codec *codec)
{
- int i;
- u8 data[2];
- u8 *cache = codec->reg_cache;
-
- /* Sync reg_cache with the hardware */
- for (i = 0; i < ARRAY_SIZE(aic3x_reg); i++) {
- data[0] = i;
- data[1] = cache[i];
- codec->hw_write(codec->control_data, data, 2);
- }
-
aic3x_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
return 0;
snd_soc_write(codec, LINE2R_2_MONOLOPM_VOL, DEFAULT_VOL);
if (aic3x->model == AIC3X_MODEL_3007) {
- /* Class-D speaker driver init; datasheet p. 46 */
- snd_soc_write(codec, AIC3X_PAGE_SELECT, 0x0D);
- snd_soc_write(codec, 0xD, 0x0D);
- snd_soc_write(codec, 0x8, 0x5C);
- snd_soc_write(codec, 0x8, 0x5D);
- snd_soc_write(codec, 0x8, 0x5C);
- snd_soc_write(codec, AIC3X_PAGE_SELECT, 0x00);
+ aic3x_init_3007(codec);
snd_soc_write(codec, CLASSD_CTRL, 0);
}
- /* off, with power on */
- aic3x_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
-
return 0;
}
static int aic3x_probe(struct snd_soc_codec *codec)
{
struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec);
- int ret;
+ int ret, i;
codec->control_data = aic3x->control_data;
+ aic3x->codec = codec;
+ codec->idle_bias_off = 1;
ret = snd_soc_codec_set_cache_io(codec, 8, 8, aic3x->control_type);
if (ret != 0) {
return ret;
}
+ if (aic3x->gpio_reset >= 0) {
+ ret = gpio_request(aic3x->gpio_reset, "tlv320aic3x reset");
+ if (ret != 0)
+ goto err_gpio;
+ gpio_direction_output(aic3x->gpio_reset, 0);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(aic3x->supplies); i++)
+ aic3x->supplies[i].supply = aic3x_supply_names[i];
+
+ ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(aic3x->supplies),
+ aic3x->supplies);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to request supplies: %d\n", ret);
+ goto err_get;
+ }
+ for (i = 0; i < ARRAY_SIZE(aic3x->supplies); i++) {
+ aic3x->disable_nb[i].nb.notifier_call = aic3x_regulator_event;
+ aic3x->disable_nb[i].aic3x = aic3x;
+ ret = regulator_register_notifier(aic3x->supplies[i].consumer,
+ &aic3x->disable_nb[i].nb);
+ if (ret) {
+ dev_err(codec->dev,
+ "Failed to request regulator notifier: %d\n",
+ ret);
+ goto err_notif;
+ }
+ }
+
+ codec->cache_only = 1;
aic3x_init(codec);
if (aic3x->setup) {
aic3x_add_widgets(codec);
return 0;
+
+err_notif:
+ while (i--)
+ regulator_unregister_notifier(aic3x->supplies[i].consumer,
+ &aic3x->disable_nb[i].nb);
+ regulator_bulk_free(ARRAY_SIZE(aic3x->supplies), aic3x->supplies);
+err_get:
+ if (aic3x->gpio_reset >= 0)
+ gpio_free(aic3x->gpio_reset);
+err_gpio:
+ kfree(aic3x);
+ return ret;
}
static int aic3x_remove(struct snd_soc_codec *codec)
{
+ struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec);
+ int i;
+
aic3x_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ if (aic3x->gpio_reset >= 0) {
+ gpio_set_value(aic3x->gpio_reset, 0);
+ gpio_free(aic3x->gpio_reset);
+ }
+ for (i = 0; i < ARRAY_SIZE(aic3x->supplies); i++)
+ regulator_unregister_notifier(aic3x->supplies[i].consumer,
+ &aic3x->disable_nb[i].nb);
+ regulator_bulk_free(ARRAY_SIZE(aic3x->supplies), aic3x->supplies);
+
return 0;
}
{
struct aic3x_pdata *pdata = i2c->dev.platform_data;
struct aic3x_priv *aic3x;
- int ret, i;
+ int ret;
const struct i2c_device_id *tbl;
aic3x = kzalloc(sizeof(struct aic3x_priv), GFP_KERNEL);
aic3x->gpio_reset = -1;
}
- if (aic3x->gpio_reset >= 0) {
- ret = gpio_request(aic3x->gpio_reset, "tlv320aic3x reset");
- if (ret != 0)
- goto err_gpio;
- gpio_direction_output(aic3x->gpio_reset, 0);
- }
-
for (tbl = aic3x_i2c_id; tbl->name[0]; tbl++) {
if (!strcmp(tbl->name, id->name))
break;
}
aic3x->model = tbl - aic3x_i2c_id;
- for (i = 0; i < ARRAY_SIZE(aic3x->supplies); i++)
- aic3x->supplies[i].supply = aic3x_supply_names[i];
-
- ret = regulator_bulk_get(&i2c->dev, ARRAY_SIZE(aic3x->supplies),
- aic3x->supplies);
- if (ret != 0) {
- dev_err(&i2c->dev, "Failed to request supplies: %d\n", ret);
- goto err_get;
- }
-
- ret = regulator_bulk_enable(ARRAY_SIZE(aic3x->supplies),
- aic3x->supplies);
- if (ret != 0) {
- dev_err(&i2c->dev, "Failed to enable supplies: %d\n", ret);
- goto err_enable;
- }
-
- if (aic3x->gpio_reset >= 0) {
- udelay(1);
- gpio_set_value(aic3x->gpio_reset, 1);
- }
-
ret = snd_soc_register_codec(&i2c->dev,
&soc_codec_dev_aic3x, &aic3x_dai, 1);
if (ret < 0)
- goto err_enable;
- return ret;
-
-err_enable:
- regulator_bulk_free(ARRAY_SIZE(aic3x->supplies), aic3x->supplies);
-err_get:
- if (aic3x->gpio_reset >= 0)
- gpio_free(aic3x->gpio_reset);
-err_gpio:
- kfree(aic3x);
+ kfree(aic3x);
return ret;
}
static int aic3x_i2c_remove(struct i2c_client *client)
{
- struct aic3x_priv *aic3x = i2c_get_clientdata(client);
-
- if (aic3x->gpio_reset >= 0) {
- gpio_set_value(aic3x->gpio_reset, 0);
- gpio_free(aic3x->gpio_reset);
- }
- regulator_bulk_disable(ARRAY_SIZE(aic3x->supplies), aic3x->supplies);
- regulator_bulk_free(ARRAY_SIZE(aic3x->supplies), aic3x->supplies);
-
snd_soc_unregister_codec(&client->dev);
kfree(i2c_get_clientdata(client));
return 0;