// 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 #include #include #include #include #include #include #include #include #include #include #include #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 "); MODULE_DESCRIPTION("FS2104/5S Audio Amplifier Driver"); MODULE_LICENSE("GPL");