diff options
Diffstat (limited to 'sound/soc/codecs/fs210x.c')
-rw-r--r-- | sound/soc/codecs/fs210x.c | 1586 |
1 files changed, 1586 insertions, 0 deletions
diff --git a/sound/soc/codecs/fs210x.c b/sound/soc/codecs/fs210x.c new file mode 100644 index 000000000000..e2f85714972d --- /dev/null +++ b/sound/soc/codecs/fs210x.c @@ -0,0 +1,1586 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// fs210x.c -- Driver for the FS2104/5S Audio Amplifier +// +// Copyright (C) 2016-2025 Shanghai FourSemi Semiconductor Co.,Ltd. + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/workqueue.h> +#include <sound/soc.h> +#include <sound/tlv.h> + +#include "fs210x.h" +#include "fs-amp-lib.h" + +#define FS210X_DEFAULT_FWM_NAME "fs210x_fwm.bin" +#define FS210X_DEFAULT_DAI_NAME "fs210x-aif" +#define FS2105S_DEVICE_ID 0x20 /* FS2105S */ +#define FS210X_DEVICE_ID 0x45 /* FS2104 */ +#define FS210X_REG_MAX 0xF8 +#define FS210X_INIT_SCENE 0 +#define FS210X_DEFAULT_SCENE 1 +#define FS210X_START_DELAY_MS 5 +#define FS210X_FAULT_CHECK_INTERVAL_MS 2000 +#define FS2105S_RATES (SNDRV_PCM_RATE_32000 | \ + SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | \ + SNDRV_PCM_RATE_88200 | \ + SNDRV_PCM_RATE_96000) +#define FS210X_RATES (SNDRV_PCM_RATE_16000 | FS2105S_RATES) +#define FS210X_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S24_3LE | \ + SNDRV_PCM_FMTBIT_S32_LE) +#define FS210X_NUM_SUPPLIES ARRAY_SIZE(fs210x_supply_names) + +static const char *const fs210x_supply_names[] = { + "pvdd", + "dvdd", +}; + +struct fs210x_platform_data { + const char *fwm_name; +}; + +struct fs210x_priv { + struct i2c_client *i2c; + struct device *dev; + struct regmap *regmap; + struct fs210x_platform_data pdata; + struct regulator_bulk_data supplies[FS210X_NUM_SUPPLIES]; + struct gpio_desc *gpio_sdz; + struct delayed_work start_work; + struct delayed_work fault_check_work; + struct fs_amp_lib amp_lib; + const struct fs_amp_scene *cur_scene; + struct clk *clk_bclk; + /* + * @lock: Mutex ensuring exclusive access for critical device operations + * + * This lock serializes access between the following actions: + * - Device initialization procedures(probe) + * - Enable/disable device(DAPM event) + * - Suspend/resume device(PM) + * - Runtime scene switching(control) + * - Scheduling/execution of delayed works items(delayed works) + */ + struct mutex lock; + unsigned int check_interval_ms; + unsigned int bclk; + unsigned int srate; + int scene_id; + u16 devid; + bool is_inited; + bool is_suspended; + bool is_bclk_on; + bool is_playing; +}; + +static const unsigned int fs2105s_rates[] = { + 32000, 44100, 48000, 88200, 96000 +}; + +static const struct snd_pcm_hw_constraint_list fs2105s_constraints = { + .count = ARRAY_SIZE(fs2105s_rates), + .list = fs2105s_rates, +}; + +static const unsigned int fs210x_rates[] = { + 16000, 32000, 44100, 48000, 88200, 96000 +}; + +static const struct snd_pcm_hw_constraint_list fs210x_constraints = { + .count = ARRAY_SIZE(fs210x_rates), + .list = fs210x_rates, +}; + +static const struct fs_pll_div fs210x_pll_div[] = { + /* bclk, pll1, pll2, pll3 */ + { 512000, 0x006C, 0x0120, 0x0001 }, + { 768000, 0x016C, 0x00C0, 0x0001 }, + { 1024000, 0x016C, 0x0090, 0x0001 }, + { 1536000, 0x016C, 0x0060, 0x0001 }, + { 2048000, 0x016C, 0x0090, 0x0002 }, + { 2304000, 0x016C, 0x0080, 0x0002 }, + { 3072000, 0x016C, 0x0090, 0x0003 }, + { 4096000, 0x016C, 0x0090, 0x0004 }, + { 4608000, 0x016C, 0x0080, 0x0004 }, + { 6144000, 0x016C, 0x0090, 0x0006 }, + { 8192000, 0x016C, 0x0090, 0x0008 }, + { 9216000, 0x016C, 0x0090, 0x0009 }, + { 12288000, 0x016C, 0x0090, 0x000C }, + { 16384000, 0x016C, 0x0090, 0x0010 }, + { 18432000, 0x016C, 0x0090, 0x0012 }, + { 24576000, 0x016C, 0x0090, 0x0018 }, + { 1411200, 0x016C, 0x0060, 0x0001 }, + { 2116800, 0x016C, 0x0080, 0x0002 }, + { 2822400, 0x016C, 0x0090, 0x0003 }, + { 4233600, 0x016C, 0x0080, 0x0004 }, + { 5644800, 0x016C, 0x0090, 0x0006 }, + { 8467200, 0x016C, 0x0090, 0x0009 }, + { 11289600, 0x016C, 0x0090, 0x000C }, + { 16934400, 0x016C, 0x0090, 0x0012 }, + { 22579200, 0x016C, 0x0090, 0x0018 }, + { 2000000, 0x017C, 0x0093, 0x0002 }, +}; + +static int fs210x_bclk_set(struct fs210x_priv *fs210x, bool on) +{ + int ret = 0; + + if (!fs210x || !fs210x->dev) + return -EINVAL; + + if ((fs210x->is_bclk_on ^ on) == 0) + return 0; + + if (on) { + clk_set_rate(fs210x->clk_bclk, fs210x->bclk); + ret = clk_prepare_enable(fs210x->clk_bclk); + fs210x->is_bclk_on = true; + fsleep(2000); /* >= 2ms */ + } else { + clk_disable_unprepare(fs210x->clk_bclk); + fs210x->is_bclk_on = false; + } + + return ret; +} + +static int fs210x_reg_write(struct fs210x_priv *fs210x, + u8 reg, u16 val) +{ + int ret; + + ret = regmap_write(fs210x->regmap, reg, val); + if (ret) { + dev_err(fs210x->dev, "Failed to write %02Xh: %d\n", reg, ret); + return ret; + } + + return 0; +} + +static int fs210x_reg_read(struct fs210x_priv *fs210x, + u8 reg, u16 *pval) +{ + unsigned int val; + int ret; + + ret = regmap_read(fs210x->regmap, reg, &val); + if (ret) { + dev_err(fs210x->dev, "Failed to read %02Xh: %d\n", reg, ret); + return ret; + } + + *pval = (u16)val; + + return 0; +} + +static int fs210x_reg_update_bits(struct fs210x_priv *fs210x, + u8 reg, u16 mask, u16 val) +{ + int ret; + + ret = regmap_update_bits(fs210x->regmap, reg, mask, val); + if (ret) { + dev_err(fs210x->dev, "Failed to update %02Xh: %d\n", reg, ret); + return ret; + } + + return 0; +} + +static int fs210x_reg_bulk_write(struct fs210x_priv *fs210x, + u8 reg, const void *val, u32 size) +{ + int ret; + + ret = regmap_bulk_write(fs210x->regmap, reg, val, size / 2); + if (ret) { + dev_err(fs210x->dev, "Failed to bulk write %02Xh: %d\n", + reg, ret); + return ret; + } + + return 0; +} + +static inline int fs210x_write_reg_val(struct fs210x_priv *fs210x, + const struct fs_reg_val *regv) +{ + return fs210x_reg_write(fs210x, regv->reg, regv->val); +} + +static inline int fs210x_write_reg_bits(struct fs210x_priv *fs210x, + const struct fs_reg_bits *regu) +{ + return fs210x_reg_update_bits(fs210x, + regu->reg, + regu->mask, + regu->val); +} + +static inline int fs210x_set_cmd_pkg(struct fs210x_priv *fs210x, + const struct fs_cmd_pkg *pkg, + unsigned int *offset) +{ + int delay_us; + + if (pkg->cmd >= 0x00 && pkg->cmd <= FS210X_REG_MAX) { + *offset = sizeof(pkg->regv); + return fs210x_write_reg_val(fs210x, &pkg->regv); + } else if (pkg->cmd == FS_CMD_UPDATE) { + *offset = sizeof(pkg->regb); + return fs210x_write_reg_bits(fs210x, &pkg->regb); + } else if (pkg->cmd == FS_CMD_DELAY) { + if (pkg->regv.val > FS_CMD_DELAY_MS_MAX) + return -EOPNOTSUPP; + delay_us = pkg->regv.val * 1000; /* ms -> us */ + fsleep(delay_us); + *offset = sizeof(pkg->regv); + return 0; + } + + dev_err(fs210x->dev, "Invalid pkg cmd: %d\n", pkg->cmd); + + return -EOPNOTSUPP; +} + +static int fs210x_reg_write_table(struct fs210x_priv *fs210x, + const struct fs_reg_table *reg) +{ + const struct fs_cmd_pkg *pkg; + unsigned int index, offset; + int ret; + + if (!fs210x || !fs210x->dev) + return -EINVAL; + + if (!reg || reg->size == 0) + return -EFAULT; + + for (index = 0; index < reg->size; index += offset) { + pkg = (struct fs_cmd_pkg *)(reg->buf + index); + ret = fs210x_set_cmd_pkg(fs210x, pkg, &offset); + if (ret) { + dev_err(fs210x->dev, "Failed to set cmd pkg: %02X-%d\n", + pkg->cmd, ret); + return ret; + } + } + + if (index != reg->size) { + dev_err(fs210x->dev, "Invalid reg table size: %d-%d\n", + index, reg->size); + return -EFAULT; + } + + return 0; +} + +static int fs210x_dev_play(struct fs210x_priv *fs210x) +{ + int ret; + + if (!fs210x->is_inited) + return -EFAULT; + + if (fs210x->is_playing) + return 0; + + ret = fs210x_reg_write(fs210x, FS210X_11H_SYSCTRL, + FS210X_11H_DPS_PLAY); + if (!ret) + fs210x->is_playing = true; + + fsleep(10000); /* >= 10ms */ + + return ret; +} + +static int fs210x_dev_stop(struct fs210x_priv *fs210x) +{ + int ret; + + if (!fs210x->is_inited) + return -EFAULT; + + if (!fs210x->is_playing) + return 0; + + ret = fs210x_reg_write(fs210x, FS210X_11H_SYSCTRL, + FS210X_11H_DPS_PWDN); + fs210x->is_playing = false; + + fsleep(30000); /* >= 30ms */ + + return ret; +} + +static int fs210x_set_reg_table(struct fs210x_priv *fs210x, + const struct fs_amp_scene *scene) +{ + const struct fs_amp_scene *cur_scene; + const struct fs_reg_table *reg; + + if (!fs210x || !fs210x->dev || !scene) + return -EINVAL; + + cur_scene = fs210x->cur_scene; + if (!scene->reg || cur_scene == scene) { + dev_dbg(fs210x->dev, "Skip writing reg table\n"); + return 0; + } + + reg = scene->reg; + dev_dbg(fs210x->dev, "reg table size: %d\n", reg->size); + + return fs210x_reg_write_table(fs210x, reg); +} + +static int fs210x_set_woofer_table(struct fs210x_priv *fs210x) +{ + const struct fs_file_table *woofer; + const struct fs_fwm_table *table; + int ret; + + if (!fs210x || !fs210x->dev) + return -EINVAL; + + /* NOTE: fs2105s has woofer ram only */ + if (fs210x->devid != FS2105S_DEVICE_ID) + return 0; + + table = fs210x->amp_lib.table[FS_INDEX_WOOFER]; + if (!table) { + dev_dbg(fs210x->dev, "Skip writing woofer table\n"); + return 0; + } + + woofer = (struct fs_file_table *)table->buf; + dev_dbg(fs210x->dev, "woofer table size: %d\n", woofer->size); + /* Unit of woofer data is u32(4 bytes) */ + if (woofer->size == 0 || (woofer->size & 0x3)) { + dev_err(fs210x->dev, "Invalid woofer size: %d\n", + woofer->size); + return -EINVAL; + } + + ret = fs210x_reg_write(fs210x, FS210X_46H_DACEQA, + FS2105S_46H_CAM_BURST_W); + ret |= fs210x_reg_bulk_write(fs210x, FS210X_42H_DACEQWL, + woofer->buf, woofer->size); + + return ret; +} + +static int fs210x_set_effect_table(struct fs210x_priv *fs210x, + const struct fs_amp_scene *scene) +{ + const struct fs_amp_scene *cur_scene; + const struct fs_file_table *effect; + int half_size; + int ret; + + if (!fs210x || !fs210x->dev || !scene) + return -EINVAL; + + cur_scene = fs210x->cur_scene; + if (!scene->effect || cur_scene == scene) { + dev_dbg(fs210x->dev, "Skip writing effect table\n"); + return 0; + } + + effect = scene->effect; + dev_dbg(fs210x->dev, "effect table size: %d\n", effect->size); + + /* Unit of effect data is u32(4 bytes), 2 channels */ + if (effect->size == 0 || (effect->size & 0x7)) { + dev_err(fs210x->dev, "Invalid effect size: %d\n", + effect->size); + return -EINVAL; + } + + half_size = effect->size / 2; + + /* Left channel */ + ret = fs210x_reg_write(fs210x, FS210X_46H_DACEQA, + FS210X_46H_CAM_BURST_L); + ret |= fs210x_reg_bulk_write(fs210x, FS210X_42H_DACEQWL, + effect->buf, half_size); + if (ret) + return ret; + + /* Right channel */ + ret = fs210x_reg_write(fs210x, FS210X_46H_DACEQA, + FS210X_46H_CAM_BURST_R); + ret |= fs210x_reg_bulk_write(fs210x, FS210X_42H_DACEQWL, + effect->buf + half_size, half_size); + + return ret; +} + +static int fs210x_access_dsp_ram(struct fs210x_priv *fs210x, bool enable) +{ + int ret; + + if (!fs210x || !fs210x->dev) + return -EINVAL; + + if (enable) { + ret = fs210x_reg_write(fs210x, FS210X_11H_SYSCTRL, + FS210X_11H_DPS_HIZ); + ret |= fs210x_reg_write(fs210x, FS210X_0BH_ACCKEY, + FS210X_0BH_ACCKEY_ON); + } else { + ret = fs210x_reg_write(fs210x, FS210X_0BH_ACCKEY, + FS210X_0BH_ACCKEY_OFF); + ret |= fs210x_reg_write(fs210x, FS210X_11H_SYSCTRL, + FS210X_11H_DPS_PWDN); + } + + fsleep(10000); /* >= 10ms */ + + return ret; +} + +static int fs210x_write_dsp_effect(struct fs210x_priv *fs210x, + const struct fs_amp_scene *scene, + int scene_id) +{ + int ret; + + if (!fs210x || !scene) + return -EINVAL; + + ret = fs210x_access_dsp_ram(fs210x, true); + if (ret) { + dev_err(fs210x->dev, "Failed to access dsp: %d\n", ret); + goto tag_exit; + } + + ret = fs210x_set_effect_table(fs210x, scene); + if (ret) { + dev_err(fs210x->dev, "Failed to set effect: %d\n", ret); + goto tag_exit; + } + + if (scene_id == FS210X_INIT_SCENE) + ret = fs210x_set_woofer_table(fs210x); + +tag_exit: + fs210x_reg_write(fs210x, FS210X_46H_DACEQA, + FS210X_46H_CAM_CLEAR); + fs210x_access_dsp_ram(fs210x, false); + + return ret; +} + +static int fs210x_check_scene(struct fs210x_priv *fs210x, + int scene_id, bool *skip_set) +{ + struct fs_amp_lib *amp_lib; + + if (!fs210x || !skip_set) + return -EINVAL; + + amp_lib = &fs210x->amp_lib; + if (amp_lib->scene_count == 0 || !amp_lib->scene) { + dev_err(fs210x->dev, "There's no scene data\n"); + return -EINVAL; + } + + if (scene_id < 0 || scene_id >= amp_lib->scene_count) { + dev_err(fs210x->dev, "Invalid scene_id: %d\n", scene_id); + return -EINVAL; + } + + if (fs210x->scene_id == scene_id) { + dev_dbg(fs210x->dev, "Skip to set same scene\n"); + return 0; + } + + *skip_set = false; + + return 0; +} + +static int fs210x_set_scene(struct fs210x_priv *fs210x, int scene_id) +{ + const struct fs_amp_scene *scene; + bool skip_set = true; + bool is_playing; + int ret; + + if (!fs210x || !fs210x->dev) + return -EINVAL; + + ret = fs210x_check_scene(fs210x, scene_id, &skip_set); + if (ret || skip_set) + return ret; + + scene = fs210x->amp_lib.scene + scene_id; + dev_info(fs210x->dev, "Switch scene.%d: %s\n", + scene_id, scene->name); + + is_playing = fs210x->is_playing; + if (is_playing) + fs210x_dev_stop(fs210x); + + ret = fs210x_set_reg_table(fs210x, scene); + if (ret) { + dev_err(fs210x->dev, "Failed to set reg: %d\n", ret); + return ret; + } + + ret = fs210x_write_dsp_effect(fs210x, scene, scene_id); + if (ret) { + dev_err(fs210x->dev, "Failed to write ram: %d\n", ret); + return ret; + } + + fs210x->cur_scene = scene; + fs210x->scene_id = scene_id; + + if (is_playing) + fs210x_dev_play(fs210x); + + return 0; +} + +static int fs210x_init_chip(struct fs210x_priv *fs210x) +{ + int scene_id; + int ret; + + regcache_cache_bypass(fs210x->regmap, true); + + if (!fs210x->gpio_sdz) { + /* Gpio is not found, i2c reset */ + ret = fs210x_reg_write(fs210x, FS210X_10H_PWRCTRL, + FS210X_10H_I2C_RESET); + if (ret) + goto tag_power_down; + } else { + /* gpio reset, deactivate */ + gpiod_set_value_cansleep(fs210x->gpio_sdz, 0); + } + + fsleep(10000); /* >= 10ms */ + + /* Backup scene id */ + scene_id = fs210x->scene_id; + fs210x->scene_id = -1; + + /* Init registers/RAM by init scene */ + ret = fs210x_set_scene(fs210x, FS210X_INIT_SCENE); + if (ret) + goto tag_power_down; + + /* + * If the firmware has effect scene(s), + * we load effect scene by default scene or scene_id + */ + if (fs210x->amp_lib.scene_count > 1) { + if (scene_id < FS210X_DEFAULT_SCENE) + scene_id = FS210X_DEFAULT_SCENE; + ret = fs210x_set_scene(fs210x, scene_id); + if (ret) + goto tag_power_down; + } + +tag_power_down: + /* Power down the device */ + ret |= fs210x_reg_write(fs210x, FS210X_11H_SYSCTRL, + FS210X_11H_DPS_PWDN); + fsleep(10000); /* >= 10ms */ + + regcache_cache_bypass(fs210x->regmap, false); + if (!ret) { + regcache_mark_dirty(fs210x->regmap); + regcache_sync(fs210x->regmap); + fs210x->is_inited = true; + } + + return ret; +} + +static int fs210x_set_i2s_params(struct fs210x_priv *fs210x) +{ + const struct fs_i2s_srate params[] = { + { 16000, 0x3 }, + { 32000, 0x7 }, + { 44100, 0x8 }, + { 48000, 0x9 }, + { 88200, 0xA }, + { 96000, 0xB }, + }; + u16 val; + int i, ret; + + for (i = 0; i < ARRAY_SIZE(params); i++) { + if (params[i].srate != fs210x->srate) + continue; + val = params[i].i2ssr << FS210X_17H_I2SSR_SHIFT; + ret = fs210x_reg_update_bits(fs210x, + FS210X_17H_I2SCTRL, + FS210X_17H_I2SSR_MASK, + val); + return ret; + } + + dev_err(fs210x->dev, "Invalid sample rate: %d\n", fs210x->srate); + + return -EINVAL; +} + +static int fs210x_get_pll_div(struct fs210x_priv *fs210x, + const struct fs_pll_div **pll_div) +{ + int i; + + if (!fs210x || !pll_div) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(fs210x_pll_div); i++) { + if (fs210x_pll_div[i].bclk != fs210x->bclk) + continue; + *pll_div = fs210x_pll_div + i; + return 0; + } + + dev_err(fs210x->dev, "No PLL table for bclk: %d\n", fs210x->bclk); + + return -EFAULT; +} + +static int fs210x_set_hw_params(struct fs210x_priv *fs210x) +{ + const struct fs_pll_div *pll_div; + int ret; + + ret = fs210x_set_i2s_params(fs210x); + if (ret) { + dev_err(fs210x->dev, "Failed to set i2s params: %d\n", ret); + return ret; + } + + /* Set pll params */ + ret = fs210x_get_pll_div(fs210x, &pll_div); + if (ret) + return ret; + + ret = fs210x_reg_write(fs210x, FS210X_A1H_PLLCTRL1, pll_div->pll1); + ret |= fs210x_reg_write(fs210x, FS210X_A2H_PLLCTRL2, pll_div->pll2); + ret |= fs210x_reg_write(fs210x, FS210X_A3H_PLLCTRL3, pll_div->pll3); + + return ret; +} + +static int fs210x_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + const struct snd_pcm_hw_constraint_list *list; + struct fs210x_priv *fs210x; + int ret; + + fs210x = snd_soc_component_get_drvdata(dai->component); + if (!fs210x) { + pr_err("dai_startup: fs210x is null\n"); + return -EINVAL; + } + + if (!substream->runtime) + return 0; + + ret = snd_pcm_hw_constraint_mask64(substream->runtime, + SNDRV_PCM_HW_PARAM_FORMAT, + FS210X_FORMATS); + if (ret < 0) { + dev_err(fs210x->dev, + "Failed to set hw param format: %d\n", ret); + return ret; + } + + if (fs210x->devid == FS2105S_DEVICE_ID) + list = &fs2105s_constraints; + else + list = &fs210x_constraints; + + ret = snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + list); + if (ret < 0) { + dev_err(fs210x->dev, + "Failed to set hw param rate: %d\n", ret); + return ret; + } + + return 0; +} + +static int fs210x_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct fs210x_priv *fs210x; + + fs210x = snd_soc_component_get_drvdata(dai->component); + + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBC_CFC: + /* Only supports consumer mode */ + break; + default: + dev_err(fs210x->dev, "Only supports consumer mode\n"); + return -EINVAL; + } + + return 0; +} + +static int fs210x_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct fs210x_priv *fs210x; + int chn_num; + int ret; + + if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) + return 0; + + fs210x = snd_soc_component_get_drvdata(dai->component); + + fs210x->srate = params_rate(params); + fs210x->bclk = snd_soc_params_to_bclk(params); + chn_num = params_channels(params); + if (chn_num == 1) /* mono */ + fs210x->bclk *= 2; /* I2S bus has 2 channels */ + + /* The FS2105S can't support 16kHz sample rate. */ + if (fs210x->devid == FS2105S_DEVICE_ID && fs210x->srate == 16000) + return -EOPNOTSUPP; + + mutex_lock(&fs210x->lock); + ret = fs210x_set_hw_params(fs210x); + mutex_unlock(&fs210x->lock); + if (ret) + dev_err(fs210x->dev, "Failed to set hw params: %d\n", ret); + + return ret; +} + +static int fs210x_dai_mute(struct snd_soc_dai *dai, int mute, int stream) +{ + struct fs210x_priv *fs210x; + unsigned long delay; + + if (stream != SNDRV_PCM_STREAM_PLAYBACK) + return 0; + + fs210x = snd_soc_component_get_drvdata(dai->component); + + mutex_lock(&fs210x->lock); + + if (!fs210x->is_inited || fs210x->is_suspended) { + mutex_unlock(&fs210x->lock); + return 0; + } + + mutex_unlock(&fs210x->lock); + + if (mute) { + cancel_delayed_work_sync(&fs210x->fault_check_work); + cancel_delayed_work_sync(&fs210x->start_work); + } else { + delay = msecs_to_jiffies(fs210x->check_interval_ms); + schedule_delayed_work(&fs210x->fault_check_work, delay); + } + + return 0; +} + +static int fs210x_dai_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct fs210x_priv *fs210x; + + fs210x = snd_soc_component_get_drvdata(dai->component); + + mutex_lock(&fs210x->lock); + + if (!fs210x->is_inited || fs210x->is_suspended || fs210x->is_playing) { + mutex_unlock(&fs210x->lock); + return 0; + } + + mutex_unlock(&fs210x->lock); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + /* + * According to the power up/down sequence of FS210x, + * it requests the I2S clock has been present + * and stable(>= 2ms) before playing. + */ + schedule_delayed_work(&fs210x->start_work, + msecs_to_jiffies(FS210X_START_DELAY_MS)); + break; + + default: + break; + } + + return 0; +} + +static void fs210x_start_work(struct work_struct *work) +{ + struct fs210x_priv *fs210x; + int ret; + + fs210x = container_of(work, struct fs210x_priv, start_work.work); + + mutex_lock(&fs210x->lock); + + ret = fs210x_dev_play(fs210x); + if (ret) + dev_err(fs210x->dev, "Failed to start playing: %d\n", ret); + + mutex_unlock(&fs210x->lock); +} + +static void fs210x_fault_check_work(struct work_struct *work) +{ + struct fs210x_priv *fs210x; + u16 status; + int ret; + + fs210x = container_of(work, struct fs210x_priv, fault_check_work.work); + + mutex_lock(&fs210x->lock); + + if (!fs210x->is_inited || fs210x->is_suspended || !fs210x->is_playing) { + mutex_unlock(&fs210x->lock); + return; + } + + ret = fs210x_reg_read(fs210x, FS210X_05H_ANASTAT, &status); + mutex_unlock(&fs210x->lock); + if (ret) + return; + + if (!(status & FS210X_05H_PVDD_MASK)) + dev_err(fs210x->dev, "PVDD fault\n"); + if (status & FS210X_05H_OCDL_MASK) + dev_err(fs210x->dev, "OC detected\n"); + if (status & FS210X_05H_UVDL_MASK) + dev_err(fs210x->dev, "UV detected\n"); + if (status & FS210X_05H_OVDL_MASK) + dev_err(fs210x->dev, "OV detected\n"); + if (status & FS210X_05H_OTPDL_MASK) + dev_err(fs210x->dev, "OT detected\n"); + if (status & FS210X_05H_OCRDL_MASK) + dev_err(fs210x->dev, "OCR detected\n"); + if (status & FS210X_05H_OCLDL_MASK) + dev_err(fs210x->dev, "OCL detected\n"); + if (status & FS210X_05H_DCRDL_MASK) + dev_err(fs210x->dev, "DCR detected\n"); + if (status & FS210X_05H_DCLDL_MASK) + dev_err(fs210x->dev, "DCL detected\n"); + if (status & FS210X_05H_SRDL_MASK) + dev_err(fs210x->dev, "SR detected\n"); + if (status & FS210X_05H_OTWDL_MASK) + dev_err(fs210x->dev, "OTW detected\n"); + if (!(status & FS210X_05H_AMPS_MASK)) + dev_dbg(fs210x->dev, "Amplifier unready\n"); + if (!(status & FS210X_05H_PLLS_MASK)) + dev_err(fs210x->dev, "PLL unlock\n"); + if (!(status & FS210X_05H_ANAS_MASK)) + dev_err(fs210x->dev, "Analog power fault\n"); + + schedule_delayed_work(&fs210x->fault_check_work, + msecs_to_jiffies(fs210x->check_interval_ms)); +} + +static int fs210x_get_drvdata_from_kctrl(struct snd_kcontrol *kctrl, + struct fs210x_priv **fs210x) +{ + struct snd_soc_component *cmpnt; + + if (!kctrl) { + pr_err("fs210x: kcontrol is null\n"); + return -EINVAL; + } + + cmpnt = snd_soc_kcontrol_component(kctrl); + if (!cmpnt) { + pr_err("fs210x: component is null\n"); + return -EINVAL; + } + + *fs210x = snd_soc_component_get_drvdata(cmpnt); + + return 0; +} + +static int fs210x_effect_scene_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + const struct fs_amp_scene *scene; + struct fs210x_priv *fs210x; + const char *name = "N/A"; + int idx, count; + int ret; + + ret = fs210x_get_drvdata_from_kctrl(kcontrol, &fs210x); + if (ret || !fs210x->dev) { + pr_err("scene_effect_info: fs210x is null\n"); + return -EINVAL; + } + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + + count = fs210x->amp_lib.scene_count - 1; /* Skip init scene */ + if (count < 1) { + uinfo->value.enumerated.items = 0; + return 0; + } + + uinfo->value.enumerated.items = count; + if (uinfo->value.enumerated.item >= count) + uinfo->value.enumerated.item = count - 1; + + idx = uinfo->value.enumerated.item; + scene = fs210x->amp_lib.scene + idx + 1; + if (scene->name) + name = scene->name; + + strscpy(uinfo->value.enumerated.name, name, strlen(name) + 1); + + return 0; +} + +static int fs210x_effect_scene_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct fs210x_priv *fs210x; + int index; + int ret; + + ret = fs210x_get_drvdata_from_kctrl(kcontrol, &fs210x); + if (ret || !fs210x->dev) { + pr_err("scene_effect_get: fs210x is null\n"); + return -EINVAL; + } + + /* The id of effect scene is from 1 to N. */ + if (fs210x->scene_id < 1) + return -EINVAL; + + mutex_lock(&fs210x->lock); + /* + * FS210x has scene(s) as below: + * init scene: id = 0 + * effect scene(s): id = 1~N (optional) + * effect_index = scene_id - 1 + */ + index = fs210x->scene_id - 1; + ucontrol->value.integer.value[0] = index; + mutex_unlock(&fs210x->lock); + + return 0; +} + +static int fs210x_effect_scene_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct fs210x_priv *fs210x; + int scene_id, scene_count; + bool is_changed = false; + int ret; + + ret = fs210x_get_drvdata_from_kctrl(kcontrol, &fs210x); + if (ret || !fs210x->dev) { + pr_err("scene_effect_put: fs210x is null\n"); + return -EINVAL; + } + + mutex_lock(&fs210x->lock); + + /* + * FS210x has scene(s) as below: + * init scene: id = 0 (It's set in fs210x_init_chip() only) + * effect scene(s): id = 1~N (optional) + * scene_id = effect_index + 1. + */ + scene_id = ucontrol->value.integer.value[0] + 1; + scene_count = fs210x->amp_lib.scene_count - 1; /* Skip init scene */ + if (scene_id < 1 || scene_id > scene_count) { + mutex_unlock(&fs210x->lock); + return -ERANGE; + } + + if (scene_id != fs210x->scene_id) + is_changed = true; + + if (fs210x->is_suspended) { + fs210x->scene_id = scene_id; + mutex_unlock(&fs210x->lock); + return is_changed; + } + + ret = fs210x_set_scene(fs210x, scene_id); + if (ret) + dev_err(fs210x->dev, "Failed to set scene: %d\n", ret); + + mutex_unlock(&fs210x->lock); + + if (!ret && is_changed) + return 1; + + return ret; +} + +static int fs210x_playback_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kc, int event) +{ + struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); + struct fs210x_priv *fs210x = snd_soc_component_get_drvdata(cmpnt); + int ret = 0; + + mutex_lock(&fs210x->lock); + + if (fs210x->is_suspended) { + mutex_unlock(&fs210x->lock); + return 0; + } + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* + * If there is no bclk for us to set the clock output, + * we will enable the device(start_work) in dai trigger. + */ + if (!fs210x->clk_bclk) + break; + fs210x_bclk_set(fs210x, true); + ret = fs210x_dev_play(fs210x); + break; + case SND_SOC_DAPM_POST_PMD: + ret = fs210x_dev_stop(fs210x); + fs210x_bclk_set(fs210x, false); + break; + default: + break; + } + + mutex_unlock(&fs210x->lock); + + return ret; +} + +static const struct snd_soc_dai_ops fs210x_dai_ops = { + .startup = fs210x_dai_startup, + .set_fmt = fs210x_dai_set_fmt, + .hw_params = fs210x_dai_hw_params, + .mute_stream = fs210x_dai_mute, + .trigger = fs210x_dai_trigger, +}; + +static const struct snd_soc_dai_driver fs210x_dai = { + .name = FS210X_DEFAULT_DAI_NAME, + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = FS210X_RATES, + .formats = FS210X_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = FS210X_RATES, + .formats = FS210X_FORMATS, + }, + .ops = &fs210x_dai_ops, + .symmetric_rate = 1, + .symmetric_sample_bits = 1, +}; + +static const DECLARE_TLV_DB_SCALE(fs2105s_vol_tlv, -9709, 19, 1); +static const DECLARE_TLV_DB_SCALE(fs210x_vol_tlv, -13357, 19, 1); + +static const struct snd_kcontrol_new fs2105s_vol_control[] = { + SOC_DOUBLE_R_TLV("PCM Playback Volume", + FS210X_39H_LVOLCTRL, FS210X_3AH_RVOLCTRL, + 7, 0x1FF, 0, fs2105s_vol_tlv), +}; + +static const struct snd_kcontrol_new fs210x_vol_control[] = { + SOC_DOUBLE_R_TLV("PCM Playback Volume", + FS210X_39H_LVOLCTRL, FS210X_3AH_RVOLCTRL, + 6, 0x2BF, 0, fs210x_vol_tlv), +}; + +static const struct snd_kcontrol_new fs210x_controls[] = { + SOC_DOUBLE("DAC Mute Switch", FS210X_30H_DACCTRL, 4, 8, 1, 0), + SOC_DOUBLE("DAC Fade Switch", FS210X_30H_DACCTRL, 5, 9, 1, 0), +}; + +static const struct snd_kcontrol_new fs210x_scene_control[] = { + FS_SOC_ENUM_EXT("Effect Scene", + fs210x_effect_scene_info, + fs210x_effect_scene_get, + fs210x_effect_scene_put), +}; + +static const struct snd_soc_dapm_widget fs210x_dapm_widgets[] = { + SND_SOC_DAPM_AIF_IN_E("AIF IN", "Playback", 0, SND_SOC_NOPM, 0, 0, + fs210x_playback_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_AIF_OUT("AIF OUT", "Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_OUTPUT("OUTL"), + SND_SOC_DAPM_OUTPUT("OUTR"), + SND_SOC_DAPM_INPUT("SDO"), +}; + +static const struct snd_soc_dapm_route fs210x_dapm_routes[] = { + { "OUTL", NULL, "AIF IN" }, + { "OUTR", NULL, "AIF IN" }, + { "AIF OUT", NULL, "SDO" }, +}; + +static int fs210x_add_mixer_controls(struct fs210x_priv *fs210x, + struct snd_soc_component *cmpnt) +{ + const struct snd_kcontrol_new *kctrl; + int count; + int ret; + + if (!fs210x || !cmpnt) + return -EINVAL; + + if (fs210x->devid == FS2105S_DEVICE_ID) { + kctrl = fs2105s_vol_control; + count = ARRAY_SIZE(fs2105s_vol_control); + } else { + kctrl = fs210x_vol_control; + count = ARRAY_SIZE(fs210x_vol_control); + } + + ret = snd_soc_add_component_controls(cmpnt, kctrl, count); + if (ret) + return ret; + + /* + * If the firmware has no scene or only init scene, + * we skip adding this mixer control. + */ + if (fs210x->amp_lib.scene_count < 2) + return 0; + + kctrl = fs210x_scene_control; + count = ARRAY_SIZE(fs210x_scene_control); + + return snd_soc_add_component_controls(cmpnt, kctrl, count); +} + +static int fs210x_probe(struct snd_soc_component *cmpnt) +{ + struct fs210x_priv *fs210x; + int ret; + + fs210x = snd_soc_component_get_drvdata(cmpnt); + if (!fs210x || !fs210x->dev) + return -EINVAL; + + fs210x->amp_lib.dev = fs210x->dev; + fs210x->amp_lib.devid = fs210x->devid; + + ret = fs_amp_load_firmware(&fs210x->amp_lib, fs210x->pdata.fwm_name); + if (ret) + return ret; + + ret = fs210x_add_mixer_controls(fs210x, cmpnt); + if (ret) + return ret; + + mutex_lock(&fs210x->lock); + ret = fs210x_init_chip(fs210x); + mutex_unlock(&fs210x->lock); + + return ret; +} + +static void fs210x_remove(struct snd_soc_component *cmpnt) +{ + struct fs210x_priv *fs210x; + + fs210x = snd_soc_component_get_drvdata(cmpnt); + if (!fs210x || !fs210x->dev) + return; + + cancel_delayed_work_sync(&fs210x->start_work); + cancel_delayed_work_sync(&fs210x->fault_check_work); +} + +#ifdef CONFIG_PM +static int fs210x_suspend(struct snd_soc_component *cmpnt) +{ + struct fs210x_priv *fs210x; + int ret; + + fs210x = snd_soc_component_get_drvdata(cmpnt); + if (!fs210x || !fs210x->dev) + return -EINVAL; + + regcache_cache_only(fs210x->regmap, true); + + mutex_lock(&fs210x->lock); + fs210x->cur_scene = NULL; + fs210x->is_inited = false; + fs210x->is_playing = false; + fs210x->is_suspended = true; + + gpiod_set_value_cansleep(fs210x->gpio_sdz, 1); /* Active */ + fsleep(30000); /* >= 30ms */ + mutex_unlock(&fs210x->lock); + + cancel_delayed_work_sync(&fs210x->start_work); + cancel_delayed_work_sync(&fs210x->fault_check_work); + + ret = regulator_bulk_disable(FS210X_NUM_SUPPLIES, fs210x->supplies); + if (ret) { + dev_err(fs210x->dev, "Failed to suspend: %d\n", ret); + return ret; + } + + return 0; +} + +static int fs210x_resume(struct snd_soc_component *cmpnt) +{ + struct fs210x_priv *fs210x; + int ret; + + fs210x = snd_soc_component_get_drvdata(cmpnt); + if (!fs210x || !fs210x->dev) + return -EINVAL; + + ret = regulator_bulk_enable(FS210X_NUM_SUPPLIES, fs210x->supplies); + if (ret) { + dev_err(fs210x->dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + + mutex_lock(&fs210x->lock); + + fs210x->is_suspended = false; + ret = fs210x_init_chip(fs210x); + + mutex_unlock(&fs210x->lock); + + return ret; +} +#else +#define fs210x_suspend NULL +#define fs210x_resume NULL +#endif // CONFIG_PM + +static bool fs210x_volatile_registers(struct device *dev, unsigned int reg) +{ + switch (reg) { + case FS210X_00H_STATUS ... FS210X_0FH_I2CADDR: + case FS210X_ABH_INTSTAT: + case FS210X_ACH_INTSTATR: + return true; + default: + return false; + } +} + +static const struct snd_soc_component_driver fs210x_soc_component_dev = { + .probe = fs210x_probe, + .remove = fs210x_remove, + .suspend = fs210x_suspend, + .resume = fs210x_resume, + .controls = fs210x_controls, + .num_controls = ARRAY_SIZE(fs210x_controls), + .dapm_widgets = fs210x_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(fs210x_dapm_widgets), + .dapm_routes = fs210x_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(fs210x_dapm_routes), +}; + +static const struct regmap_config fs210x_regmap = { + .reg_bits = 8, + .val_bits = 16, + .max_register = FS210X_REG_MAX, + .val_format_endian = REGMAP_ENDIAN_BIG, + .cache_type = REGCACHE_MAPLE, + .volatile_reg = fs210x_volatile_registers, +}; + +static int fs210x_detect_device(struct fs210x_priv *fs210x) +{ + u16 devid; + int ret; + + ret = fs210x_reg_read(fs210x, FS210X_03H_DEVID, &devid); + if (ret) + return ret; + + fs210x->devid = HI_U16(devid); + + switch (fs210x->devid) { + case FS210X_DEVICE_ID: + dev_info(fs210x->dev, "FS2104 detected\n"); + break; + case FS2105S_DEVICE_ID: + dev_info(fs210x->dev, "FS2105S detected\n"); + break; + default: + dev_err(fs210x->dev, "DEVID: 0x%04X dismatch\n", devid); + return -ENODEV; + } + + return 0; +} + +static int fs210x_parse_dts(struct fs210x_priv *fs210x, + struct fs210x_platform_data *pdata) +{ + struct device_node *node = fs210x->dev->of_node; + int i, ret; + + if (!node) + return 0; + + ret = of_property_read_string(node, "firmware-name", &pdata->fwm_name); + if (ret) + pdata->fwm_name = FS210X_DEFAULT_FWM_NAME; + + fs210x->gpio_sdz = devm_gpiod_get_optional(fs210x->dev, + "reset", GPIOD_OUT_HIGH); + if (IS_ERR(fs210x->gpio_sdz)) + return dev_err_probe(fs210x->dev, PTR_ERR(fs210x->gpio_sdz), + "Failed to get reset-gpios\n"); + + for (i = 0; i < FS210X_NUM_SUPPLIES; i++) + fs210x->supplies[i].supply = fs210x_supply_names[i]; + + ret = devm_regulator_bulk_get(fs210x->dev, + ARRAY_SIZE(fs210x->supplies), + fs210x->supplies); + if (ret) + return dev_err_probe(fs210x->dev, ret, + "Failed to get supplies\n"); + + return 0; +} + +static void fs210x_deinit(struct fs210x_priv *fs210x) +{ + gpiod_set_value_cansleep(fs210x->gpio_sdz, 1); /* Active */ + fsleep(10000); /* >= 10ms */ + + regulator_bulk_disable(FS210X_NUM_SUPPLIES, fs210x->supplies); +} + +static int fs210x_init(struct fs210x_priv *fs210x) +{ + int ret; + + ret = fs210x_parse_dts(fs210x, &fs210x->pdata); + if (ret) + return ret; + + fs210x->clk_bclk = devm_clk_get_optional(fs210x->dev, "bclk"); + if (IS_ERR(fs210x->clk_bclk)) + return dev_err_probe(fs210x->dev, PTR_ERR(fs210x->clk_bclk), + "Failed to get bclk\n"); + + ret = regulator_bulk_enable(FS210X_NUM_SUPPLIES, fs210x->supplies); + if (ret) + return dev_err_probe(fs210x->dev, ret, + "Failed to enable supplies\n"); + + /* Make sure the SDZ pin is pulled down enough time. */ + fsleep(10000); /* >= 10ms */ + gpiod_set_value_cansleep(fs210x->gpio_sdz, 0); /* Deactivate */ + fsleep(10000); /* >= 10ms */ + + ret = fs210x_detect_device(fs210x); + if (ret) { + fs210x_deinit(fs210x); + return ret; + } + + fs210x->scene_id = -1; /* Invalid scene */ + fs210x->cur_scene = NULL; + fs210x->is_playing = false; + fs210x->is_inited = false; + fs210x->is_suspended = false; + fs210x->check_interval_ms = FS210X_FAULT_CHECK_INTERVAL_MS; + + INIT_DELAYED_WORK(&fs210x->fault_check_work, fs210x_fault_check_work); + INIT_DELAYED_WORK(&fs210x->start_work, fs210x_start_work); + mutex_init(&fs210x->lock); + + return 0; +} + +static int fs210x_register_snd_component(struct fs210x_priv *fs210x) +{ + struct snd_soc_dai_driver *dai_drv; + static int instance_id; + int ret; + + dai_drv = devm_kmemdup(fs210x->dev, &fs210x_dai, + sizeof(fs210x_dai), GFP_KERNEL); + if (!dai_drv) + return -ENOMEM; + + dai_drv->name = devm_kasprintf(fs210x->dev, + GFP_KERNEL, "%s-%d", + dai_drv->name, instance_id); + if (!dai_drv->name) + return -ENOMEM; + + instance_id++; + + if (fs210x->devid == FS2105S_DEVICE_ID) { + dai_drv->playback.rates = FS2105S_RATES; + dai_drv->capture.rates = FS2105S_RATES; + } + + ret = snd_soc_register_component(fs210x->dev, + &fs210x_soc_component_dev, + dai_drv, 1); + return ret; +} + +static ssize_t check_interval_ms_show(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct fs210x_priv *fs210x = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%d\n", fs210x->check_interval_ms); +} + +static ssize_t check_interval_ms_store(struct device *dev, + struct device_attribute *devattr, + const char *buf, + size_t count) +{ + struct fs210x_priv *fs210x = dev_get_drvdata(dev); + int ret; + + ret = kstrtouint(buf, 10, &fs210x->check_interval_ms); + if (ret) + return -EINVAL; + + return (ssize_t)count; +} + +static DEVICE_ATTR_RW(check_interval_ms); + +static struct attribute *fs210x_attrs[] = { + &dev_attr_check_interval_ms.attr, + NULL, +}; + +static struct attribute_group fs210x_attr_group = { + .attrs = fs210x_attrs, +}; + +static int fs210x_i2c_probe(struct i2c_client *client) +{ + struct fs210x_priv *fs210x; + int ret; + + fs210x = devm_kzalloc(&client->dev, sizeof(*fs210x), GFP_KERNEL); + if (!fs210x) + return -ENOMEM; + + fs210x->i2c = client; + fs210x->dev = &client->dev; + i2c_set_clientdata(client, fs210x); + + fs210x->regmap = devm_regmap_init_i2c(client, &fs210x_regmap); + if (IS_ERR(fs210x->regmap)) + return dev_err_probe(fs210x->dev, PTR_ERR(fs210x->regmap), + "Failed to get regmap\n"); + + ret = fs210x_init(fs210x); + if (ret) + return ret; + + ret = devm_device_add_group(fs210x->dev, &fs210x_attr_group); + if (ret) { + fs210x_deinit(fs210x); + return dev_err_probe(fs210x->dev, ret, + "Failed to create sysfs group\n"); + } + + ret = fs210x_register_snd_component(fs210x); + if (ret) { + fs210x_deinit(fs210x); + return dev_err_probe(fs210x->dev, ret, + "Failed to register component\n"); + } + + return 0; +} + +static void fs210x_i2c_remove(struct i2c_client *client) +{ + struct fs210x_priv *fs210x = i2c_get_clientdata(client); + + snd_soc_unregister_component(fs210x->dev); + fs210x_deinit(fs210x); +} + +static const struct i2c_device_id fs210x_i2c_id[] = { + { "fs2104" }, + { "fs2105s" }, + {} +}; +MODULE_DEVICE_TABLE(i2c, fs210x_i2c_id); + +static const struct of_device_id fs210x_of_match[] = { + { .compatible = "foursemi,fs2105s", }, + {}, +}; +MODULE_DEVICE_TABLE(of, fs210x_of_match); + +static struct i2c_driver fs210x_i2c_driver = { + .driver = { + .name = "fs210x", + .of_match_table = fs210x_of_match, + }, + .id_table = fs210x_i2c_id, + .probe = fs210x_i2c_probe, + .remove = fs210x_i2c_remove, +}; + +module_i2c_driver(fs210x_i2c_driver); + +MODULE_AUTHOR("Nick Li <nick.li@foursemi.com>"); +MODULE_DESCRIPTION("FS2104/5S Audio Amplifier Driver"); +MODULE_LICENSE("GPL"); |