diff options
Diffstat (limited to 'sound/soc/codecs/wsa881x.c')
-rw-r--r-- | sound/soc/codecs/wsa881x.c | 200 |
1 files changed, 142 insertions, 58 deletions
diff --git a/sound/soc/codecs/wsa881x.c b/sound/soc/codecs/wsa881x.c index d39d479e2378..6627d2da3722 100644 --- a/sound/soc/codecs/wsa881x.c +++ b/sound/soc/codecs/wsa881x.c @@ -5,12 +5,10 @@ #include <linux/bitops.h> #include <linux/gpio.h> #include <linux/gpio/consumer.h> -#include <linux/interrupt.h> #include <linux/module.h> -#include <linux/of.h> -#include <linux/of_gpio.h> #include <linux/regmap.h> #include <linux/slab.h> +#include <linux/pm_runtime.h> #include <linux/soundwire/sdw.h> #include <linux/soundwire/sdw_registers.h> #include <linux/soundwire/sdw_type.h> @@ -198,15 +196,11 @@ #define WSA881X_OCP_CTL_TIMER_SEC 2 #define WSA881X_OCP_CTL_TEMP_CELSIUS 25 #define WSA881X_OCP_CTL_POLL_TIMER_SEC 60 +#define WSA881X_PROBE_TIMEOUT 1000 #define WSA881X_PA_GAIN_TLV(xname, reg, shift, max, invert, tlv_array) \ -{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ - .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\ - SNDRV_CTL_ELEM_ACCESS_READWRITE,\ - .tlv.p = (tlv_array), \ - .info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\ - .put = wsa881x_put_pa_gain, \ - .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert, 0) } + SOC_SINGLE_EXT_TLV(xname, reg, shift, max, invert, \ + snd_soc_get_volsw, wsa881x_put_pa_gain, tlv_array) static struct reg_default wsa881x_defaults[] = { { WSA881X_CHIP_ID0, 0x00 }, @@ -387,33 +381,32 @@ enum wsa_port_ids { /* 4 ports */ static struct sdw_dpn_prop wsa_sink_dpn_prop[WSA881X_MAX_SWR_PORTS] = { - { - /* DAC */ - .num = 1, + [WSA881X_PORT_DAC] = { + .num = WSA881X_PORT_DAC + 1, .type = SDW_DPN_SIMPLE, .min_ch = 1, .max_ch = 1, .simple_ch_prep_sm = true, .read_only_wordlength = true, - }, { - /* COMP */ - .num = 2, + }, + [WSA881X_PORT_COMP] = { + .num = WSA881X_PORT_COMP + 1, .type = SDW_DPN_SIMPLE, .min_ch = 1, .max_ch = 1, .simple_ch_prep_sm = true, .read_only_wordlength = true, - }, { - /* BOOST */ - .num = 3, + }, + [WSA881X_PORT_BOOST] = { + .num = WSA881X_PORT_BOOST + 1, .type = SDW_DPN_SIMPLE, .min_ch = 1, .max_ch = 1, .simple_ch_prep_sm = true, .read_only_wordlength = true, - }, { - /* VISENSE */ - .num = 4, + }, + [WSA881X_PORT_VISENSE] = { + .num = WSA881X_PORT_VISENSE + 1, .type = SDW_DPN_SIMPLE, .min_ch = 1, .max_ch = 1, @@ -422,18 +415,21 @@ static struct sdw_dpn_prop wsa_sink_dpn_prop[WSA881X_MAX_SWR_PORTS] = { } }; -static struct sdw_port_config wsa881x_pconfig[WSA881X_MAX_SWR_PORTS] = { - { - .num = 1, +static const struct sdw_port_config wsa881x_pconfig[WSA881X_MAX_SWR_PORTS] = { + [WSA881X_PORT_DAC] = { + .num = WSA881X_PORT_DAC + 1, .ch_mask = 0x1, - }, { - .num = 2, + }, + [WSA881X_PORT_COMP] = { + .num = WSA881X_PORT_COMP + 1, .ch_mask = 0xf, - }, { - .num = 3, + }, + [WSA881X_PORT_BOOST] = { + .num = WSA881X_PORT_BOOST + 1, .ch_mask = 0x3, - }, { /* IV feedback */ - .num = 4, + }, + [WSA881X_PORT_VISENSE] = { + .num = WSA881X_PORT_VISENSE + 1, .ch_mask = 0x3, }, }; @@ -635,17 +631,17 @@ static bool wsa881x_volatile_register(struct device *dev, unsigned int reg) } } -static struct regmap_config wsa881x_regmap_config = { +static const struct regmap_config wsa881x_regmap_config = { .reg_bits = 32, .val_bits = 8, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .reg_defaults = wsa881x_defaults, + .max_register = WSA881X_SPKR_STATUS3, .num_reg_defaults = ARRAY_SIZE(wsa881x_defaults), .volatile_reg = wsa881x_volatile_register, .readable_reg = wsa881x_readable_register, .reg_format_endian = REGMAP_ENDIAN_NATIVE, .val_format_endian = REGMAP_ENDIAN_NATIVE, - .can_multi_write = true, }; enum { @@ -676,7 +672,11 @@ struct wsa881x_priv { struct sdw_stream_runtime *sruntime; struct sdw_port_config port_config[WSA881X_MAX_SWR_PORTS]; struct gpio_desc *sd_n; - int version; + /* + * Logical state for SD_N GPIO: high for shutdown, low for enable. + * For backwards compatibility. + */ + unsigned int sd_n_val; int active_ports; bool port_prepared[WSA881X_MAX_SWR_PORTS]; bool port_enable[WSA881X_MAX_SWR_PORTS]; @@ -687,7 +687,6 @@ static void wsa881x_init(struct wsa881x_priv *wsa881x) struct regmap *rm = wsa881x->regmap; unsigned int val = 0; - regmap_read(rm, WSA881X_CHIP_ID1, &wsa881x->version); regmap_register_patch(wsa881x->regmap, wsa881x_rev_2_0, ARRAY_SIZE(wsa881x_rev_2_0)); @@ -746,6 +745,10 @@ static int wsa881x_put_pa_gain(struct snd_kcontrol *kc, unsigned int mask = (1 << fls(max)) - 1; int val, ret, min_gain, max_gain; + ret = pm_runtime_resume_and_get(comp->dev); + if (ret < 0 && ret != -EACCES) + return ret; + max_gain = (max - ucontrol->value.integer.value[0]) & mask; /* * Gain has to set incrementally in 4 steps @@ -771,7 +774,11 @@ static int wsa881x_put_pa_gain(struct snd_kcontrol *kc, usleep_range(1000, 1010); } - return 0; + + pm_runtime_mark_last_busy(comp->dev); + pm_runtime_put_autosuspend(comp->dev); + + return 1; } static int wsa881x_get_port(struct snd_kcontrol *kcontrol, @@ -815,15 +822,22 @@ static int wsa881x_set_port(struct snd_kcontrol *kcontrol, (struct soc_mixer_control *)kcontrol->private_value; int portidx = mixer->reg; - if (ucontrol->value.integer.value[0]) + if (ucontrol->value.integer.value[0]) { + if (data->port_enable[portidx]) + return 0; + data->port_enable[portidx] = true; - else + } else { + if (!data->port_enable[portidx]) + return 0; + data->port_enable[portidx] = false; + } if (portidx == WSA881X_PORT_BOOST) /* Boost Switch */ wsa881x_boost_ctrl(comp, data->port_enable[portidx]); - return 0; + return 1; } static const char * const smart_boost_lvl_text[] = { @@ -1013,11 +1027,11 @@ static int wsa881x_digital_mute(struct snd_soc_dai *dai, int mute, int stream) return 0; } -static struct snd_soc_dai_ops wsa881x_dai_ops = { +static const struct snd_soc_dai_ops wsa881x_dai_ops = { .hw_params = wsa881x_hw_params, .hw_free = wsa881x_hw_free, .mute_stream = wsa881x_digital_mute, - .set_sdw_stream = wsa881x_set_sdw_stream, + .set_stream = wsa881x_set_sdw_stream, }; static struct snd_soc_dai_driver wsa881x_dais[] = { @@ -1026,6 +1040,8 @@ static struct snd_soc_dai_driver wsa881x_dais[] = { .id = 0, .playback = { .stream_name = "SPKR Playback", + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, .rate_max = 48000, .rate_min = 48000, .channels_min = 1, @@ -1044,6 +1060,7 @@ static const struct snd_soc_component_driver wsa881x_component_drv = { .num_dapm_widgets = ARRAY_SIZE(wsa881x_dapm_widgets), .dapm_routes = wsa881x_audio_map, .num_dapm_routes = ARRAY_SIZE(wsa881x_audio_map), + .endianness = 1, }; static int wsa881x_update_status(struct sdw_slave *slave, @@ -1080,7 +1097,7 @@ static int wsa881x_bus_config(struct sdw_slave *slave, return 0; } -static struct sdw_slave_ops wsa881x_slave_ops = { +static const struct sdw_slave_ops wsa881x_slave_ops = { .update_status = wsa881x_update_status, .bus_config = wsa881x_bus_config, .port_prep = wsa881x_port_prep, @@ -1090,42 +1107,108 @@ static int wsa881x_probe(struct sdw_slave *pdev, const struct sdw_device_id *id) { struct wsa881x_priv *wsa881x; + struct device *dev = &pdev->dev; - wsa881x = devm_kzalloc(&pdev->dev, sizeof(*wsa881x), GFP_KERNEL); + wsa881x = devm_kzalloc(dev, sizeof(*wsa881x), GFP_KERNEL); if (!wsa881x) return -ENOMEM; - wsa881x->sd_n = devm_gpiod_get_optional(&pdev->dev, "powerdown", + wsa881x->sd_n = devm_gpiod_get_optional(dev, "powerdown", GPIOD_FLAGS_BIT_NONEXCLUSIVE); - if (IS_ERR(wsa881x->sd_n)) { - dev_err(&pdev->dev, "Shutdown Control GPIO not found\n"); - return PTR_ERR(wsa881x->sd_n); - } + if (IS_ERR(wsa881x->sd_n)) + return dev_err_probe(dev, PTR_ERR(wsa881x->sd_n), + "Shutdown Control GPIO not found\n"); - dev_set_drvdata(&pdev->dev, wsa881x); + /* + * Backwards compatibility work-around. + * + * The SD_N GPIO is active low, however upstream DTS used always active + * high. Changing the flag in driver and DTS will break backwards + * compatibility, so add a simple value inversion to work with both old + * and new DTS. + * + * This won't work properly with DTS using the flags properly in cases: + * 1. Old DTS with proper ACTIVE_LOW, however such case was broken + * before as the driver required the active high. + * 2. New DTS with proper ACTIVE_HIGH (intended), which is rare case + * (not existing upstream) but possible. This is the price of + * backwards compatibility, therefore this hack should be removed at + * some point. + */ + wsa881x->sd_n_val = gpiod_is_active_low(wsa881x->sd_n); + if (!wsa881x->sd_n_val) + dev_warn(dev, "Using ACTIVE_HIGH for shutdown GPIO. Your DTB might be outdated or you use unsupported configuration for the GPIO."); + + dev_set_drvdata(dev, wsa881x); wsa881x->slave = pdev; - wsa881x->dev = &pdev->dev; + wsa881x->dev = dev; wsa881x->sconfig.ch_count = 1; wsa881x->sconfig.bps = 1; wsa881x->sconfig.frame_rate = 48000; wsa881x->sconfig.direction = SDW_DATA_DIR_RX; wsa881x->sconfig.type = SDW_STREAM_PDM; - pdev->prop.sink_ports = GENMASK(WSA881X_MAX_SWR_PORTS, 0); + pdev->prop.sink_ports = GENMASK(WSA881X_MAX_SWR_PORTS - 1, 0); pdev->prop.sink_dpn_prop = wsa_sink_dpn_prop; - gpiod_direction_output(wsa881x->sd_n, 1); + pdev->prop.scp_int1_mask = SDW_SCP_INT1_BUS_CLASH | SDW_SCP_INT1_PARITY; + pdev->prop.clk_stop_mode1 = true; + gpiod_direction_output(wsa881x->sd_n, !wsa881x->sd_n_val); wsa881x->regmap = devm_regmap_init_sdw(pdev, &wsa881x_regmap_config); - if (IS_ERR(wsa881x->regmap)) { - dev_err(&pdev->dev, "regmap_init failed\n"); - return PTR_ERR(wsa881x->regmap); - } + if (IS_ERR(wsa881x->regmap)) + return dev_err_probe(dev, PTR_ERR(wsa881x->regmap), "regmap_init failed\n"); + + pm_runtime_set_autosuspend_delay(dev, 3000); + pm_runtime_use_autosuspend(dev); + pm_runtime_mark_last_busy(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); - return devm_snd_soc_register_component(&pdev->dev, + return devm_snd_soc_register_component(dev, &wsa881x_component_drv, wsa881x_dais, ARRAY_SIZE(wsa881x_dais)); } +static int wsa881x_runtime_suspend(struct device *dev) +{ + struct regmap *regmap = dev_get_regmap(dev, NULL); + struct wsa881x_priv *wsa881x = dev_get_drvdata(dev); + + gpiod_direction_output(wsa881x->sd_n, wsa881x->sd_n_val); + + regcache_cache_only(regmap, true); + regcache_mark_dirty(regmap); + + return 0; +} + +static int wsa881x_runtime_resume(struct device *dev) +{ + struct sdw_slave *slave = dev_to_sdw_dev(dev); + struct regmap *regmap = dev_get_regmap(dev, NULL); + struct wsa881x_priv *wsa881x = dev_get_drvdata(dev); + unsigned long time; + + gpiod_direction_output(wsa881x->sd_n, !wsa881x->sd_n_val); + + time = wait_for_completion_timeout(&slave->initialization_complete, + msecs_to_jiffies(WSA881X_PROBE_TIMEOUT)); + if (!time) { + dev_err(dev, "Initialization not complete, timed out\n"); + gpiod_direction_output(wsa881x->sd_n, wsa881x->sd_n_val); + return -ETIMEDOUT; + } + + regcache_cache_only(regmap, false); + regcache_sync(regmap); + + return 0; +} + +static const struct dev_pm_ops wsa881x_pm_ops = { + RUNTIME_PM_OPS(wsa881x_runtime_suspend, wsa881x_runtime_resume, NULL) +}; + static const struct sdw_device_id wsa881x_slave_id[] = { SDW_SLAVE_ENTRY(0x0217, 0x2010, 0), SDW_SLAVE_ENTRY(0x0217, 0x2110, 0), @@ -1139,6 +1222,7 @@ static struct sdw_driver wsa881x_codec_driver = { .id_table = wsa881x_slave_id, .driver = { .name = "wsa881x-codec", + .pm = pm_ptr(&wsa881x_pm_ops), } }; module_sdw_driver(wsa881x_codec_driver); |