#include <linux/bitmap.h>
#include <linux/bits.h>
#include <linux/gpio/consumer.h>
#include <linux/math.h>
#include <linux/minmax.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
#include <linux/slab.h>
#include <sound/soc.h>
#include <linux/sort.h>
#include <sound/tlv.h>
struct simple_amp_single {
struct gpio_desc *gpio;
bool is_inverted;
int kctrl_val;
const char *control_name;
};
struct simple_amp_point {
u32 gpio_val;
int gain_db;
};
struct simple_amp_range {
unsigned int nb_points;
struct simple_amp_point min;
struct simple_amp_point max;
};
struct simple_amp_ranges {
unsigned int nb_ranges;
struct simple_amp_range *tab_ranges;
};
struct simple_amp_labels {
unsigned int nb_labels;
const char **tab_labels;
};
enum simple_amp_mode {
SIMPLE_AMP_MODE_NONE,
SIMPLE_AMP_MODE_RANGES,
SIMPLE_AMP_MODE_LABELS,
};
struct simple_amp_multi {
struct gpio_descs *gpios;
u32 kctrl_val;
u32 kctrl_max;
const char *control_name;
unsigned int *tlv_array;
enum simple_amp_mode mode;
union {
struct simple_amp_ranges ranges;
struct simple_amp_labels labels;
};
};
struct simple_amp_data {
unsigned int supports;
#define SIMPLE_AUDIO_SUPPORT_PGA BIT(0)
#define SIMPLE_AUDIO_SUPPORT_POWER_SUPPLIES BIT(1)
#define SIMPLE_AUDIO_SUPPORT_MUTE BIT(2)
#define SIMPLE_AUDIO_SUPPORT_BYPASS BIT(3)
const struct snd_soc_dapm_widget *dapm_widgets;
unsigned int num_dapm_widgets;
const struct snd_soc_dapm_route *dapm_routes;
unsigned int num_dapm_routes;
};
struct simple_amp {
const struct simple_amp_data *data;
struct gpio_desc *gpiod_enable;
struct simple_amp_single mute;
struct simple_amp_single bypass;
struct simple_amp_multi gain;
};
static int simple_amp_power_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *control, int event)
{
struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm);
struct simple_amp *simple_amp = snd_soc_component_get_drvdata(c);
int val;
switch (event) {
case SND_SOC_DAPM_POST_PMU:
val = 1;
break;
case SND_SOC_DAPM_PRE_PMD:
val = 0;
break;
default:
WARN(1, "Unexpected event");
return -EINVAL;
}
gpiod_set_value_cansleep(simple_amp->gpiod_enable, val);
return 0;
}
static const struct snd_soc_dapm_widget simple_amp_dapm_widgets[] = {
SND_SOC_DAPM_INPUT("INL"),
SND_SOC_DAPM_INPUT("INR"),
SND_SOC_DAPM_OUT_DRV_E("DRV", SND_SOC_NOPM, 0, 0, NULL, 0, simple_amp_power_event,
(SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD)),
SND_SOC_DAPM_OUTPUT("OUTL"),
SND_SOC_DAPM_OUTPUT("OUTR"),
SND_SOC_DAPM_REGULATOR_SUPPLY("VCC", 20, 0),
};
static const struct snd_soc_dapm_route simple_amp_dapm_routes[] = {
{ "DRV", NULL, "INL" },
{ "DRV", NULL, "INR" },
{ "OUTL", NULL, "VCC" },
{ "OUTR", NULL, "VCC" },
{ "OUTL", NULL, "DRV" },
{ "OUTR", NULL, "DRV" },
};
static const struct snd_soc_dapm_widget simple_amp_mono_pga_dapm_widgets[] = {
SND_SOC_DAPM_INPUT("IN"),
SND_SOC_DAPM_OUTPUT("OUT"),
SND_SOC_DAPM_PGA_E("PGA", SND_SOC_NOPM, 0, 0, NULL, 0, simple_amp_power_event,
(SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD)),
SND_SOC_DAPM_REGULATOR_SUPPLY("vdd", 0, 0),
};
static const struct snd_soc_dapm_route simple_amp_mono_pga_dapm_routes[] = {
{ "PGA", NULL, "IN" },
{ "PGA", NULL, "vdd" },
{ "OUT", NULL, "PGA" },
};
static const struct snd_soc_dapm_widget simple_amp_stereo_pga_dapm_widgets[] = {
SND_SOC_DAPM_INPUT("INL"),
SND_SOC_DAPM_INPUT("INR"),
SND_SOC_DAPM_OUTPUT("OUTL"),
SND_SOC_DAPM_OUTPUT("OUTR"),
SND_SOC_DAPM_PGA_E("PGA", SND_SOC_NOPM, 0, 0, NULL, 0, simple_amp_power_event,
(SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD)),
SND_SOC_DAPM_REGULATOR_SUPPLY("vdd", 0, 0),
};
static const struct snd_soc_dapm_route simple_amp_stereo_pga_dapm_routes[] = {
{ "PGA", NULL, "INL" },
{ "PGA", NULL, "INR" },
{ "PGA", NULL, "vdd" },
{ "OUTL", NULL, "PGA" },
{ "OUTR", NULL, "PGA" },
};
static int simple_amp_single_kctrl_write_gpio(struct simple_amp_single *single,
int kctrl_val)
{
int gpio_val;
gpio_val = single->is_inverted ? !kctrl_val : kctrl_val;
return gpiod_set_value_cansleep(single->gpio, gpio_val);
}
static int simple_amp_single_kctrl_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 1;
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
return 0;
}
static int simple_amp_single_kctrl_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct simple_amp_single *single = (struct simple_amp_single *)kcontrol->private_value;
ucontrol->value.integer.value[0] = single->kctrl_val;
return 0;
}
static int simple_amp_single_kctrl_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct simple_amp_single *single = (struct simple_amp_single *)kcontrol->private_value;
int kctrl_val;
int err;
kctrl_val = ucontrol->value.integer.value[0] ? 1 : 0;
if (kctrl_val == single->kctrl_val)
return 0;
err = simple_amp_single_kctrl_write_gpio(single, kctrl_val);
if (err)
return err;
single->kctrl_val = kctrl_val;
return 1;
}
static int simple_amp_single_add_kcontrol(struct snd_soc_component *component,
struct simple_amp_single *single)
{
struct snd_kcontrol_new control = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = single->control_name,
.info = simple_amp_single_kctrl_info,
.get = simple_amp_single_kctrl_get,
.put = simple_amp_single_kctrl_put,
.private_value = (unsigned long)single,
};
int ret;
ret = simple_amp_single_kctrl_write_gpio(single, single->kctrl_val);
if (ret)
return ret;
return snd_soc_add_component_controls(component, &control, 1);
}
static u32 simple_amp_multi_ranges_kctrl_to_gpio(u32 kctrl_val,
struct simple_amp_ranges *ranges)
{
struct simple_amp_range *range;
u32 index = kctrl_val;
unsigned int i;
for (i = 0; i < ranges->nb_ranges; i++) {
range = &ranges->tab_ranges[i];
if (index < range->nb_points)
return (range->max.gpio_val >= range->min.gpio_val) ?
range->min.gpio_val + index :
range->min.gpio_val - index;
index -= range->nb_points;
}
WARN(1, "kctrl_val %u out of ranges\n", kctrl_val);
return ranges->tab_ranges[ranges->nb_ranges - 1].max.gpio_val;
}
static int simple_amp_multi_kctrl_write_gpios(struct simple_amp_multi *multi,
u32 kctrl_val)
{
DECLARE_BITMAP(bm, 32);
u32 gpio_val;
if (kctrl_val > multi->kctrl_max)
return -EINVAL;
if (multi->mode == SIMPLE_AMP_MODE_RANGES)
gpio_val = simple_amp_multi_ranges_kctrl_to_gpio(kctrl_val,
&multi->ranges);
else
gpio_val = kctrl_val;
bitmap_from_arr32(bm, &gpio_val, multi->gpios->ndescs);
return gpiod_multi_set_value_cansleep(multi->gpios, bm);
}
static int simple_amp_multi_kctrl_int_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
struct simple_amp_multi *multi = (struct simple_amp_multi *)kcontrol->private_value;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = multi->kctrl_max;
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
return 0;
}
static int simple_amp_multi_kctrl_int_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct simple_amp_multi *multi = (struct simple_amp_multi *)kcontrol->private_value;
ucontrol->value.integer.value[0] = multi->kctrl_val;
return 0;
}
static int simple_amp_multi_kctrl_int_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct simple_amp_multi *multi = (struct simple_amp_multi *)kcontrol->private_value;
u32 kctrl_val;
int ret;
kctrl_val = ucontrol->value.integer.value[0];
if (kctrl_val == multi->kctrl_val)
return 0;
ret = simple_amp_multi_kctrl_write_gpios(multi, kctrl_val);
if (ret)
return ret;
multi->kctrl_val = kctrl_val;
return 1;
}
static int simple_amp_multi_kctrl_enum_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
struct simple_amp_multi *multi = (struct simple_amp_multi *)kcontrol->private_value;
return snd_ctl_enum_info(uinfo, 1, multi->labels.nb_labels,
multi->labels.tab_labels);
}
static int simple_amp_multi_kctrl_enum_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct simple_amp_multi *multi = (struct simple_amp_multi *)kcontrol->private_value;
ucontrol->value.enumerated.item[0] = multi->kctrl_val;
return 0;
}
static int simple_amp_multi_kctrl_enum_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct simple_amp_multi *multi = (struct simple_amp_multi *)kcontrol->private_value;
u32 kctrl_val;
int ret;
kctrl_val = ucontrol->value.enumerated.item[0];
if (kctrl_val == multi->kctrl_val)
return 0;
ret = simple_amp_multi_kctrl_write_gpios(multi, kctrl_val);
if (ret)
return ret;
multi->kctrl_val = kctrl_val;
return 1;
}
static unsigned int *simple_amp_alloc_tlv_ranges(const struct simple_amp_ranges *ranges)
{
unsigned int index;
unsigned int *tlv;
unsigned int *t;
unsigned int i;
tlv = kzalloc_objs(*tlv, 2 + ranges->nb_ranges * 6, GFP_KERNEL);
if (!tlv)
return NULL;
t = tlv;
*t++ = SNDRV_CTL_TLVT_DB_RANGE;
*t++ = ranges->nb_ranges * 6 * sizeof(*tlv);
index = 0;
for (i = 0; i < ranges->nb_ranges; i++) {
*t++ = index;
index += ranges->tab_ranges[i].nb_points;
*t++ = index - 1;
*t++ = SNDRV_CTL_TLVT_DB_MINMAX;
*t++ = 2 * sizeof(*tlv);
*t++ = ranges->tab_ranges[i].min.gain_db;
*t++ = ranges->tab_ranges[i].max.gain_db;
}
return tlv;
}
static int simple_amp_multi_add_kcontrol(struct snd_soc_component *component,
struct simple_amp_multi *multi)
{
struct snd_kcontrol_new control = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = multi->control_name,
.info = simple_amp_multi_kctrl_int_info,
.get = simple_amp_multi_kctrl_int_get,
.put = simple_amp_multi_kctrl_int_put,
.private_value = (unsigned long)multi,
};
int ret;
switch (multi->mode) {
case SIMPLE_AMP_MODE_RANGES:
multi->tlv_array = simple_amp_alloc_tlv_ranges(&multi->ranges);
if (!multi->tlv_array)
return -ENOMEM;
control.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |
SNDRV_CTL_ELEM_ACCESS_READWRITE;
control.tlv.p = multi->tlv_array;
break;
case SIMPLE_AMP_MODE_LABELS:
control.info = simple_amp_multi_kctrl_enum_info;
control.get = simple_amp_multi_kctrl_enum_get;
control.put = simple_amp_multi_kctrl_enum_put;
break;
case SIMPLE_AMP_MODE_NONE:
break;
default:
return -EINVAL;
}
ret = simple_amp_multi_kctrl_write_gpios(multi, multi->kctrl_val);
if (ret)
goto err_free_tlv_array;
ret = snd_soc_add_component_controls(component, &control, 1);
if (ret)
goto err_free_tlv_array;
return 0;
err_free_tlv_array:
kfree(multi->tlv_array);
return ret;
}
static int simple_amp_add_basic_dapm(struct snd_soc_component *component)
{
struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component);
struct simple_amp *simple_amp = snd_soc_component_get_drvdata(component);
struct device *dev = component->dev;
int ret;
ret = snd_soc_dapm_new_controls(dapm, simple_amp->data->dapm_widgets,
simple_amp->data->num_dapm_widgets);
if (ret) {
dev_err(dev, "Failed to add basic dapm widgets (%d)\n", ret);
return ret;
}
ret = snd_soc_dapm_add_routes(dapm, simple_amp->data->dapm_routes,
simple_amp->data->num_dapm_routes);
if (ret) {
dev_err(dev, "Failed to add basic dapm routes (%d)\n", ret);
return ret;
}
return 0;
}
struct simple_amp_supply {
const char *prop_name;
const struct snd_soc_dapm_widget dapm_widget;
const struct snd_soc_dapm_route dapm_route;
};
static const struct simple_amp_supply simple_amp_supplies[] = {
{
.prop_name = "vddio-supply",
.dapm_widget = SND_SOC_DAPM_REGULATOR_SUPPLY("vddio", 0, 0),
.dapm_route = { "PGA", NULL, "vddio" },
}, {
.prop_name = "vdda1-supply",
.dapm_widget = SND_SOC_DAPM_REGULATOR_SUPPLY("vdda1", 0, 0),
.dapm_route = { "PGA", NULL, "vdda1" },
}, {
.prop_name = "vdda2-supply",
.dapm_widget = SND_SOC_DAPM_REGULATOR_SUPPLY("vdda2", 0, 0),
.dapm_route = { "PGA", NULL, "vdda2" },
},
{ }
};
static int simple_amp_add_power_supplies(struct snd_soc_component *component)
{
struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component);
struct simple_amp *simple_amp = snd_soc_component_get_drvdata(component);
const struct simple_amp_supply *supply;
struct device *dev = component->dev;
int ret;
if (!(simple_amp->data->supports & SIMPLE_AUDIO_SUPPORT_PGA)) {
dev_err(dev, "Extra power supplied need PGA\n");
return -EINVAL;
}
supply = simple_amp_supplies;
do {
if (!of_property_present(dev->of_node, supply->prop_name))
continue;
ret = snd_soc_dapm_new_controls(dapm, &supply->dapm_widget, 1);
if (ret) {
dev_err(dev, "Failed to add control for '%s' (%d)\n",
supply->prop_name, ret);
return ret;
}
ret = snd_soc_dapm_add_routes(dapm, &supply->dapm_route, 1);
if (ret) {
dev_err(dev, "Failed to add route for '%s' (%d)\n",
supply->prop_name, ret);
return ret;
}
} while ((++supply)->prop_name);
return 0;
}
static int simple_amp_component_probe(struct snd_soc_component *component)
{
struct simple_amp *simple_amp = snd_soc_component_get_drvdata(component);
int ret;
ret = simple_amp_add_basic_dapm(component);
if (ret)
return ret;
if (simple_amp->data->supports & SIMPLE_AUDIO_SUPPORT_POWER_SUPPLIES) {
ret = simple_amp_add_power_supplies(component);
if (ret)
return ret;
}
if (simple_amp->mute.gpio) {
simple_amp->mute.is_inverted = true;
simple_amp->mute.kctrl_val = 1;
ret = simple_amp_single_add_kcontrol(component, &simple_amp->mute);
if (ret)
return ret;
}
if (simple_amp->bypass.gpio) {
ret = simple_amp_single_add_kcontrol(component, &simple_amp->bypass);
if (ret)
return ret;
}
if (simple_amp->gain.gpios) {
ret = simple_amp_multi_add_kcontrol(component, &simple_amp->gain);
if (ret)
return ret;
}
return 0;
}
static void simple_amp_component_remove(struct snd_soc_component *component)
{
struct simple_amp *simple_amp = snd_soc_component_get_drvdata(component);
kfree(simple_amp->gain.tlv_array);
simple_amp->gain.tlv_array = NULL;
}
static const struct snd_soc_component_driver simple_amp_component_driver = {
.probe = simple_amp_component_probe,
.remove = simple_amp_component_remove,
};
static int simple_amp_parse_single_gpio(struct device *dev,
struct simple_amp_single *single,
const char *gpio_property)
{
single->is_inverted = false;
single->kctrl_val = 0;
single->gpio = devm_gpiod_get_optional(dev, gpio_property, GPIOD_OUT_LOW);
if (IS_ERR(single->gpio))
return dev_err_probe(dev, PTR_ERR(single->gpio),
"Failed to get '%s' gpio\n",
gpio_property);
return 0;
}
static int simple_amp_cmp_ranges(const void *a, const void *b)
{
const struct simple_amp_range *a_range = a;
const struct simple_amp_range *b_range = b;
return a_range->min.gain_db - b_range->max.gain_db;
}
static int simple_amp_check_new_range(const struct simple_amp_range *new_range,
const struct simple_amp_range *tab_ranges,
unsigned int nb_ranges)
{
unsigned int i;
for (i = 0; i < nb_ranges; i++) {
if (new_range->min.gain_db >= tab_ranges[i].min.gain_db &&
new_range->min.gain_db <= tab_ranges[i].max.gain_db)
return -EINVAL;
if (new_range->max.gain_db >= tab_ranges[i].min.gain_db &&
new_range->max.gain_db <= tab_ranges[i].max.gain_db)
return -EINVAL;
if (new_range->min.gain_db <= tab_ranges[i].min.gain_db &&
new_range->max.gain_db >= tab_ranges[i].max.gain_db)
return -EINVAL;
}
return 0;
}
static int simple_amp_parse_ranges(struct device *dev,
struct simple_amp_multi *multi,
const char *ranges_property)
{
struct simple_amp_ranges *ranges = &multi->ranges;
struct simple_amp_range *range;
struct device_node *np = dev->of_node;
struct simple_amp_point first_point;
unsigned int max_gpio_val;
unsigned int i;
int ret;
u32 u;
s32 s;
max_gpio_val = (1 << multi->gpios->ndescs) - 1;
ret = of_property_count_u32_elems(np, ranges_property);
if (ret < 0)
return ret;
if (ret == 0)
return -EINVAL;
if (ret % 4)
return -EINVAL;
ranges->nb_ranges = ret / 4;
if (ranges->nb_ranges > max_gpio_val + 1)
return -EINVAL;
ranges->tab_ranges = devm_kcalloc(dev, ranges->nb_ranges,
sizeof(*ranges->tab_ranges),
GFP_KERNEL);
if (!ranges->tab_ranges)
return -ENOMEM;
multi->kctrl_max = 0;
for (i = 0; i < ranges->nb_ranges; i++) {
range = &ranges->tab_ranges[i];
ret = of_property_read_u32_index(np, ranges_property, i * 4, &u);
if (ret)
return ret;
if (u > max_gpio_val)
return -EINVAL;
range->min.gpio_val = u;
ret = of_property_read_s32_index(np, ranges_property, i * 4 + 1, &s);
if (ret)
return ret;
range->min.gain_db = s;
ret = of_property_read_u32_index(np, ranges_property, i * 4 + 2, &u);
if (ret)
return ret;
if (u > max_gpio_val)
return -EINVAL;
range->max.gpio_val = u;
ret = of_property_read_s32_index(np, ranges_property, i * 4 + 3, &s);
if (ret)
return ret;
range->max.gain_db = s;
if (i == 0)
first_point = range->min;
if (range->min.gain_db > range->max.gain_db)
swap(range->min, range->max);
ret = simple_amp_check_new_range(range, ranges->tab_ranges, i);
if (ret)
return ret;
range->nb_points = abs_diff(range->min.gpio_val,
range->max.gpio_val) + 1;
multi->kctrl_max += range->nb_points;
}
multi->kctrl_max -= 1;
sort(ranges->tab_ranges, ranges->nb_ranges, sizeof(*ranges->tab_ranges),
simple_amp_cmp_ranges, NULL);
multi->kctrl_val = 0;
for (i = 0; i < ranges->nb_ranges; i++) {
range = &ranges->tab_ranges[i];
if (range->min.gpio_val == first_point.gpio_val &&
range->min.gain_db == first_point.gain_db)
break;
multi->kctrl_val += range->nb_points;
if (range->max.gpio_val == first_point.gpio_val &&
range->max.gain_db == first_point.gain_db) {
multi->kctrl_val--;
break;
}
}
return 0;
}
static int simple_amp_parse_labels(struct device *dev,
struct simple_amp_multi *multi,
const char *labels_property)
{
struct simple_amp_labels *labels = &multi->labels;
struct device_node *np = dev->of_node;
int ret;
ret = of_property_count_strings(np, labels_property);
if (ret < 0)
return ret;
if (ret == 0)
return -EINVAL;
labels->nb_labels = ret;
if (labels->nb_labels > (1 << multi->gpios->ndescs))
return -EINVAL;
labels->tab_labels = devm_kcalloc(dev, labels->nb_labels,
sizeof(*labels->tab_labels),
GFP_KERNEL);
if (!labels->tab_labels)
return -ENOMEM;
multi->kctrl_max = labels->nb_labels - 1;
multi->kctrl_val = 0;
return of_property_read_string_array(np, labels_property, labels->tab_labels,
labels->nb_labels);
}
static int simple_amp_parse_multi_gpio(struct device *dev,
struct simple_amp_multi *multi,
const char *gpios_property,
const char *ranges_property,
const char *labels_property)
{
struct device_node *np = dev->of_node;
int ret;
multi->kctrl_val = 0;
multi->gpios = devm_gpiod_get_array_optional(dev, gpios_property, GPIOD_OUT_LOW);
if (IS_ERR(multi->gpios))
return dev_err_probe(dev, PTR_ERR(multi->gpios),
"Failed to get '%s' gpios\n",
gpios_property);
if (!multi->gpios)
return 0;
if (multi->gpios->ndescs > 16)
return dev_err_probe(dev, -EINVAL,
"Number of '%s' gpios limited to 16\n",
gpios_property);
multi->kctrl_max = (1 << multi->gpios->ndescs) - 1;
multi->mode = SIMPLE_AMP_MODE_NONE;
if (of_property_present(np, ranges_property)) {
ret = simple_amp_parse_ranges(dev, multi, ranges_property);
if (ret < 0)
return dev_err_probe(dev, ret, "Failed to parse '%s'\n",
ranges_property);
multi->mode = SIMPLE_AMP_MODE_RANGES;
} else if (of_property_present(np, labels_property)) {
ret = simple_amp_parse_labels(dev, multi, labels_property);
if (ret < 0)
return dev_err_probe(dev, ret, "Failed to parse '%s'\n",
labels_property);
multi->mode = SIMPLE_AMP_MODE_LABELS;
}
return 0;
}
static int simple_amp_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct simple_amp *simple_amp;
int ret;
simple_amp = devm_kzalloc(dev, sizeof(*simple_amp), GFP_KERNEL);
if (!simple_amp)
return -ENOMEM;
platform_set_drvdata(pdev, simple_amp);
simple_amp->data = of_device_get_match_data(dev);
if (!simple_amp->data)
return -EINVAL;
simple_amp->gpiod_enable = devm_gpiod_get_optional(dev, "enable",
GPIOD_OUT_LOW);
if (IS_ERR(simple_amp->gpiod_enable))
return dev_err_probe(dev, PTR_ERR(simple_amp->gpiod_enable),
"Failed to get 'enable' gpio");
if (simple_amp->data->supports & SIMPLE_AUDIO_SUPPORT_MUTE) {
ret = simple_amp_parse_single_gpio(dev, &simple_amp->mute, "mute");
if (ret)
return ret;
}
if (simple_amp->data->supports & SIMPLE_AUDIO_SUPPORT_BYPASS) {
ret = simple_amp_parse_single_gpio(dev, &simple_amp->bypass, "bypass");
if (ret)
return ret;
}
if (simple_amp->data->supports & SIMPLE_AUDIO_SUPPORT_PGA) {
ret = simple_amp_parse_multi_gpio(dev, &simple_amp->gain, "gain",
"gain-ranges", "gain-labels");
if (ret)
return ret;
}
simple_amp->gain.control_name = "Volume";
simple_amp->mute.control_name = "Switch";
simple_amp->bypass.control_name = "Bypass Switch";
if (simple_amp->gain.mode == SIMPLE_AMP_MODE_LABELS) {
simple_amp->mute.control_name = "Out Switch";
}
return devm_snd_soc_register_component(dev,
&simple_amp_component_driver,
NULL, 0);
}
static const struct simple_amp_data simple_audio_amplifier_data = {
.dapm_widgets = simple_amp_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(simple_amp_dapm_widgets),
.dapm_routes = simple_amp_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(simple_amp_dapm_routes),
};
static const struct simple_amp_data simple_audio_mono_pga_data = {
.supports = SIMPLE_AUDIO_SUPPORT_PGA |
SIMPLE_AUDIO_SUPPORT_POWER_SUPPLIES |
SIMPLE_AUDIO_SUPPORT_MUTE |
SIMPLE_AUDIO_SUPPORT_BYPASS,
.dapm_widgets = simple_amp_mono_pga_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(simple_amp_mono_pga_dapm_widgets),
.dapm_routes = simple_amp_mono_pga_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(simple_amp_mono_pga_dapm_routes),
};
static const struct simple_amp_data simple_audio_stereo_pga_data = {
.supports = SIMPLE_AUDIO_SUPPORT_PGA |
SIMPLE_AUDIO_SUPPORT_POWER_SUPPLIES |
SIMPLE_AUDIO_SUPPORT_MUTE |
SIMPLE_AUDIO_SUPPORT_BYPASS,
.dapm_widgets = simple_amp_stereo_pga_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(simple_amp_stereo_pga_dapm_widgets),
.dapm_routes = simple_amp_stereo_pga_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(simple_amp_stereo_pga_dapm_routes),
};
static const struct of_device_id simple_amp_ids[] = {
{ .compatible = "dioo,dio2125", .data = &simple_audio_amplifier_data},
{ .compatible = "simple-audio-amplifier", .data = &simple_audio_amplifier_data},
{ .compatible = "gpio-audio-amp-mono", .data = &simple_audio_mono_pga_data},
{ .compatible = "gpio-audio-amp-stereo", .data = &simple_audio_stereo_pga_data},
{ }
};
MODULE_DEVICE_TABLE(of, simple_amp_ids);
static struct platform_driver simple_amp_driver = {
.driver = {
.name = "simple-amplifier",
.of_match_table = simple_amp_ids,
},
.probe = simple_amp_probe,
};
module_platform_driver(simple_amp_driver);
MODULE_DESCRIPTION("ASoC Simple Audio Amplifier driver");
MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
MODULE_AUTHOR("Herve Codina <herve.codina@bootlin.com>");
MODULE_LICENSE("GPL");