* [PATCH v1 0/4] ASoC: codecs: Add support for FourSemi FS2104/5S @ 2025-07-03 3:56 Nick 2025-07-03 3:56 ` [PATCH v1 1/4] ASoC: codecs: Add library for FourSemi audio amplifiers Nick ` (4 more replies) 0 siblings, 5 replies; 37+ messages in thread From: Nick @ 2025-07-03 3:56 UTC (permalink / raw) To: lgirdwood, broonie, robh, krzk+dt, conor+dt, perex, tiwai, nick.li Cc: like.sin, xiaoming.yang, danyang.zheng, linux-sound, devicetree, linux-kernel The FS2104/5S are Inductor-Less, Stereo, Closed-Loop, Digital Input Class-D Power Amplifiers with Enhanced Signal Processing FS2104 can deliver 2x15W into 4ohm BTL speaker loads, FS2105S can deliver 2x30W into 8ohm BTL speaker loads. The driver has been checked with kernel scrpits and cppcheck. Most functions have been built and tested on EVB boards: ARMv8-A, Linux version 6.16.0-rc4-v8 Part of test logs: FS2105S without effect scene: [ 2.941289] fs210x 1-0068: version: v1.0.6 [ 2.941382] fs210x 1-0068: supply pvdd not found, using dummy regulator [ 2.941441] fs210x 1-0068: supply dvdd not found, using dummy regulator [ 2.965632] fs210x 1-0068: FS2105S detected [ 2.969875] fs210x 1-0068: Loading foursemi/fs2105s-btl-0s.bin - size: 2854 [ 2.969907] fs210x 1-0068: Project: NONAME Device: DEV1 [ 2.970319] fs210x 1-0068: Date: 20250624-1918 [ 2.981171] fs210x 1-0068: Switch scene.0: S0 [ 6.142932] fs210x 1-0068: hw params: 48000-2-1536000 [ 6.157219] fs210x 1-0068: playback unmute [ 6.201567] fs210x 1-0068: playback mute [ 6.202801] fs210x 1-0068: hw params: 48000-2-3072000 [ 6.217364] fs210x 1-0068: playback unmute [ 6.247900] fs210x 1-0068: playback mute FS2104 with 1 effect scene: [ 18.067507] fs210x 0-0068: version: v1.0.6 [ 18.072095] fs210x 0-0068: 0-0068 supply pvdd not found, using dummy regulator [ 18.085700] fs210x 0-0068: 0-0068 supply dvdd not found, using dummy regulator [ 18.113663] fs210x 0-0068: FS2104 detected [ 18.119216] fs210x 0-0069: version: v1.0.6 [ 18.123675] fs210x 0-0069: 0-0069 supply pvdd not found, using dummy regulator [ 18.137239] fs210x 0-0069: 0-0069 supply dvdd not found, using dummy regulator [ 18.159819] fs210x 0-0069: Failed to read 03h: -5 [ 18.164969] fs210x 0-0069: probe with driver fs210x failed with error -5 [ 28.422623] fs210x 0-0068: Loading fs2104-btl-1s.bin - size: 6696 [ 28.422741] fs210x 0-0068: Project: NONAME Device: DEV1 [ 28.422751] fs210x 0-0068: Date: 20250523-1538 [ 28.433739] fs210x 0-0068: Switch scene.0: S0 [ 28.513483] fs210x 0-0068: Switch scene.1: MUSIC [ 61.912099] fs210x 0-0068: hw params: 48000-2-3072000 [ 61.928384] fs210x 0-0068: playback unmute [ 73.962865] fs210x 0-0068: playback mute [ 92.833623] fs210x 0-0068: pm suspended [ 92.893641] fs210x 0-0068: Switch scene.0: S0 [ 92.990521] fs210x 0-0068: Switch scene.1: MUSIC [ 93.077759] fs210x 0-0068: pm resumed [ 103.315898] fs210x 0-0068: hw params: 48000-2-3072000 [ 103.331770] fs210x 0-0068: playback unmute [ 114.316948] fs210x 0-0068: playback mute Nick Li (4): ASoC: codecs: Add library for FourSemi audio amplifiers ASoC: codecs: Add FourSemi FS2104/5S audio amplifier driver ASoC: dt-bindings: Add dt bindings for FS2104/5S audio amplifiers dt-bindings: vendor-prefixes: Add Shanghai FourSemi Semiconductor Co.,Ltd .../bindings/sound/foursemi,fs210x.yaml | 95 + .../devicetree/bindings/vendor-prefixes.yaml | 2 + sound/soc/codecs/Kconfig | 14 + sound/soc/codecs/Makefile | 4 + sound/soc/codecs/fs-amp-lib.c | 265 +++ sound/soc/codecs/fs-amp-lib.h | 150 ++ sound/soc/codecs/fs210x.c | 1616 +++++++++++++++++ sound/soc/codecs/fs210x.h | 79 + 8 files changed, 2225 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/foursemi,fs210x.yaml create mode 100644 sound/soc/codecs/fs-amp-lib.c create mode 100644 sound/soc/codecs/fs-amp-lib.h create mode 100644 sound/soc/codecs/fs210x.c create mode 100644 sound/soc/codecs/fs210x.h -- 2.39.5 ^ permalink raw reply [flat|nested] 37+ messages in thread
* [PATCH v1 1/4] ASoC: codecs: Add library for FourSemi audio amplifiers 2025-07-03 3:56 [PATCH v1 0/4] ASoC: codecs: Add support for FourSemi FS2104/5S Nick @ 2025-07-03 3:56 ` Nick 2025-07-03 3:56 ` [PATCH v1 2/4] ASoC: codecs: Add FourSemi FS2104/5S audio amplifier driver Nick ` (3 subsequent siblings) 4 siblings, 0 replies; 37+ messages in thread From: Nick @ 2025-07-03 3:56 UTC (permalink / raw) To: lgirdwood, broonie, robh, krzk+dt, conor+dt, perex, tiwai, nick.li Cc: like.sin, xiaoming.yang, danyang.zheng, linux-sound, devicetree, linux-kernel From: Nick Li <nick.li@foursemi.com> This patch adds firmware loading and parsing support for FourSemi audio amplifiers. The library handles firmware file (*.bin) generated by the FourSemi tuning tool, which contains: - Register initialization settings - DSP effect parameters - Multi-scene sound effect switching configurations(optional) The firmware is required for proper initialization and configuration of FourSemi amplifier devices. Signed-off-by: Nick Li <nick.li@foursemi.com> --- sound/soc/codecs/Kconfig | 3 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/fs-amp-lib.c | 265 ++++++++++++++++++++++++++++++++++ sound/soc/codecs/fs-amp-lib.h | 150 +++++++++++++++++++ 4 files changed, 420 insertions(+) create mode 100644 sound/soc/codecs/fs-amp-lib.c create mode 100644 sound/soc/codecs/fs-amp-lib.h diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 4494e7cd6..6265cc897 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -1238,6 +1238,9 @@ config SND_SOC_FRAMER To compile this driver as a module, choose M here: the module will be called snd-soc-framer. +config SND_SOC_FS_AMP_LIB + select CRC16 + tristate config SND_SOC_GTM601 tristate 'GTM601 UMTS modem audio codec' diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index c497d343d..9afce9aa6 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -137,6 +137,7 @@ snd-soc-es8328-spi-y := es8328-spi.o snd-soc-es8375-y := es8375.o snd-soc-es8389-y := es8389.o snd-soc-framer-y := framer-codec.o +snd-soc-fs-amp-lib-y := fs-amp-lib.o snd-soc-gtm601-y := gtm601.o snd-soc-hdac-hdmi-y := hdac_hdmi.o snd-soc-hdac-hda-y := hdac_hda.o @@ -561,6 +562,7 @@ obj-$(CONFIG_SND_SOC_ES8328_SPI)+= snd-soc-es8328-spi.o obj-$(CONFIG_SND_SOC_ES8375) += snd-soc-es8375.o obj-$(CONFIG_SND_SOC_ES8389) += snd-soc-es8389.o obj-$(CONFIG_SND_SOC_FRAMER) += snd-soc-framer.o +obj-$(CONFIG_SND_SOC_FS_AMP_LIB)+= snd-soc-fs-amp-lib.o obj-$(CONFIG_SND_SOC_GTM601) += snd-soc-gtm601.o obj-$(CONFIG_SND_SOC_HDAC_HDMI) += snd-soc-hdac-hdmi.o obj-$(CONFIG_SND_SOC_HDAC_HDA) += snd-soc-hdac-hda.o diff --git a/sound/soc/codecs/fs-amp-lib.c b/sound/soc/codecs/fs-amp-lib.c new file mode 100644 index 000000000..e1b42c51a --- /dev/null +++ b/sound/soc/codecs/fs-amp-lib.c @@ -0,0 +1,265 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// fs-amp-lib.c --- Common library for FourSemi Audio Amplifiers +// +// Copyright (C) 2016-2025 Shanghai FourSemi Semiconductor Co.,Ltd. + +#include <linux/crc16.h> +#include <linux/device.h> +#include <linux/firmware.h> +#include <linux/module.h> +#include <linux/slab.h> + +#include "fs-amp-lib.h" + +static int fs_get_scene_count(struct fs_amp_lib *amp_lib) +{ + const struct fs_fwm_table *table; + int count; + + if (!amp_lib || !amp_lib->dev) + return -EINVAL; + + table = amp_lib->table[FS_INDEX_SCENE]; + if (!table) + return -EFAULT; + + count = table->size / sizeof(struct fs_scene_index); + if (count < 1 || count > FS_SCENE_COUNT_MAX) { + dev_err(amp_lib->dev, "Invalid scene count: %d\n", count); + return -ERANGE; + } + + return count; +} + +static void fs_get_fwm_string(struct fs_amp_lib *amp_lib, + int offset, const char **pstr) +{ + const struct fs_fwm_table *table; + + if (!amp_lib || !amp_lib->dev || !pstr) + return; + + table = amp_lib->table[FS_INDEX_STRING]; + if (table && offset > 0 && offset < table->size + sizeof(*table)) + *pstr = (char *)table + offset; + else + *pstr = NULL; +} + +static void fs_get_scene_reg(struct fs_amp_lib *amp_lib, + int offset, struct fs_amp_scene *scene) +{ + const struct fs_fwm_table *table; + + if (!amp_lib || !amp_lib->dev || !scene) + return; + + table = amp_lib->table[FS_INDEX_REG]; + if (table && offset > 0 && offset < table->size + sizeof(*table)) + scene->reg = (struct fs_reg_table *)((char *)table + offset); + else + scene->reg = NULL; +} + +static void fs_get_scene_model(struct fs_amp_lib *amp_lib, + int offset, struct fs_amp_scene *scene) +{ + const struct fs_fwm_table *table; + const char *ptr; + + if (!amp_lib || !amp_lib->dev || !scene) + return; + + table = amp_lib->table[FS_INDEX_MODEL]; + ptr = (char *)table; + if (table && offset > 0 && offset < table->size + sizeof(*table)) + scene->model = (struct fs_file_table *)(ptr + offset); + else + scene->model = NULL; +} + +static void fs_get_scene_effect(struct fs_amp_lib *amp_lib, + int offset, struct fs_amp_scene *scene) +{ + const struct fs_fwm_table *table; + const char *ptr; + + if (!amp_lib || !amp_lib->dev || !scene) + return; + + table = amp_lib->table[FS_INDEX_EFFECT]; + ptr = (char *)table; + if (table && offset > 0 && offset < table->size + sizeof(*table)) + scene->effect = (struct fs_file_table *)(ptr + offset); + else + scene->effect = NULL; +} + +static int fs_parse_scene_tables(struct fs_amp_lib *amp_lib) +{ + const struct fs_scene_index *scene_index; + const struct fs_fwm_table *table; + struct fs_amp_scene *scene; + int idx, count; + + if (!amp_lib || !amp_lib->dev) + return -EINVAL; + + count = fs_get_scene_count(amp_lib); + if (count <= 0) + return -EFAULT; + + scene = devm_kzalloc(amp_lib->dev, count * sizeof(*scene), GFP_KERNEL); + if (!scene) + return -ENOMEM; + + amp_lib->scene_count = count; + amp_lib->scene = scene; + + table = amp_lib->table[FS_INDEX_SCENE]; + scene_index = (struct fs_scene_index *)table->buf; + + for (idx = 0; idx < count; idx++) { + fs_get_fwm_string(amp_lib, scene_index->name, &scene->name); + if (!scene->name) + scene->name = devm_kasprintf(amp_lib->dev, + GFP_KERNEL, "S%d", idx); + dev_dbg(amp_lib->dev, "scene.%d name: %s\n", idx, scene->name); + fs_get_scene_reg(amp_lib, scene_index->reg, scene); + fs_get_scene_model(amp_lib, scene_index->model, scene); + fs_get_scene_effect(amp_lib, scene_index->effect, scene); + scene++; + scene_index++; + } + + return 0; +} + +static int fs_parse_all_tables(struct fs_amp_lib *amp_lib) +{ + const struct fs_fwm_table *table; + const struct fs_fwm_index *index; + const char *ptr; + int idx, count; + int ret; + + if (!amp_lib || !amp_lib->dev || !amp_lib->hdr) + return -EINVAL; + + /* Parse all fwm tables */ + table = (struct fs_fwm_table *)amp_lib->hdr->params; + index = (struct fs_fwm_index *)table->buf; + count = table->size / sizeof(*index); + + for (idx = 0; idx < count; idx++, index++) { + if (index->type >= FS_INDEX_MAX) + return -ERANGE; + ptr = (char *)table + (int)index->offset; + amp_lib->table[index->type] = (struct fs_fwm_table *)ptr; + } + + /* Parse all scene tables */ + ret = fs_parse_scene_tables(amp_lib); + if (ret) + dev_err(amp_lib->dev, "Failed to parse scene: %d\n", ret); + + return ret; +} + +static int fs_verify_firmware(struct fs_amp_lib *amp_lib) +{ + const struct fs_fwm_header *hdr; + int crcsum; + + if (!amp_lib || !amp_lib->dev || !amp_lib->hdr) + return -EINVAL; + + hdr = amp_lib->hdr; + + /* Verify the crcsum code */ + crcsum = crc16(0x0000, (const char *)&hdr->crc_size, hdr->crc_size); + if (crcsum != hdr->crc16) { + dev_err(amp_lib->dev, "Failed to checksum: %x-%x\n", + crcsum, hdr->crc16); + return -EFAULT; + } + + /* Verify the devid(chip_type) */ + if (amp_lib->devid != LO_U16(hdr->chip_type)) { + dev_err(amp_lib->dev, "DEVID dismatch: %04X#%04X\n", + amp_lib->devid, hdr->chip_type); + return -EINVAL; + } + + return 0; +} + +static void fs_print_firmware_info(struct fs_amp_lib *amp_lib) +{ + const struct fs_fwm_header *hdr; + const char *pro_name = NULL; + const char *dev_name = NULL; + + if (!amp_lib || !amp_lib->dev || !amp_lib->hdr) + return; + + hdr = amp_lib->hdr; + + fs_get_fwm_string(amp_lib, hdr->project, &pro_name); + fs_get_fwm_string(amp_lib, hdr->device, &dev_name); + + dev_info(amp_lib->dev, "Project: %s Device: %s\n", + pro_name ? pro_name : "null", + dev_name ? dev_name : "null"); + + dev_info(amp_lib->dev, "Date: %04d%02d%02d-%02d%02d\n", + hdr->date.year, hdr->date.month, hdr->date.day, + hdr->date.hour, hdr->date.minute); +} + +int fs_amp_load_firmware(struct fs_amp_lib *amp_lib, const char *name) +{ + const struct firmware *cont; + struct fs_fwm_header *hdr; + int ret; + + if (!amp_lib || !amp_lib->dev || !name) + return -EINVAL; + + ret = request_firmware(&cont, name, amp_lib->dev); + if (ret) { + dev_err(amp_lib->dev, "Failed to request %s: %d\n", name, ret); + return ret; + } + + dev_info(amp_lib->dev, "Loading %s - size: %zu\n", name, cont->size); + + hdr = devm_kmemdup(amp_lib->dev, cont->data, cont->size, GFP_KERNEL); + release_firmware(cont); + if (!hdr) + return -ENOMEM; + + amp_lib->hdr = hdr; + ret = fs_verify_firmware(amp_lib); + if (ret) { + amp_lib->hdr = NULL; + return ret; + } + + ret = fs_parse_all_tables(amp_lib); + if (ret) { + amp_lib->hdr = NULL; + return ret; + } + + fs_print_firmware_info(amp_lib); + + return 0; +} +EXPORT_SYMBOL_GPL(fs_amp_load_firmware); + +MODULE_AUTHOR("Nick Li <nick.li@foursemi.com>"); +MODULE_DESCRIPTION("FourSemi audio amplifier library"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/fs-amp-lib.h b/sound/soc/codecs/fs-amp-lib.h new file mode 100644 index 000000000..4a77c7b38 --- /dev/null +++ b/sound/soc/codecs/fs-amp-lib.h @@ -0,0 +1,150 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * fs-amp-lib.h --- Common library for FourSemi Audio Amplifiers + * + * Copyright (C) 2016-2025 Shanghai FourSemi Semiconductor Co.,Ltd. + */ + +#ifndef __FS_AMP_LIB_H__ +#define __FS_AMP_LIB_H__ + +#define HI_U16(a) (((a) >> 8) & 0xFF) +#define LO_U16(a) ((a) & 0xFF) +#define FS_TABLE_NAME_LEN (4) +#define FS_SCENE_COUNT_MAX (16) +#define FS_CMD_DELAY_MS_MAX (100) /* 100ms */ + +#define FS_CMD_DELAY (0xFF) +#define FS_CMD_BURST (0xFE) +#define FS_CMD_UPDATE (0xFD) + +#define FS_SOC_ENUM_EXT(xname, xhandler_info, xhandler_get, xhandler_put) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = xhandler_info, \ + .get = xhandler_get, .put = xhandler_put \ +} + +enum fs_index_type { + FS_INDEX_INFO = 0, + FS_INDEX_STCOEF, + FS_INDEX_SCENE, + FS_INDEX_MODEL, + FS_INDEX_REG, + FS_INDEX_EFFECT, + FS_INDEX_STRING, + FS_INDEX_WOOFER, + FS_INDEX_MAX, +}; + +#pragma pack(push, 1) + +struct fs_reg_val { + u8 reg; + u16 val; +}; + +struct fs_reg_bits { + u8 cmd; /* FS_CMD_UPDATE */ + u8 reg; + u16 val; + u16 mask; +}; + +struct fs_cmd_pkg { + union { + u8 cmd; + struct fs_reg_val regv; + struct fs_reg_bits regb; + }; +}; + +struct fs_fwm_index { + /* Index type */ + u16 type; + /* Offset address starting from the end of header */ + u16 offset; +}; + +struct fs_fwm_table { + char name[FS_TABLE_NAME_LEN]; + u16 size; /* size of buf */ + u8 buf[]; +}; + +struct fs_scene_index { + /* Offset address(scene name) in string table */ + u16 name; + /* Offset address(scene reg) in register table */ + u16 reg; + /* Offset address(scene model) in model table */ + u16 model; + /* Offset address(scene effect) in effect table */ + u16 effect; +}; + +struct fs_reg_table { + u16 size; /* size of buf */ + u8 buf[]; +}; + +struct fs_file_table { + u16 name; + u16 size; /* size of buf */ + u8 buf[]; +}; + +struct fs_fwm_date { + u32 year:12; + u32 month:4; + u32 day:5; + u32 hour:5; + u32 minute:6; +}; + +struct fs_fwm_header { + u16 version; + u16 project; /* Offset address(project name) in string table */ + u16 device; /* Offset address(device name) in string table */ + struct fs_fwm_date date; + u16 crc16; + u16 crc_size; /* Starting position for CRC checking */ + u16 chip_type; + u16 addr; /* 7-bit i2c address */ + u16 spkid; + u16 rsvd[6]; + u8 params[]; +}; + +#pragma pack(pop) + +struct fs_i2s_srate { + u32 srate; /* Sample rate */ + u16 i2ssr; /* Value of Bit field[I2SSR] */ +}; + +struct fs_pll_div { + unsigned int bclk; /* Rate of bit clock */ + u16 pll1; + u16 pll2; + u16 pll3; +}; + +struct fs_amp_scene { + const char *name; + const struct fs_reg_table *reg; + const struct fs_file_table *model; + const struct fs_file_table *effect; +}; + +struct fs_amp_lib { + const struct fs_fwm_header *hdr; + const struct fs_fwm_table *table[FS_INDEX_MAX]; + struct fs_amp_scene *scene; + struct device *dev; + int scene_count; + u16 devid; +}; + +int fs_amp_load_firmware(struct fs_amp_lib *amp_lib, const char *name); + +#endif // __FS_AMP_LIB_H__ -- 2.39.5 ^ permalink raw reply related [flat|nested] 37+ messages in thread
* [PATCH v1 2/4] ASoC: codecs: Add FourSemi FS2104/5S audio amplifier driver 2025-07-03 3:56 [PATCH v1 0/4] ASoC: codecs: Add support for FourSemi FS2104/5S Nick 2025-07-03 3:56 ` [PATCH v1 1/4] ASoC: codecs: Add library for FourSemi audio amplifiers Nick @ 2025-07-03 3:56 ` Nick 2025-07-03 14:59 ` Mark Brown 2025-07-03 3:56 ` [PATCH v1 3/4] ASoC: dt-bindings: Add dt bindings for FS2104/5S audio amplifiers Nick ` (2 subsequent siblings) 4 siblings, 1 reply; 37+ messages in thread From: Nick @ 2025-07-03 3:56 UTC (permalink / raw) To: lgirdwood, broonie, robh, krzk+dt, conor+dt, perex, tiwai, nick.li Cc: like.sin, xiaoming.yang, danyang.zheng, linux-sound, devicetree, linux-kernel From: Nick Li <nick.li@foursemi.com> The FS2104/5S are FourSemi digital audio amplifiers with I2C control. They are Inductor-Less, Stereo, Closed-Loop, Digital Input Class-D Power Amplifiers with Enhanced Signal Processing. Signed-off-by: Nick Li <nick.li@foursemi.com> --- sound/soc/codecs/Kconfig | 11 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/fs210x.c | 1616 +++++++++++++++++++++++++++++++++++++ sound/soc/codecs/fs210x.h | 79 ++ 4 files changed, 1708 insertions(+) create mode 100644 sound/soc/codecs/fs210x.c create mode 100644 sound/soc/codecs/fs210x.h diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 6265cc897..c2df32cc4 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -125,6 +125,7 @@ config SND_SOC_ALL_CODECS imply SND_SOC_ES7134 imply SND_SOC_ES7241 imply SND_SOC_FRAMER + imply SND_SOC_FS210X imply SND_SOC_GTM601 imply SND_SOC_HDAC_HDMI imply SND_SOC_HDAC_HDA @@ -1242,6 +1243,16 @@ config SND_SOC_FS_AMP_LIB select CRC16 tristate +config SND_SOC_FS210X + tristate 'FourSemi FS2104/5S digital audio amplifier' + depends on I2C + select GPIOLIB + select REGMAP_I2C + select SND_SOC_FS_AMP_LIB + help + Enable support for FourSemi FS2104/5S digital audio amplifier + with I2C control. + config SND_SOC_GTM601 tristate 'GTM601 UMTS modem audio codec' diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 9afce9aa6..967e939a4 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -138,6 +138,7 @@ snd-soc-es8375-y := es8375.o snd-soc-es8389-y := es8389.o snd-soc-framer-y := framer-codec.o snd-soc-fs-amp-lib-y := fs-amp-lib.o +snd-soc-fs210x-y := fs210x.o snd-soc-gtm601-y := gtm601.o snd-soc-hdac-hdmi-y := hdac_hdmi.o snd-soc-hdac-hda-y := hdac_hda.o @@ -563,6 +564,7 @@ obj-$(CONFIG_SND_SOC_ES8375) += snd-soc-es8375.o obj-$(CONFIG_SND_SOC_ES8389) += snd-soc-es8389.o obj-$(CONFIG_SND_SOC_FRAMER) += snd-soc-framer.o obj-$(CONFIG_SND_SOC_FS_AMP_LIB)+= snd-soc-fs-amp-lib.o +obj-$(CONFIG_SND_SOC_FS210X) += snd-soc-fs210x.o obj-$(CONFIG_SND_SOC_GTM601) += snd-soc-gtm601.o obj-$(CONFIG_SND_SOC_HDAC_HDMI) += snd-soc-hdac-hdmi.o obj-$(CONFIG_SND_SOC_HDAC_HDA) += snd-soc-hdac-hda.o diff --git a/sound/soc/codecs/fs210x.c b/sound/soc/codecs/fs210x.c new file mode 100644 index 000000000..cf2781594 --- /dev/null +++ b/sound/soc/codecs/fs210x.c @@ -0,0 +1,1616 @@ +// 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/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/of_gpio.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_DRV_VERSION "v1.0.6" +#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_VOLUME_MIN 0 +#define FS210X_VOLUME_MAX 511 +#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; + const char *dai_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; + unsigned int bclk; + unsigned int srate; + int scene_id; + u16 devid; + u16 vol[2]; /* L/R channels volume */ + bool is_inited; + bool is_suspended; + bool is_bclk_on; + bool is_playing; +}; + +static DEFINE_MUTEX(fs210x_mutex); + +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->clk_bclk) + return 0; + + if ((fs210x->is_bclk_on ^ on) == 0) + return 0; + + dev_dbg(fs210x->dev, "bclk switch %s\n", on ? "on" : "off"); + + if (on) { + clk_set_rate(fs210x->clk_bclk, fs210x->bclk); + ret = clk_prepare_enable(fs210x->clk_bclk); + fs210x->is_bclk_on = true; + usleep_range(2000, 2050); /* >= 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; + } + + dev_dbg(fs210x->dev, "RW: %02x %04x\n", reg, val); + + 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; + } + + if (pval) + *pval = (u16)val; + + dev_dbg(fs210x->dev, "RR: %02x %04x\n", reg, 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; + } + + dev_dbg(fs210x->dev, "RU: %02x %04x %04x\n", reg, mask, val); + + 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; + } + + dev_dbg(fs210x->dev, "BW: %02x %d\n", reg, size); + + return 0; +} + +static int fs210x_reg_dump(struct fs210x_priv *fs210x) +{ + u16 val[8]; /* 8 registers per line */ + u8 idx, reg; + int ret; + + if (!fs210x || !fs210x->dev) + return -EINVAL; + +#define FS210X_REG_DUMP_MAX 0xCF + for (reg = 0x00; reg <= FS210X_REG_DUMP_MAX; reg++) { + /* Print 8 register values per line */ + idx = (reg & 0x7); + ret = fs210x_reg_read(fs210x, reg, val + idx); + if (ret) + break; + if (idx != 0x7 && reg != FS210X_REG_DUMP_MAX) + continue; + dev_info(fs210x->dev, + "%02Xh: %04x %04x %04x %04x %04x %04x %04x %04x\n", + (reg & 0xF8), val[0], val[1], val[2], + val[3], val[4], val[5], val[6], val[7]); + } + + return ret; +} + +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 -ENOTSUPP; + delay_us = pkg->regv.val * 1000; + usleep_range(delay_us, delay_us + 50); + *offset = sizeof(pkg->regv); + return 0; + } + + dev_err(fs210x->dev, "Invalid pkg cmd: %d\n", pkg->cmd); + + return -ENOTSUPP; +} + +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; + + usleep_range(10000, 10050); /* >= 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; + + usleep_range(30000, 30050); /* >= 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); + } + + usleep_range(10000, 10050); /* >= 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 inline int volume_is_valid(u16 vol) +{ + return (vol <= FS210X_VOLUME_MAX); +} + +static int fs210x_set_pcm_volume(struct fs210x_priv *fs210x) +{ + u16 vol[2]; + int ret; + + if (fs210x->devid == FS2105S_DEVICE_ID) { + /* + * FS2105SE volume registers: 39h(Left)/3Ah(Right): + * VOL[15:7]: 0 dB to -95.8125dB in steps of -0.1875dB + * 1FFh: 0dB + * 000h: -95.8125dB + */ + vol[0] = fs210x->vol[0] << FS2105S_39H_VOL_SHIFT; + vol[1] = fs210x->vol[1] << FS2105S_3AH_VOL_SHIFT; + } else { + /* + * FS2104 volume registers: 39h(Left)/3Ah(Right): + * VOL[15:6]: 0 dB to -131.8125dB in steps of -0.1875dB + * 2BFh: 0dB + * 0C0h: -95.8125dB + * 000h: -131.8125dB + * Offset between FS2104 and FS2105S: 2BFh-1FFh=0C0h + */ +#define VOL_OFFSET (0x2BF - 0x1FF) + vol[0] = (fs210x->vol[0] + VOL_OFFSET) << FS210X_39H_VOL_SHIFT; + vol[1] = (fs210x->vol[1] + VOL_OFFSET) << FS210X_3AH_VOL_SHIFT; + } + + ret = fs210x_reg_write(fs210x, FS210X_39H_LVOLCTRL, vol[0]); + ret |= fs210x_reg_write(fs210x, FS210X_3AH_RVOLCTRL, vol[1]); + + return ret; +} + +static int fs210x_init_chip(struct fs210x_priv *fs210x) +{ + int scene_id; + int ret; + + /* i2c reset */ + ret = fs210x_reg_write(fs210x, FS210X_10H_PWRCTRL, + FS210X_10H_I2C_RESET); + if (ret) + goto tag_power_down; + + /* Wait >= 10ms for i2c resetting */ + usleep_range(10000, 10050); + + /* 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; + } + + ret = fs210x_set_pcm_volume(fs210x); + +tag_power_down: + /* Power down the device */ + ret |= fs210x_reg_write(fs210x, FS210X_11H_SYSCTRL, + FS210X_11H_DPS_PWDN); + if (!ret) + fs210x->is_inited = true; + + return ret; +} + +static void fs210x_sdz_pin_set(struct fs210x_priv *fs210x, bool active) +{ + if (!fs210x || !fs210x->gpio_sdz) + return; + + /* + * The SDZ pin(Shut Down) is active low, when it is pulled low, + * it will trigger two procedures sequentially: + * - Power down the chip; + * - Reset the chip to shut down state. + * NOTE: + * The I2C communication is nack until the SDZ pin + * is pulled up and wait 10ms at least. + */ + if (active) { + gpiod_set_value_cansleep(fs210x->gpio_sdz, 1); + dev_dbg(fs210x->dev, "SDZ pin is OFF\n"); + usleep_range(30000, 30050); /* >= 30ms */ + } else { + gpiod_set_value_cansleep(fs210x->gpio_sdz, 0); + dev_dbg(fs210x->dev, "SDZ pin is ON\n"); + usleep_range(10000, 10050); /* >= 10ms */ + } +} + +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_mute(struct fs210x_priv *fs210x, bool mute) +{ + int ret; + + if (mute) { + cancel_delayed_work_sync(&fs210x->fault_check_work); + cancel_delayed_work_sync(&fs210x->start_work); + mutex_lock(&fs210x_mutex); + ret = fs210x_dev_stop(fs210x); + mutex_unlock(&fs210x_mutex); + return ret; + } + + /* + * According to the power up/down sequence of FS210x, + * the FS210x requests the I2S clock has been present + * and stable(>= 2ms) before it playing. + */ + if (fs210x->clk_bclk) { + mutex_lock(&fs210x_mutex); + ret = fs210x_dev_play(fs210x); + mutex_unlock(&fs210x_mutex); + } else { + schedule_delayed_work(&fs210x->start_work, + msecs_to_jiffies(FS210X_START_DELAY_MS)); + ret = 0; + } + + schedule_delayed_work(&fs210x->fault_check_work, + msecs_to_jiffies(FS210X_FAULT_CHECK_INTERVAL_MS)); + + return ret; +} + +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); + + dev_dbg(fs210x->dev, "fmt: %x\n", fmt); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBC_CFC: + /* Only supports slave mode */ + break; + default: + dev_err(fs210x->dev, "Only supports slave 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 */ + + dev_info(fs210x->dev, "hw params: %d-%d-%d\n", + fs210x->srate, chn_num, fs210x->bclk); + + /* The FS2105S can't support 16kHz sample rate. */ + if (fs210x->devid == FS2105S_DEVICE_ID && fs210x->srate == 16000) + return -ENOTSUPP; + + mutex_lock(&fs210x_mutex); + ret = fs210x_set_hw_params(fs210x); + mutex_unlock(&fs210x_mutex); + 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; + int ret; + + if (stream != SNDRV_PCM_STREAM_PLAYBACK) + return 0; + + fs210x = snd_soc_component_get_drvdata(dai->component); + + if (!fs210x->is_inited) + return -EFAULT; + + if (mute) { + ret = fs210x_mute(fs210x, true); + fs210x_bclk_set(fs210x, false); + dev_info(fs210x->dev, "playback mute\n"); + } else { + fs210x_bclk_set(fs210x, true); + ret = fs210x_mute(fs210x, false); + dev_info(fs210x->dev, "playback unmute\n"); + } + + return ret; +} + +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_mutex); + + ret = fs210x_dev_play(fs210x); + if (ret) + dev_err(fs210x->dev, "Failed to start playing: %d\n", ret); + + mutex_unlock(&fs210x_mutex); +} + +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_mutex); + if (!fs210x->is_inited || !fs210x->is_playing) { + mutex_unlock(&fs210x_mutex); + return; + } + + ret = fs210x_reg_read(fs210x, FS210X_05H_ANASTAT, &status); + mutex_unlock(&fs210x_mutex); + 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_err(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"); + + if (status != FS210X_05H_ANASTAT_OK) + fs210x_reg_dump(fs210x); + + schedule_delayed_work(&fs210x->fault_check_work, + msecs_to_jiffies(FS210X_FAULT_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_pcm_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + + uinfo->value.integer.min = FS210X_VOLUME_MIN; + uinfo->value.integer.max = FS210X_VOLUME_MAX; + + return 0; +} + +static int fs210x_pcm_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct fs210x_priv *fs210x; + int ret; + + ret = fs210x_get_drvdata_from_kctrl(kcontrol, &fs210x); + if (ret) { + pr_err("pcm_volume_get: fs210x is null\n"); + return -EINVAL; + } + + mutex_lock(&fs210x_mutex); + ucontrol->value.integer.value[0] = fs210x->vol[0]; + ucontrol->value.integer.value[1] = fs210x->vol[1]; + mutex_unlock(&fs210x_mutex); + + return 0; +} + +static int fs210x_pcm_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct fs210x_priv *fs210x; + long *pval; + int ret; + + ret = fs210x_get_drvdata_from_kctrl(kcontrol, &fs210x); + if (ret || !fs210x->dev) { + pr_err("pcm_volume_put: fs210x is null\n"); + return -EINVAL; + } + + pval = ucontrol->value.integer.value; + if (!volume_is_valid(*pval) || !volume_is_valid(*(pval + 1))) { + dev_err(fs210x->dev, "Invalid volume: %ld-%ld\n", + *pval, *(pval + 1)); + return -EINVAL; + } + + fs210x->vol[0] = (u16)*pval; + fs210x->vol[1] = (u16)*(pval + 1); + dev_dbg(fs210x->dev, "Set volume: %d-%d\n", + fs210x->vol[0], fs210x->vol[1]); + + mutex_lock(&fs210x_mutex); + + if (fs210x->is_suspended) { + mutex_unlock(&fs210x_mutex); + return 0; + } + + ret = fs210x_set_pcm_volume(fs210x); + if (ret) + dev_err(fs210x->dev, "Failed to set volume: %d\n", ret); + + mutex_unlock(&fs210x_mutex); + + return ret; +} + +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); + dev_dbg(fs210x->dev, "Scene name.%d: %s\n", idx, name); + + 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_mutex); + /* + * 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; + dev_dbg(fs210x->dev, "Get scene index: %d\n", index); + mutex_unlock(&fs210x_mutex); + + 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; + 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_mutex); + + /* + * 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; + if (fs210x->is_suspended) { + fs210x->scene_id = scene_id; + mutex_unlock(&fs210x_mutex); + return 0; + } + + ret = fs210x_set_scene(fs210x, scene_id); + if (ret) + dev_err(fs210x->dev, "Failed to set scene: %d\n", ret); + + mutex_unlock(&fs210x_mutex); + + return ret; +} + +static const struct snd_soc_dai_ops fs210x_dai_ops = { + .set_fmt = fs210x_dai_set_fmt, + .hw_params = fs210x_dai_hw_params, + .mute_stream = fs210x_dai_mute, +}; + +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 struct snd_kcontrol_new fs210x_controls[] = { + FS_SOC_ENUM_EXT("PCM Playback Volume", + fs210x_pcm_volume_info, + fs210x_pcm_volume_get, + fs210x_pcm_volume_put), +}; + +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("AIF IN", "Playback", 0, SND_SOC_NOPM, 0, 0), + 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_optional_controls(struct fs210x_priv *fs210x, + struct snd_soc_component *cmpnt) +{ + int count; + + if (!fs210x || !cmpnt) + return -EINVAL; + + /* + * 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; + + count = ARRAY_SIZE(fs210x_scene_control); + + return snd_soc_add_component_controls(cmpnt, + fs210x_scene_control, + count); +} + +static int fs210x_get_bclk(struct fs210x_priv *fs210x, + struct snd_soc_component *cmpnt) +{ + struct clk *bclk; + int ret; + + bclk = devm_clk_get(fs210x->dev, "bclk"); + if (IS_ERR_OR_NULL(bclk)) { + ret = bclk ? PTR_ERR(bclk) : -ENODATA; + if (ret == -EPROBE_DEFER) + return ret; + /* + * If the SOC doesn't have the bclk clock source, + * we skip setting the bclk clock. + */ + return 0; + } + + fs210x->clk_bclk = bclk; + dev_dbg(fs210x->dev, "Got I2S bclk clock\n"); + + return 0; +} + +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) { + dev_err(fs210x->dev, "Failed to load firmware: %d\n", ret); + return ret; + } + + ret = fs210x_add_optional_controls(fs210x, cmpnt); + if (ret) { + dev_err(fs210x->dev, "Failed to add opt-ctrl: %d\n", ret); + return ret; + } + + INIT_DELAYED_WORK(&fs210x->fault_check_work, fs210x_fault_check_work); + INIT_DELAYED_WORK(&fs210x->start_work, fs210x_start_work); + + fs210x_get_bclk(fs210x, cmpnt); + + mutex_lock(&fs210x_mutex); + ret = fs210x_init_chip(fs210x); + mutex_unlock(&fs210x_mutex); + if (ret) + dev_err(fs210x->dev, "Failed to probe: %d\n", ret); + + 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); + + dev_dbg(fs210x->dev, "Codec removed\n"); +} + +#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; + + cancel_delayed_work_sync(&fs210x->start_work); + cancel_delayed_work_sync(&fs210x->fault_check_work); + + mutex_lock(&fs210x_mutex); + fs210x->cur_scene = NULL; + fs210x->is_inited = false; + fs210x->is_playing = false; + fs210x->is_suspended = true; + + fs210x_sdz_pin_set(fs210x, true); + mutex_unlock(&fs210x_mutex); + + ret = regulator_bulk_disable(FS210X_NUM_SUPPLIES, fs210x->supplies); + if (ret) { + dev_err(fs210x->dev, "Failed to suspend: %d\n", ret); + return ret; + } + + dev_info(fs210x->dev, "pm suspended\n"); + + 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_mutex); + fs210x_sdz_pin_set(fs210x, false); + + fs210x->is_suspended = false; + ret = fs210x_init_chip(fs210x); + mutex_unlock(&fs210x_mutex); + + dev_info(fs210x->dev, "pm resumed\n"); + + return ret; +} +#else +#define fs210x_suspend NULL +#define fs210x_resume NULL +#endif // CONFIG_PM + +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), + .use_pmdown_time = 1, +}; + +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_NONE, +}; + +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, "fs,fwm-name", &pdata->fwm_name); + if (ret) + pdata->fwm_name = FS210X_DEFAULT_FWM_NAME; + + ret = of_property_read_string(node, "fs,dai-name", &pdata->dai_name); + if (ret) + pdata->dai_name = FS210X_DEFAULT_DAI_NAME; + + fs210x->gpio_sdz = devm_gpiod_get_optional(fs210x->dev, + "sdz", GPIOD_OUT_HIGH); + if (IS_ERR_OR_NULL(fs210x->gpio_sdz)) { + ret = fs210x->gpio_sdz ? PTR_ERR(fs210x->gpio_sdz) : -ENODATA; + fs210x->gpio_sdz = NULL; + if (ret == -EPROBE_DEFER) + return ret; + dev_dbg(fs210x->dev, "Assuming gpio_sdz is unused\n"); + } else { + dev_dbg(fs210x->dev, "gpio_sdz: %d\n", + desc_to_gpio(fs210x->gpio_sdz)); + } + + 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) { + dev_err(fs210x->dev, "Failed to get supplies: %d\n", ret); + return ret; + } + + return 0; +} + +static int fs210x_parse_platdata(struct fs210x_priv *fs210x) +{ + struct fs210x_platform_data *pdata; + int ret; + + pdata = &fs210x->pdata; + ret = fs210x_parse_dts(fs210x, pdata); + if (ret) { + dev_err(fs210x->dev, "Failed to parse dts: %d\n", ret); + return ret; + } + + return 0; +} + +static void fs210x_deinit(struct fs210x_priv *fs210x) +{ + fs210x_sdz_pin_set(fs210x, true); + regulator_bulk_disable(FS210X_NUM_SUPPLIES, fs210x->supplies); +} + +static int fs210x_init(struct fs210x_priv *fs210x) +{ + int ret; + + ret = fs210x_parse_platdata(fs210x); + if (ret) { + dev_err(fs210x->dev, "Failed to parse platdata: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(FS210X_NUM_SUPPLIES, fs210x->supplies); + if (ret) { + dev_err(fs210x->dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + + /* Make sure the SDZ pin is pulled down enough time. */ + usleep_range(10000, 10050); /* >= 10ms */ + fs210x_sdz_pin_set(fs210x, false); + + 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->vol[0] = FS210X_VOLUME_MAX; + fs210x->vol[1] = FS210X_VOLUME_MAX; + + return 0; +} + +static int fs210x_register_snd_component(struct fs210x_priv *fs210x) +{ + struct snd_soc_dai_driver *dai_drv; + int ret; + + dai_drv = devm_kmemdup(fs210x->dev, &fs210x_dai, + sizeof(fs210x_dai), GFP_KERNEL); + if (!dai_drv) + return -ENOMEM; + + dai_drv->name = fs210x->pdata.dai_name; + 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 int fs210x_i2c_probe(struct i2c_client *client) +{ + struct fs210x_priv *fs210x; + int ret; + + dev_info(&client->dev, "version: %s\n", FS210X_DRV_VERSION); + + 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_OR_NULL(fs210x->regmap)) { + dev_err(fs210x->dev, "Failed to get regmap\n"); + ret = fs210x->regmap ? PTR_ERR(fs210x->regmap) : -ENODATA; + return ret; + } + + mutex_lock(&fs210x_mutex); + ret = fs210x_init(fs210x); + mutex_unlock(&fs210x_mutex); + if (ret) + return ret; + + ret = fs210x_register_snd_component(fs210x); + if (ret) { + fs210x_deinit(fs210x); + return ret; + } + + 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); + +#ifdef CONFIG_OF +static const struct of_device_id fs210x_of_match[] = { + { .compatible = "foursemi,fs2104", }, + { .compatible = "foursemi,fs2105s", }, + {}, +}; +MODULE_DEVICE_TABLE(of, fs210x_of_match); +#endif // CONFIG_OF + +static struct i2c_driver fs210x_i2c_driver = { + .driver = { + .name = "fs210x", + .of_match_table = of_match_ptr(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_VERSION(FS210X_DRV_VERSION); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/fs210x.h b/sound/soc/codecs/fs210x.h new file mode 100644 index 000000000..a64f8d37f --- /dev/null +++ b/sound/soc/codecs/fs210x.h @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * fs210x.h -- Driver for the FS2104/5S Audio Amplifier + * + * Copyright (C) 2016-2025 Shanghai FourSemi Semiconductor Co.,Ltd. + */ + +#ifndef __FS210X_H__ +#define __FS210X_H__ + +#define FS210X_00H_STATUS 0x00 +#define FS210X_03H_DEVID 0x03 +#define FS210X_05H_ANASTAT 0x05 +#define FS210X_06H_DIGSTAT 0x06 +#define FS210X_0BH_ACCKEY 0x0B +#define FS210X_10H_PWRCTRL 0x10 +#define FS210X_11H_SYSCTRL 0x11 +#define FS210X_17H_I2SCTRL 0x17 +#define FS210X_30H_DACCTRL 0x30 +#define FS210X_39H_LVOLCTRL 0x39 +#define FS210X_3AH_RVOLCTRL 0x3A +#define FS210X_42H_DACEQWL 0x42 +#define FS210X_46H_DACEQA 0x46 +#define FS210X_A1H_PLLCTRL1 0xA1 +#define FS210X_A2H_PLLCTRL2 0xA2 +#define FS210X_A3H_PLLCTRL3 0xA3 +#define FS210X_ABH_INTSTAT 0xAB +#define FS210X_ACH_INTSTATR 0xAC + +#define FS210X_05H_PVDD_SHIFT 14 +#define FS210X_05H_PVDD_MASK BIT(14) +#define FS210X_05H_OCDL_SHIFT 13 +#define FS210X_05H_OCDL_MASK BIT(13) +#define FS210X_05H_UVDL_SHIFT 12 +#define FS210X_05H_UVDL_MASK BIT(12) +#define FS210X_05H_OVDL_SHIFT 11 +#define FS210X_05H_OVDL_MASK BIT(11) +#define FS210X_05H_OTPDL_SHIFT 10 +#define FS210X_05H_OTPDL_MASK BIT(10) +#define FS210X_05H_OCRDL_SHIFT 9 +#define FS210X_05H_OCRDL_MASK BIT(9) +#define FS210X_05H_OCLDL_SHIFT 8 +#define FS210X_05H_OCLDL_MASK BIT(8) +#define FS210X_05H_DCRDL_SHIFT 7 +#define FS210X_05H_DCRDL_MASK BIT(7) +#define FS210X_05H_DCLDL_SHIFT 6 +#define FS210X_05H_DCLDL_MASK BIT(6) +#define FS210X_05H_SRDL_SHIFT 5 +#define FS210X_05H_SRDL_MASK BIT(5) +#define FS210X_05H_OTWDL_SHIFT 4 +#define FS210X_05H_OTWDL_MASK BIT(4) +#define FS210X_05H_AMPS_SHIFT 3 +#define FS210X_05H_AMPS_MASK BIT(3) +#define FS210X_05H_PLLS_SHIFT 1 +#define FS210X_05H_PLLS_MASK BIT(1) +#define FS210X_05H_ANAS_SHIFT 0 +#define FS210X_05H_ANAS_MASK BIT(0) +#define FS210X_17H_I2SSR_SHIFT 12 +#define FS210X_17H_I2SSR_MASK GENMASK(15, 12) +#define FS210X_30H_RMUTE_SHIFT 8 +#define FS210X_30H_LMUTE_SHIFT 4 +#define FS210X_39H_VOL_SHIFT 6 +#define FS210X_3AH_VOL_SHIFT 6 +#define FS2105S_39H_VOL_SHIFT 7 +#define FS2105S_3AH_VOL_SHIFT 7 + +#define FS210X_05H_ANASTAT_OK 0x400B +#define FS210X_0BH_ACCKEY_ON 0x0091 +#define FS210X_0BH_ACCKEY_OFF 0x0000 +#define FS210X_10H_I2C_RESET 0x0002 +#define FS210X_11H_DPS_HIZ 0x0100 +#define FS210X_11H_DPS_PWDN 0x0000 +#define FS210X_11H_DPS_PLAY 0x0300 +#define FS210X_46H_CAM_BURST_L 0x8000 +#define FS210X_46H_CAM_BURST_R 0x8200 +#define FS2105S_46H_CAM_BURST_W 0x8400 +#define FS210X_46H_CAM_CLEAR 0x0000 + +#endif /* __FS210X_H__ */ -- 2.39.5 ^ permalink raw reply related [flat|nested] 37+ messages in thread
* Re: [PATCH v1 2/4] ASoC: codecs: Add FourSemi FS2104/5S audio amplifier driver 2025-07-03 3:56 ` [PATCH v1 2/4] ASoC: codecs: Add FourSemi FS2104/5S audio amplifier driver Nick @ 2025-07-03 14:59 ` Mark Brown 2025-07-04 11:12 ` Nick Li 0 siblings, 1 reply; 37+ messages in thread From: Mark Brown @ 2025-07-03 14:59 UTC (permalink / raw) To: Nick Cc: lgirdwood, robh, krzk+dt, conor+dt, perex, tiwai, like.sin, xiaoming.yang, danyang.zheng, linux-sound, devicetree, linux-kernel [-- Attachment #1: Type: text/plain, Size: 6038 bytes --] On Thu, Jul 03, 2025 at 11:56:37AM +0800, Nick wrote: > +++ b/sound/soc/codecs/fs210x.c > @@ -0,0 +1,1616 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * fs210x.c -- Driver for the FS2104/5S Audio Amplifier > + * > + * Copyright (C) 2016-2025 Shanghai FourSemi Semiconductor Co.,Ltd. Please make the entire comment a C++ one so things look more intentional. > +#define FS210X_DRV_VERSION "v1.0.6" We generally don't do versions for components within the kernel, nobody is going to update it going forward. > +static int fs210x_reg_read(struct fs210x_priv *fs210x, > + u8 reg, u16 *pval) > + if (pval) > + *pval = (u16)val; > + If this cast is needed that's a bit worrying. > + dev_dbg(fs210x->dev, "RR: %02x %04x\n", reg, val); We do have a lot of logging support in regmap which is more controllable. > +static int fs210x_reg_dump(struct fs210x_priv *fs210x) > +{ This is duplicating regmap's debugfs. > + } else if (pkg->cmd == FS_CMD_DELAY) { > + if (pkg->regv.val > FS_CMD_DELAY_MS_MAX) > + return -ENOTSUPP; > + delay_us = pkg->regv.val * 1000; > + usleep_range(delay_us, delay_us + 50); Just use fsleep(), it'll use the most sensible delay type for the delay. In general this applies to all delays in the driver, but especially with a variable delay like this. > +static int fs210x_set_pcm_volume(struct fs210x_priv *fs210x) > + ret = fs210x_reg_write(fs210x, FS210X_39H_LVOLCTRL, vol[0]); > + ret |= fs210x_reg_write(fs210x, FS210X_3AH_RVOLCTRL, vol[1]); > + This looks pretty generic, why is it not using a standard control type? > + ret = fs210x_set_pcm_volume(fs210x); The driver should use the device defaults rather than having to > +static void fs210x_sdz_pin_set(struct fs210x_priv *fs210x, bool active) > +{ > + if (!fs210x || !fs210x->gpio_sdz) > + return; Shouldn't this be integrated with the chip init/reset? > +static int fs210x_mute(struct fs210x_priv *fs210x, bool mute) > +{ > + int ret; > + > + if (mute) { > + cancel_delayed_work_sync(&fs210x->fault_check_work); > + cancel_delayed_work_sync(&fs210x->start_work); > + mutex_lock(&fs210x_mutex); > + ret = fs210x_dev_stop(fs210x); > + mutex_unlock(&fs210x_mutex); > + return ret; > + } Mute is expected to be a really fast operation, this is stopping the device entirely and fiddling about with locks (which were held where?). This looks like the device just doesn't support mute. > + /* > + * According to the power up/down sequence of FS210x, > + * the FS210x requests the I2S clock has been present > + * and stable(>= 2ms) before it playing. > + */ > + if (fs210x->clk_bclk) { > + mutex_lock(&fs210x_mutex); > + ret = fs210x_dev_play(fs210x); > + mutex_unlock(&fs210x_mutex); > + } else { This is definitely not appropriate for mute, it should be in the power management flow - either set_bias_level() or a DAPM widget. > + case SND_SOC_DAIFMT_CBC_CFC: > + /* Only supports slave mode */ consumer mode. > +static int fs210x_dai_hw_params(struct snd_pcm_substream *substream, > + struct snd_pcm_hw_params *params, > + struct snd_soc_dai *dai) > + dev_info(fs210x->dev, "hw params: %d-%d-%d\n", > + fs210x->srate, chn_num, fs210x->bclk); No dev_info() prints in the normal playback/record flow, that just spams the logs too easily. > + /* The FS2105S can't support 16kHz sample rate. */ > + if (fs210x->devid == FS2105S_DEVICE_ID && fs210x->srate == 16000) > + return -ENOTSUPP; This should be reported in constraints too. > + if (!(status & FS210X_05H_AMPS_MASK)) > + dev_err(fs210x->dev, "Amplifier unready\n"); Does this get triggered during the normal start/stop flow? > + schedule_delayed_work(&fs210x->fault_check_work, > + msecs_to_jiffies(FS210X_FAULT_CHECK_INTERVAL_MS)); Might be good to have this tunable from sysfs. > +static int fs210x_pcm_volume_put(struct snd_kcontrol *kcontrol, > + struct snd_ctl_elem_value *ucontrol) > +{ > + struct fs210x_priv *fs210x; > + long *pval; > + int ret; > + > + ret = fs210x_get_drvdata_from_kctrl(kcontrol, &fs210x); > + if (ret || !fs210x->dev) { > + pr_err("pcm_volume_put: fs210x is null\n"); > + return -EINVAL; > + } > + _put() callbacks should return 1 when the control changes values, though as indicated above I'm not clear this needs to be a custom control at all. Please run mixer-test against your driver, it will check for this and other issues. > +static int fs210x_effect_scene_put(struct snd_kcontrol *kcontrol, > + struct snd_ctl_elem_value *ucontrol) > +{ > + /* > + * 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; > + if (fs210x->is_suspended) { > + fs210x->scene_id = scene_id; > + mutex_unlock(&fs210x_mutex); > + return 0; > + } This doesn't validate the passed value at all. > +static int fs210x_probe(struct snd_soc_component *cmpnt) > +{ > + struct fs210x_priv *fs210x; > + int ret; > + INIT_DELAYED_WORK(&fs210x->fault_check_work, fs210x_fault_check_work); > + INIT_DELAYED_WORK(&fs210x->start_work, fs210x_start_work); > + > + fs210x_get_bclk(fs210x, cmpnt); This sort of initialisation and resource acquisition that doesn't require any audio stuff should be done at the bus level so that we don't register with ASoC until all the inputs are ready. > +#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; > + > + cancel_delayed_work_sync(&fs210x->start_work); > + cancel_delayed_work_sync(&fs210x->fault_check_work); > + > + mutex_lock(&fs210x_mutex); We don't need to prevent new work being scheduled? [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 488 bytes --] ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v1 2/4] ASoC: codecs: Add FourSemi FS2104/5S audio amplifier driver 2025-07-03 14:59 ` Mark Brown @ 2025-07-04 11:12 ` Nick Li 2025-07-04 14:37 ` Mark Brown 0 siblings, 1 reply; 37+ messages in thread From: Nick Li @ 2025-07-04 11:12 UTC (permalink / raw) To: Mark Brown Cc: lgirdwood, robh, krzk+dt, conor+dt, perex, tiwai, xiaoming.yang, danyang.zheng, linux-sound, devicetree, linux-kernel On Thu, Jul 03, 2025 at 03:59:34PM +0100, Mark Brown wrote: > On Thu, Jul 03, 2025 at 11:56:37AM +0800, Nick wrote: > > > +++ b/sound/soc/codecs/fs210x.c > > @@ -0,0 +1,1616 @@ > > +// SPDX-License-Identifier: GPL-2.0 > > +/* > > + * fs210x.c -- Driver for the FS2104/5S Audio Amplifier > > + * > > + * Copyright (C) 2016-2025 Shanghai FourSemi Semiconductor Co.,Ltd. > > Please make the entire comment a C++ one so things look more > intentional. OK, we will update to use // istead of /**/ > > > +#define FS210X_DRV_VERSION "v1.0.6" > > We generally don't do versions for components within the kernel, nobody > is going to update it going forward. OK, we will delete the version and related calls > > > +static int fs210x_reg_read(struct fs210x_priv *fs210x, > > + u8 reg, u16 *pval) > > > + if (pval) > > + *pval = (u16)val; > > + > > If this cast is needed that's a bit worrying. OK, we will delete the judgement. > > > + dev_dbg(fs210x->dev, "RR: %02x %04x\n", reg, val); > > We do have a lot of logging support in regmap which is more > controllable. OK, we will remove this debug log, it's for test. > > > +static int fs210x_reg_dump(struct fs210x_priv *fs210x) > > +{ > > This is duplicating regmap's debugfs. We use it to print reigsters when the device accors a fault in the checking monitor, especially for dealing with probabilistic issues, these logs are beneficial for analyzing the problem. > > > + } else if (pkg->cmd == FS_CMD_DELAY) { > > + if (pkg->regv.val > FS_CMD_DELAY_MS_MAX) > > + return -ENOTSUPP; > > + delay_us = pkg->regv.val * 1000; > > + usleep_range(delay_us, delay_us + 50); > > Just use fsleep(), it'll use the most sensible delay type for the delay. > In general this applies to all delays in the driver, but especially with > a variable delay like this. OK, we will replace usleep_range to fsleep > > > +static int fs210x_set_pcm_volume(struct fs210x_priv *fs210x) > > > + ret = fs210x_reg_write(fs210x, FS210X_39H_LVOLCTRL, vol[0]); > > + ret |= fs210x_reg_write(fs210x, FS210X_3AH_RVOLCTRL, vol[1]); > > + > > This looks pretty generic, why is it not using a standard control type? 1. We use regmap as REGMAP_NONE, because most of the registers settings are in the firmware, if we use a standard control,the driver shouldn't cache the registers after suspending the device(it will be reset). 2. The volume registers of FS2104 and FS2105S are different, if we us a stardard control, we need two controls, and register it by checking the device type. so we customize the volume control. > > > + ret = fs210x_set_pcm_volume(fs210x); > > The driver should use the device defaults rather than having to The volume contorl can be used to set different volumes, the volume will be masked in fs210x->vol[2], we restore the volume when the driver resumes(reinitializes) the deivce. > > > +static void fs210x_sdz_pin_set(struct fs210x_priv *fs210x, bool active) > > +{ > > + if (!fs210x || !fs210x->gpio_sdz) > > + return; > > Shouldn't this be integrated with the chip init/reset? 1. We implement this function(reset and wait times) to clarify that pulling up/down the SDZ/reset pin must to wait enougth delay time. > > > +static int fs210x_mute(struct fs210x_priv *fs210x, bool mute) > > +{ > > + int ret; > > + > > + if (mute) { > > + cancel_delayed_work_sync(&fs210x->fault_check_work); > > + cancel_delayed_work_sync(&fs210x->start_work); > > + mutex_lock(&fs210x_mutex); > > + ret = fs210x_dev_stop(fs210x); > > + mutex_unlock(&fs210x_mutex); > > + return ret; > > + } > > Mute is expected to be a really fast operation, this is stopping the > device entirely and fiddling about with locks (which were held where?). > This looks like the device just doesn't support mute. To avoid the pop noise, the device will fade out when we mute/stop it, and the power down sequence of FS210x requests the driver to wait enougth times before the BCLK is off or starting device in next time. > > > + /* > > + * According to the power up/down sequence of FS210x, > > + * the FS210x requests the I2S clock has been present > > + * and stable(>= 2ms) before it playing. > > + */ > > + if (fs210x->clk_bclk) { > > + mutex_lock(&fs210x_mutex); > > + ret = fs210x_dev_play(fs210x); > > + mutex_unlock(&fs210x_mutex); > > + } else { > > This is definitely not appropriate for mute, it should be in the power > management flow - either set_bias_level() or a DAPM widget. 1. Because the device uses BCLK clock as the clock source, we need to start the device in the life cycle of the clock, also we need to start device after the PLL setting(set in dai->hw_params) so we start the device in here: dai->mute_stream(unmute) 2. If the SOC(s) doesn't have the clock(bclk) for us to configure, It meams no clock bclk defined in the DTS, and the clock is activated in dai->trigger start usually, so we will use a delay work to start the device in here. Any good ideas about satisfying this power up/down sequence? > > > + case SND_SOC_DAIFMT_CBC_CFC: > > + /* Only supports slave mode */ > > consumer mode. OK. > > > +static int fs210x_dai_hw_params(struct snd_pcm_substream *substream, > > + struct snd_pcm_hw_params *params, > > + struct snd_soc_dai *dai) > > > + dev_info(fs210x->dev, "hw params: %d-%d-%d\n", > > + fs210x->srate, chn_num, fs210x->bclk); > > No dev_info() prints in the normal playback/record flow, that just spams > the logs too easily. OK. > > > + /* The FS2105S can't support 16kHz sample rate. */ > > + if (fs210x->devid == FS2105S_DEVICE_ID && fs210x->srate == 16000) > > + return -ENOTSUPP; > > This should be reported in constraints too. OK. > > > + if (!(status & FS210X_05H_AMPS_MASK)) > > + dev_err(fs210x->dev, "Amplifier unready\n"); > > Does this get triggered during the normal start/stop flow? It will get triggered when: 1. BCLK clock is closed before stoping device 2. BCLK clock is opened after starting device We should avoid these power up/down sequence, it may cause pop noise, If they happens, it should be reported and fixed? > > > + schedule_delayed_work(&fs210x->fault_check_work, > > + msecs_to_jiffies(FS210X_FAULT_CHECK_INTERVAL_MS)); > > Might be good to have this tunable from sysfs. Good idea, or set the interval times by the DTS property. We are considering adding a DTS property: foursemi,monitor-period-ms > > > +static int fs210x_pcm_volume_put(struct snd_kcontrol *kcontrol, > > + struct snd_ctl_elem_value *ucontrol) > > +{ > > + struct fs210x_priv *fs210x; > > + long *pval; > > + int ret; > > + > > + ret = fs210x_get_drvdata_from_kctrl(kcontrol, &fs210x); > > + if (ret || !fs210x->dev) { > > + pr_err("pcm_volume_put: fs210x is null\n"); > > + return -EINVAL; > > + } > > + > > _put() callbacks should return 1 when the control changes values, though > as indicated above I'm not clear this needs to be a custom control at > all. Please run mixer-test against your driver, it will check for this > and other issues. OK, will fix it in next version. > > > +static int fs210x_effect_scene_put(struct snd_kcontrol *kcontrol, > > + struct snd_ctl_elem_value *ucontrol) > > +{ > > > + /* > > + * 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; > > + if (fs210x->is_suspended) { > > + fs210x->scene_id = scene_id; > > + mutex_unlock(&fs210x_mutex); > > + return 0; > > + } > > This doesn't validate the passed value at all. OK, I will fix it. > > > +static int fs210x_probe(struct snd_soc_component *cmpnt) > > +{ > > + struct fs210x_priv *fs210x; > > + int ret; > > > + INIT_DELAYED_WORK(&fs210x->fault_check_work, fs210x_fault_check_work); > > + INIT_DELAYED_WORK(&fs210x->start_work, fs210x_start_work); > > + > > + fs210x_get_bclk(fs210x, cmpnt); > > This sort of initialisation and resource acquisition that doesn't > require any audio stuff should be done at the bus level so that we don't > register with ASoC until all the inputs are ready. > OK, we will do these work in i2c probe > > +#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; > > + > > + cancel_delayed_work_sync(&fs210x->start_work); > > + cancel_delayed_work_sync(&fs210x->fault_check_work); > > + > > + mutex_lock(&fs210x_mutex); > > We don't need to prevent new work being scheduled? Could you please explain more details to help me understand and test this case? Thanks. Best regards, Nick ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v1 2/4] ASoC: codecs: Add FourSemi FS2104/5S audio amplifier driver 2025-07-04 11:12 ` Nick Li @ 2025-07-04 14:37 ` Mark Brown 2025-07-07 8:34 ` Nick Li 0 siblings, 1 reply; 37+ messages in thread From: Mark Brown @ 2025-07-04 14:37 UTC (permalink / raw) To: Nick Li Cc: lgirdwood, robh, krzk+dt, conor+dt, perex, tiwai, xiaoming.yang, danyang.zheng, linux-sound, devicetree, linux-kernel [-- Attachment #1: Type: text/plain, Size: 5488 bytes --] On Fri, Jul 04, 2025 at 07:12:47PM +0800, Nick Li wrote: > On Thu, Jul 03, 2025 at 03:59:34PM +0100, Mark Brown wrote: > > On Thu, Jul 03, 2025 at 11:56:37AM +0800, Nick wrote: > > > +static int fs210x_set_pcm_volume(struct fs210x_priv *fs210x) > > > + ret = fs210x_reg_write(fs210x, FS210X_39H_LVOLCTRL, vol[0]); > > > + ret |= fs210x_reg_write(fs210x, FS210X_3AH_RVOLCTRL, vol[1]); > > This looks pretty generic, why is it not using a standard control type? > 1. We use regmap as REGMAP_NONE, because most of the registers settings > are in the firmware, if we use a standard control,the driver shouldn't > cache the registers after suspending the device(it will be reset). You can exclude registers from a cache, including excluding most of them, or manually handle registers over suspend/resume. It's generally better to share the userspace facing interfaces. > 2. The volume registers of FS2104 and FS2105S are different, > if we us a stardard control, we need two controls, > and register it by checking the device type. > so we customize the volume control. If the different devices have different controls you should just register different controls. > > > + ret = fs210x_set_pcm_volume(fs210x); > > The driver should use the device defaults rather than having to > The volume contorl can be used to set different volumes, > the volume will be masked in fs210x->vol[2], > we restore the volume when the driver resumes(reinitializes) the deivce. You're not just restoring the values on resume, you're also overwriting them on probe. > > > +static void fs210x_sdz_pin_set(struct fs210x_priv *fs210x, bool active) > > > +{ > > > + if (!fs210x || !fs210x->gpio_sdz) > > > + return; > > Shouldn't this be integrated with the chip init/reset? > 1. We implement this function(reset and wait times) to clarify that > pulling up/down the SDZ/reset pin must to wait enougth delay time. That doesn't really answer the question? > > > + * According to the power up/down sequence of FS210x, > > > + * the FS210x requests the I2S clock has been present > > > + * and stable(>= 2ms) before it playing. > > > + */ > > > + if (fs210x->clk_bclk) { > > > + mutex_lock(&fs210x_mutex); > > > + ret = fs210x_dev_play(fs210x); > > > + mutex_unlock(&fs210x_mutex); > > > + } else { > > This is definitely not appropriate for mute, it should be in the power > > management flow - either set_bias_level() or a DAPM widget. > 1. Because the device uses BCLK clock as the clock source, > we need to start the device in the life cycle of the clock, > also we need to start device after the PLL setting(set in dai->hw_params) > so we start the device in here: dai->mute_stream(unmute) All the power management happens after hw_params(), this isn't an issue. > 2. If the SOC(s) doesn't have the clock(bclk) for us to configure, > It meams no clock bclk defined in the DTS, > and the clock is activated in dai->trigger start usually, > so we will use a delay work to start the device in here. > Any good ideas about satisfying this power up/down sequence? There's not great options here, and you're going to loose the start of playback especially with devices that don't start clocking until audio starts. You really need the CPU vendors you're working with to implement SND_SOC_DAIFMT_CONT or expose their clocks via the clock API but not all hardware is able to do this. I think given how limited your hardware is here you really need something in trigger() or some new callback that runs later than that, the delayed work you've got there is trying to fudge things to run after trigger. > > > + if (!(status & FS210X_05H_AMPS_MASK)) > > > + dev_err(fs210x->dev, "Amplifier unready\n"); > > Does this get triggered during the normal start/stop flow? > It will get triggered when: > 1. BCLK clock is closed before stoping device > 2. BCLK clock is opened after starting device > We should avoid these power up/down sequence, it may cause pop noise, > If they happens, it should be reported and fixed? If they don't happen in normal operation it's fine to have them, the concern was that this would be triggered during normal operation as part of the startup or shutdown sequence. > > > + schedule_delayed_work(&fs210x->fault_check_work, > > > + msecs_to_jiffies(FS210X_FAULT_CHECK_INTERVAL_MS)); > > Might be good to have this tunable from sysfs. > Good idea, or set the interval times by the DTS property. > We are considering adding a DTS property: > foursemi,monitor-period-ms I suspect the DT people won't like that since it's more of a tuning thing. > > > +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; > > > + cancel_delayed_work_sync(&fs210x->start_work); > > > + cancel_delayed_work_sync(&fs210x->fault_check_work); > > > + mutex_lock(&fs210x_mutex); > > We don't need to prevent new work being scheduled? > Could you please explain more details to help me understand and test this case? What if for example playback is starting up at the same time as the system enters suspend - the CODEC startup might get run after the delayed work is cancelled but before the lock is taken. [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 488 bytes --] ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v1 2/4] ASoC: codecs: Add FourSemi FS2104/5S audio amplifier driver 2025-07-04 14:37 ` Mark Brown @ 2025-07-07 8:34 ` Nick Li 2025-07-07 20:42 ` Mark Brown 0 siblings, 1 reply; 37+ messages in thread From: Nick Li @ 2025-07-07 8:34 UTC (permalink / raw) To: Mark Brown Cc: lgirdwood, robh, krzk+dt, conor+dt, perex, tiwai, xiaoming.yang, danyang.zheng, linux-sound, devicetree, linux-kernel On Fri, Jul 04, 2025 at 03:37:32PM +0100, Mark Brown wrote: > On Fri, Jul 04, 2025 at 07:12:47PM +0800, Nick Li wrote: > > On Thu, Jul 03, 2025 at 03:59:34PM +0100, Mark Brown wrote: > > > On Thu, Jul 03, 2025 at 11:56:37AM +0800, Nick wrote: > > > > > +static int fs210x_set_pcm_volume(struct fs210x_priv *fs210x) > > > > > + ret = fs210x_reg_write(fs210x, FS210X_39H_LVOLCTRL, vol[0]); > > > > + ret |= fs210x_reg_write(fs210x, FS210X_3AH_RVOLCTRL, vol[1]); > > > > This looks pretty generic, why is it not using a standard control type? > > > 1. We use regmap as REGMAP_NONE, because most of the registers settings > > are in the firmware, if we use a standard control,the driver shouldn't > > cache the registers after suspending the device(it will be reset). > > You can exclude registers from a cache, including excluding most of > them, or manually handle registers over suspend/resume. It's generally > better to share the userspace facing interfaces. OK, we will update the volume control to: SOC_DOUBLE_TLV(...) and use the regmap to cache the volumes. > > > 2. The volume registers of FS2104 and FS2105S are different, > > if we us a stardard control, we need two controls, > > and register it by checking the device type. > > so we customize the volume control. > > If the different devices have different controls you should just > register different controls. OK. > > > > > + ret = fs210x_set_pcm_volume(fs210x); > > > > The driver should use the device defaults rather than having to > > > The volume contorl can be used to set different volumes, > > the volume will be masked in fs210x->vol[2], > > we restore the volume when the driver resumes(reinitializes) the deivce. > > You're not just restoring the values on resume, you're also overwriting > them on probe. Yes, the volume will be set to the default value at the first time of initialization on probe, and it may be updated by volume control later. It's a good way to use the regmap cache(REGCACHE_RBTREE) to cache the volumes. > > > > > +static void fs210x_sdz_pin_set(struct fs210x_priv *fs210x, bool active) > > > > +{ > > > > + if (!fs210x || !fs210x->gpio_sdz) > > > > + return; > > > > Shouldn't this be integrated with the chip init/reset? > > > 1. We implement this function(reset and wait times) to clarify that > > pulling up/down the SDZ/reset pin must to wait enougth delay time. > > That doesn't really answer the question? It's ok to integrate it into the chip init/reset. > > > > > + * According to the power up/down sequence of FS210x, > > > > + * the FS210x requests the I2S clock has been present > > > > + * and stable(>= 2ms) before it playing. > > > > + */ > > > > + if (fs210x->clk_bclk) { > > > > + mutex_lock(&fs210x_mutex); > > > > + ret = fs210x_dev_play(fs210x); > > > > + mutex_unlock(&fs210x_mutex); > > > > + } else { > > > > This is definitely not appropriate for mute, it should be in the power > > > management flow - either set_bias_level() or a DAPM widget. > > > 1. Because the device uses BCLK clock as the clock source, > > we need to start the device in the life cycle of the clock, > > also we need to start device after the PLL setting(set in dai->hw_params) > > so we start the device in here: dai->mute_stream(unmute) > > All the power management happens after hw_params(), this isn't an issue. We will evaluate the way to reduce the cost time of starting/stoping device: Do PM by DAPM widget or set_bias_on. > > > 2. If the SOC(s) doesn't have the clock(bclk) for us to configure, > > It meams no clock bclk defined in the DTS, > > and the clock is activated in dai->trigger start usually, > > so we will use a delay work to start the device in here. > > > Any good ideas about satisfying this power up/down sequence? > > There's not great options here, and you're going to loose the start of > playback especially with devices that don't start clocking until audio > starts. You really need the CPU vendors you're working with to > implement SND_SOC_DAIFMT_CONT or expose their clocks via the clock API > but not all hardware is able to do this. I think given how limited your > hardware is here you really need something in trigger() or some new > callback that runs later than that, the delayed work you've got there is > trying to fudge things to run after trigger. > We will start the start_work in dai->trigger if there is no clock bclk for us to control, and disable fading in/out in firmware to reduce the time consumption if it is needed(As a backup plan). > > > > + if (!(status & FS210X_05H_AMPS_MASK)) > > > > + dev_err(fs210x->dev, "Amplifier unready\n"); > > > > Does this get triggered during the normal start/stop flow? > > > It will get triggered when: > > 1. BCLK clock is closed before stoping device > > 2. BCLK clock is opened after starting device > > We should avoid these power up/down sequence, it may cause pop noise, > > If they happens, it should be reported and fixed? > > If they don't happen in normal operation it's fine to have them, the > concern was that this would be triggered during normal operation as part > of the startup or shutdown sequence. We can remove the registers dumping, and set the AMPS status to dev_dbg level. > > > > > + schedule_delayed_work(&fs210x->fault_check_work, > > > > + msecs_to_jiffies(FS210X_FAULT_CHECK_INTERVAL_MS)); > > > > Might be good to have this tunable from sysfs. > > > Good idea, or set the interval times by the DTS property. > > We are considering adding a DTS property: > > foursemi,monitor-period-ms > > I suspect the DT people won't like that since it's more of a tuning > thing. OK, we add a sysfs node for this. > > > > > +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; > > > > > + cancel_delayed_work_sync(&fs210x->start_work); > > > > + cancel_delayed_work_sync(&fs210x->fault_check_work); > > > > > + mutex_lock(&fs210x_mutex); > > > > We don't need to prevent new work being scheduled? > > > Could you please explain more details to help me understand and test this case? > > What if for example playback is starting up at the same time as the > system enters suspend - the CODEC startup might get run after the > delayed work is cancelled but before the lock is taken. Understood, thanks a lot. We evaluate to remove the resume/suspend(cold start up/down device): The power consumptions between register settings and pins controlling are very close, and it can help us to reduce the time consumption. Best regards, Nick ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v1 2/4] ASoC: codecs: Add FourSemi FS2104/5S audio amplifier driver 2025-07-07 8:34 ` Nick Li @ 2025-07-07 20:42 ` Mark Brown 0 siblings, 0 replies; 37+ messages in thread From: Mark Brown @ 2025-07-07 20:42 UTC (permalink / raw) To: Nick Li Cc: lgirdwood, robh, krzk+dt, conor+dt, perex, tiwai, xiaoming.yang, danyang.zheng, linux-sound, devicetree, linux-kernel [-- Attachment #1: Type: text/plain, Size: 1876 bytes --] On Mon, Jul 07, 2025 at 04:34:03PM +0800, Nick Li wrote: > On Fri, Jul 04, 2025 at 03:37:32PM +0100, Mark Brown wrote: > > On Fri, Jul 04, 2025 at 07:12:47PM +0800, Nick Li wrote: > > > On Thu, Jul 03, 2025 at 03:59:34PM +0100, Mark Brown wrote: > > > > On Thu, Jul 03, 2025 at 11:56:37AM +0800, Nick wrote: > > > the volume will be masked in fs210x->vol[2], > > > we restore the volume when the driver resumes(reinitializes) the deivce. > > You're not just restoring the values on resume, you're also overwriting > > them on probe. > Yes, the volume will be set to the default value at the first time of initialization on probe, > and it may be updated by volume control later. > It's a good way to use the regmap cache(REGCACHE_RBTREE) to cache the volumes. Great. Note that for new things you should generally use _MAPLE instead, it's a more modern data structure - _RBTREE can still make sense for some specialist use cases but this is unlikely to be one of them. > > There's not great options here, and you're going to loose the start of > > playback especially with devices that don't start clocking until audio > > starts. You really need the CPU vendors you're working with to > > implement SND_SOC_DAIFMT_CONT or expose their clocks via the clock API > > but not all hardware is able to do this. I think given how limited your > > hardware is here you really need something in trigger() or some new > > callback that runs later than that, the delayed work you've got there is > > trying to fudge things to run after trigger. > We will start the start_work in dai->trigger if there is no clock bclk > for us to control, and disable fading in/out in firmware to reduce > the time consumption if it is needed(As a backup plan). Great. You could also make the fade in/out user selectable so the application can trade off between pops/clicks and cut off audio. [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 488 bytes --] ^ permalink raw reply [flat|nested] 37+ messages in thread
* [PATCH v1 3/4] ASoC: dt-bindings: Add dt bindings for FS2104/5S audio amplifiers 2025-07-03 3:56 [PATCH v1 0/4] ASoC: codecs: Add support for FourSemi FS2104/5S Nick 2025-07-03 3:56 ` [PATCH v1 1/4] ASoC: codecs: Add library for FourSemi audio amplifiers Nick 2025-07-03 3:56 ` [PATCH v1 2/4] ASoC: codecs: Add FourSemi FS2104/5S audio amplifier driver Nick @ 2025-07-03 3:56 ` Nick 2025-07-03 5:48 ` Rob Herring (Arm) ` (2 more replies) 2025-07-03 3:56 ` [PATCH v1 4/4] dt-bindings: vendor-prefixes: Add Shanghai FourSemi Semiconductor Co.,Ltd Nick 2025-07-08 11:28 ` [PATCH v2 0/4] ASoC: codecs: Add support for FourSemi FS2104/5S Nick Li 4 siblings, 3 replies; 37+ messages in thread From: Nick @ 2025-07-03 3:56 UTC (permalink / raw) To: lgirdwood, broonie, robh, krzk+dt, conor+dt, perex, tiwai, nick.li Cc: like.sin, xiaoming.yang, danyang.zheng, linux-sound, devicetree, linux-kernel From: Nick Li <nick.li@foursemi.com> This patch adds bindings for FourSemi FS2104/5S audio amplifiers which can support both I2S and I2C interface. Signed-off-by: Nick Li <nick.li@foursemi.com> --- .../bindings/sound/foursemi,fs210x.yaml | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/foursemi,fs210x.yaml diff --git a/Documentation/devicetree/bindings/sound/foursemi,fs210x.yaml b/Documentation/devicetree/bindings/sound/foursemi,fs210x.yaml new file mode 100644 index 000000000..3a1aba9b8 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/foursemi,fs210x.yaml @@ -0,0 +1,95 @@ +# SPDX-License-Identifier: GPL-2.0-only +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/foursemi,fs210x.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: FourSemi FS2104/5S Digital Audio Amplifier + +maintainers: + - Nick <nick.li@foursemi.com> + +description: | + The FS2104 is a 15W Inductor-Less, Stereo, Closed-Loop, + Digital Input Class-D Power Amplifier with Enhanced Signal Processing. + The FS2105S is a 30W Inductor-Less, Stereo, Closed-Loop, + Digital Input Class-D Power Amplifier with Enhanced Signal Processing. + +properties: + compatible: + enum: + - foursemi,fs2104 + - foursemi,fs2105s + + reg: + maxItems: 1 + description: | + I2C address of the amplifier. Refer to datasheet for possible values: + 0x68/0x69/0x6A/0x6B + + clocks: + items: + - description: The clock of I2S BCLK + + clock-names: + items: + - const: bclk + + '#sound-dai-cells': + const: 0 + + pvdd-supply: + description: | + Regulator for power supply(PVDD in datasheet). + + dvdd-supply: + description: | + Regulator for digital supply(DVDD in datasheet). + + sdz-gpios: + maxItems: 1 + description: | + SDZ(Shut Down) pin is active low, it will power down the chip, + and then reset the chip to shut down state. + + fs,fwm-name: + description: | + The name of firmware that should be loaded for this + instance. The firmware is gernerated by FourSemi's tuning tool. + $ref: /schemas/types.yaml#/definitions/string + + fs,dai-name: + description: | + The name of FS210x DAI, it can be set different name when + there are multiple FS210x devices in the system. + $ref: /schemas/types.yaml#/definitions/string + +required: + - compatible + - reg + - sdz-gpios + - fs,fwm-name + - '#sound-dai-cells' + +additionalProperties: false + +examples: + - | + #include <dt-bindings/gpio/gpio.h> + i2c { + #address-cells = <1>; + #size-cells = <0>; + audio-codec@68 { + compatible = "foursemi,fs2105s"; + reg = <0x68>; + pvdd-supply = <&pvdd_supply>; + dvdd-supply = <&dvdd_supply>; + sdz-gpios = <&gpio 18 GPIO_ACTIVE_LOW>; + pinctrl-names = "default"; + pinctrl-0 = <&fs210x_pins_default>; + fs,fwm-name = "fs2105s-btl-2p0-0s.bin"; + clocks = <&clocks 18>; + clock-names = "bclk"; + #sound-dai-cells = <0>; + }; + }; -- 2.39.5 ^ permalink raw reply related [flat|nested] 37+ messages in thread
* Re: [PATCH v1 3/4] ASoC: dt-bindings: Add dt bindings for FS2104/5S audio amplifiers 2025-07-03 3:56 ` [PATCH v1 3/4] ASoC: dt-bindings: Add dt bindings for FS2104/5S audio amplifiers Nick @ 2025-07-03 5:48 ` Rob Herring (Arm) 2025-07-03 6:36 ` Krzysztof Kozlowski 2025-07-03 7:10 ` Krzysztof Kozlowski 2 siblings, 0 replies; 37+ messages in thread From: Rob Herring (Arm) @ 2025-07-03 5:48 UTC (permalink / raw) To: Nick Cc: conor+dt, tiwai, xiaoming.yang, danyang.zheng, devicetree, linux-kernel, linux-sound, lgirdwood, like.sin, perex, broonie, krzk+dt On Thu, 03 Jul 2025 11:56:38 +0800, Nick wrote: > From: Nick Li <nick.li@foursemi.com> > > This patch adds bindings for FourSemi FS2104/5S audio amplifiers > which can support both I2S and I2C interface. > > Signed-off-by: Nick Li <nick.li@foursemi.com> > --- > .../bindings/sound/foursemi,fs210x.yaml | 95 +++++++++++++++++++ > 1 file changed, 95 insertions(+) > create mode 100644 Documentation/devicetree/bindings/sound/foursemi,fs210x.yaml > My bot found errors running 'make dt_binding_check' on your patch: yamllint warnings/errors: dtschema/dtc warnings/errors: /builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/sound/foursemi,fs210x.example.dtb: audio-codec@68 (foursemi,fs2105s): 'fs,fwm-name' does not match any of the regexes: '^#.*', '^(at25|bm|devbus|dmacap|dsa|exynos|fsi[ab]|gpio-fan|gpio-key|gpio|gpmc|hdmi|i2c-gpio),.*', '^(keypad|m25p|max8952|max8997|max8998|mpmc),.*', '^(pciclass|pinctrl-single|#pinctrl-single|PowerPC),.*', '^(pl022|pxa-mmc|rcar_sound|rotary-encoder|s5m8767|sdhci),.*', '^(simple-audio-card|st-plgpio|st-spics|ts),.*', '^100ask,.*', '^70mai,.*', '^8dev,.*', '^GEFanuc,.*', '^IBM,.*', '^ORCL,.*', '^SUNW,.*', '^[a-zA-Z0-9#_][a-zA-Z0-9+\\-._@]{0,63}$', '^[a-zA-Z0-9+\\-._]*@[0-9a-zA-Z,]*$', '^abb,.*', '^abilis,.*', '^abracon,.*', '^abt,.*', '^acbel,.*', '^acelink,.*', '^acer,.*', '^acme,.*', '^actions,.*', '^active-semi,.*', '^ad,.*', '^adafruit,.*', '^adapteva,.*', '^adaptrum,.*', '^adh,.*', '^adi,.*', '^adieng,.*', '^admatec,.*', '^advantech,.*', '^aeroflexgaisler,.*', '^aesop,.*', '^airoha,.*', '^al,.*', '^alcatel,.*', '^aldec,.*', '^alfa-network,.*', '^allegro,.*', '^allegromicro,.*', '^alliedvision,.*', '^allo,.*', '^allwinner,.*', '^alphascale,.*', '^alps,.*', '^alt,.*', '^altr,.*', '^amarula,.*', '^amazon,.*', '^amcc,.*', '^amd,.*', '^amediatech,.*', '^amlogic,.*', '^ampere,.*', '^amphenol,.*', '^ampire,.*', '^ams,.*', '^amstaos,.*', '^analogix,.*', '^anbernic,.*', '^andestech,.*', '^anvo,.*', '^aoly,.*', '^aosong,.*', '^apm,.*', '^apple,.*', '^aptina,.*', '^arasan,.*', '^archermind,.*', '^arcom,.*', '^arctic,.*', '^arcx,.*', '^argon40,.*', '^ariaboard,.*', '^aries,.*', '^arm,.*', '^armadeus,.*', '^armsom,.*', '^arrow,.*', '^artesyn,.*', '^asahi-kasei,.*', '^asc,.*', '^asix,.*', '^aspeed,.*', '^asrock,.*', '^asteralabs,.*', '^asus,.*', '^atheros,.*', '^atlas,.*', '^atmel,.*', '^auo,.*', '^auvidea,.*', '^avago,.*', '^avia,.*', '^avic,.*', '^avnet,.*', '^awinic,.*', '^axentia,.*', '^axis,.*', '^azoteq,.*', '^azw,.*', '^baikal,.*', '^bananapi,.*', '^beacon,.*', '^beagle,.*', '^belling,.*', '^bhf,.*', '^bigtreetech,.*', '^bitmain,.*', '^blaize,.*', '^blutek,.*', '^boe,.*', '^bosch,.*', '^boundary,.*', '^brcm,.*', '^broadmobi,.*', '^bsh,.*', '^bticino,.*', '^buffalo,.*', '^bur,.*', '^bytedance,.*', '^calamp,.*', '^calao,.*', '^calaosystems,.*', '^calxeda,.*', '^cameo,.*', '^canaan,.*', '^caninos,.*', '^capella,.*', '^cascoda,.*', '^catalyst,.*', '^cavium,.*', '^cct,.*', '^cdns,.*', '^cdtech,.*', '^cellwise,.*', '^ceva,.*', '^chargebyte,.*', '^checkpoint,.*', '^chefree,.*', '^chipidea,.*', '^chipone,.*', '^chipspark,.*', '^chongzhou,.*', '^chrontel,.*', '^chrp,.*', '^chunghwa,.*', '^chuwi,.*', '^ciaa,.*', '^cirrus,.*', '^cisco,.*', '^clockwork,.*', '^cloos,.*', '^cloudengines,.*', '^cnm,.*', '^cnxt,.*', '^colorfly,.*', '^compulab,.*', '^comvetia,.*', '^congatec,.*', '^coolpi,.*', '^coreriver,.*', '^corpro,.*', '^cortina,.*', '^cosmic,.*', '^crane,.*', '^creative,.*', '^crystalfontz,.*', '^csky,.*', '^csot,.*', '^csq,.*', '^ctera,.*', '^ctu,.*', '^cubietech,.*', '^cudy,.*', '^cui,.*', '^cypress,.*', '^cyx,.*', '^cznic,.*', '^dallas,.*', '^dataimage,.*', '^davicom,.*', '^deepcomputing,.*', '^dell,.*', '^delta,.*', '^densitron,.*', '^denx,.*', '^devantech,.*', '^dfi,.*', '^dfrobot,.*', '^dh,.*', '^difrnce,.*', '^digi,.*', '^digilent,.*', '^dimonoff,.*', '^diodes,.*', '^dioo,.*', '^djn,.*', '^dlc,.*', '^dlg,.*', '^dlink,.*', '^dmo,.*', '^domintech,.*', '^dongwoon,.*', '^dptechnics,.*', '^dragino,.*', '^dream,.*', '^ds,.*', '^dserve,.*', '^dynaimage,.*', '^ea,.*', '^ebang,.*', '^ebbg,.*', '^ebs-systart,.*', '^ebv,.*', '^eckelmann,.*', '^econet,.*', '^edgeble,.*', '^edimax,.*', '^edt,.*', '^ees,.*', '^eeti,.*', '^einfochips,.*', '^eink,.*', '^elan,.*', '^element14,.*', '^elgin,.*', '^elida,.*', '^elimo,.*', '^elpida,.*', '^embedfire,.*', '^embest,.*', '^emcraft,.*', '^emlid,.*', '^emmicro,.*', '^empire-electronix,.*', '^emtrion,.*', '^enclustra,.*', '^endless,.*', '^ene,.*', '^energymicro,.*', '^engicam,.*', '^engleder,.*', '^epcos,.*', '^epfl,.*', '^epson,.*', '^esp,.*', '^est,.*', '^ettus,.*', '^eukrea,.*', '^everest,.*', '^everspin,.*', '^evervision,.*', '^exar,.*', '^excito,.*', '^exegin,.*', '^ezchip,.*', '^facebook,.*', '^fairchild,.*', '^fairphone,.*', '^faraday,.*', '^fascontek,.*', '^fastrax,.*', '^fcs,.*', '^feixin,.*', '^feiyang,.*', '^fii,.*', '^firefly,.*', '^focaltech,.*', '^forlinx,.*', '^freebox,.*', '^freecom,.*', '^frida,.*', '^friendlyarm,.*', '^fsl,.*', '^fujitsu,.*', '^fxtec,.*', '^galaxycore,.*', '^gameforce,.*', '^gardena,.*', '^gateway,.*', '^gateworks,.*', '^gcw,.*', '^ge,.*', '^geekbuying,.*', '^gef,.*', '^gehc,.*', '^gemei,.*', '^gemtek,.*', '^genesys,.*', '^genexis,.*', '^geniatech,.*', '^giantec,.*', '^giantplus,.*', '^glinet,.*', '^globalscale,.*', '^globaltop,.*', '^gmt,.*', '^gocontroll,.*', '^goldelico,.*', '^goodix,.*', '^google,.*', '^goramo,.*', '^gplus,.*', '^grinn,.*', '^grmn,.*', '^gumstix,.*', '^gw,.*', '^hannstar,.*', '^haochuangyi,.*', '^haoyu,.*', '^hardkernel,.*', '^hechuang,.*', '^hideep,.*', '^himax,.*', '^hirschmann,.*', '^hisi,.*', '^hisilicon,.*', '^hit,.*', '^hitex,.*', '^holt,.*', '^holtek,.*', '^honestar,.*', '^honeywell,.*', '^hoperf,.*', '^hoperun,.*', '^hp,.*', '^hpe,.*', '^hsg,.*', '^htc,.*', '^huawei,.*', '^hugsun,.*', '^huiling,.*', '^hwacom,.*', '^hxt,.*', '^hycon,.*', '^hydis,.*', '^hynitron,.*', '^hynix,.*', '^hyundai,.*', '^i2se,.*', '^ibm,.*', '^icplus,.*', '^idt,.*', '^iei,.*', '^ifi,.*', '^ilitek,.*', '^imagis,.*', '^img,.*', '^imi,.*', '^inanbo,.*', '^incircuit,.*', '^indiedroid,.*', '^inet-tek,.*', '^infineon,.*', '^inforce,.*', '^ingenic,.*', '^ingrasys,.*', '^injoinic,.*', '^innocomm,.*', '^innolux,.*', '^inside-secure,.*', '^insignal,.*', '^inspur,.*', '^intel,.*', '^intercontrol,.*', '^invensense,.*', '^inventec,.*', '^inversepath,.*', '^iom,.*', '^irondevice,.*', '^isee,.*', '^isil,.*', '^issi,.*', '^ite,.*', '^itead,.*', '^itian,.*', '^ivo,.*', '^iwave,.*', '^jadard,.*', '^jasonic,.*', '^jdi,.*', '^jedec,.*', '^jenson,.*', '^jesurun,.*', '^jethome,.*', '^jianda,.*', '^jide,.*', '^joz,.*', '^kam,.*', '^karo,.*', '^keithkoep,.*', '^keymile,.*', '^khadas,.*', '^kiebackpeter,.*', '^kinetic,.*', '^kingdisplay,.*', '^kingnovel,.*', '^kionix,.*', '^kobo,.*', '^kobol,.*', '^koe,.*', '^kontron,.*', '^kosagi,.*', '^kvg,.*', '^kyo,.*', '^lacie,.*', '^laird,.*', '^lamobo,.*', '^lantiq,.*', '^lattice,.*', '^lckfb,.*', '^lctech,.*', '^leadtek,.*', '^leez,.*', '^lego,.*', '^lemaker,.*', '^lenovo,.*', '^lg,.*', '^lgphilips,.*', '^libretech,.*', '^licheepi,.*', '^linaro,.*', '^lincolntech,.*', '^lineartechnology,.*', '^linksprite,.*', '^linksys,.*', '^linutronix,.*', '^linux,.*', '^linx,.*', '^liontron,.*', '^liteon,.*', '^litex,.*', '^lltc,.*', '^logicpd,.*', '^logictechno,.*', '^longcheer,.*', '^lontium,.*', '^loongmasses,.*', '^loongson,.*', '^lsi,.*', '^luckfox,.*', '^lunzn,.*', '^luxul,.*', '^lwn,.*', '^lxa,.*', '^m5stack,.*', '^macnica,.*', '^mantix,.*', '^mapleboard,.*', '^marantec,.*', '^marvell,.*', '^maxbotix,.*', '^maxim,.*', '^maxlinear,.*', '^mbvl,.*', '^mcube,.*', '^meas,.*', '^mecer,.*', '^mediatek,.*', '^megachips,.*', '^mele,.*', '^melexis,.*', '^melfas,.*', '^mellanox,.*', '^memsensing,.*', '^memsic,.*', '^menlo,.*', '^mentor,.*', '^meraki,.*', '^merrii,.*', '^methode,.*', '^micrel,.*', '^microchip,.*', '^microcrystal,.*', '^micron,.*', '^microsoft,.*', '^microsys,.*', '^microtips,.*', '^mikroe,.*', '^mikrotik,.*', '^milkv,.*', '^miniand,.*', '^minix,.*', '^mips,.*', '^miramems,.*', '^mitsubishi,.*', '^mitsumi,.*', '^mixel,.*', '^miyoo,.*', '^mntre,.*', '^mobileye,.*', '^modtronix,.*', '^moortec,.*', '^mosaixtech,.*', '^motorcomm,.*', '^motorola,.*', '^moxa,.*', '^mpl,.*', '^mps,.*', '^mqmaker,.*', '^mrvl,.*', '^mscc,.*', '^msi,.*', '^mstar,.*', '^mti,.*', '^multi-inno,.*', '^mundoreader,.*', '^murata,.*', '^mxic,.*', '^mxicy,.*', '^myir,.*', '^national,.*', '^neardi,.*', '^nec,.*', '^neofidelity,.*', '^neonode,.*', '^netcube,.*', '^netgear,.*', '^netlogic,.*', '^netron-dy,.*', '^netronix,.*', '^netxeon,.*', '^neweast,.*', '^newhaven,.*', '^newvision,.*', '^nexbox,.*', '^nextthing,.*', '^ni,.*', '^nintendo,.*', '^nlt,.*', '^nokia,.*', '^nordic,.*', '^nothing,.*', '^novatek,.*', '^novtech,.*', '^numonyx,.*', '^nutsboard,.*', '^nuvoton,.*', '^nvd,.*', '^nvidia,.*', '^nxp,.*', '^oceanic,.*', '^ocs,.*', '^oct,.*', '^okaya,.*', '^oki,.*', '^olimex,.*', '^olpc,.*', '^oneplus,.*', '^onie,.*', '^onion,.*', '^onnn,.*', '^ontat,.*', '^opalkelly,.*', '^openailab,.*', '^opencores,.*', '^openembed,.*', '^openpandora,.*', '^openrisc,.*', '^openwrt,.*', '^option,.*', '^oranth,.*', '^orisetech,.*', '^ortustech,.*', '^osddisplays,.*', '^osmc,.*', '^ouya,.*', '^overkiz,.*', '^ovti,.*', '^oxsemi,.*', '^ozzmaker,.*', '^panasonic,.*', '^parade,.*', '^parallax,.*', '^pda,.*', '^pegatron,.*', '^pericom,.*', '^pervasive,.*', '^phicomm,.*', '^phytec,.*', '^picochip,.*', '^pinctrl-[0-9]+$', '^pine64,.*', '^pineriver,.*', '^pixcir,.*', '^plantower,.*', '^plathome,.*', '^plda,.*', '^plx,.*', '^ply,.*', '^pni,.*', '^pocketbook,.*', '^polaroid,.*', '^polyhex,.*', '^portwell,.*', '^poslab,.*', '^pov,.*', '^powertip,.*', '^powervr,.*', '^powkiddy,.*', '^pri,.*', '^primeview,.*', '^primux,.*', '^probox2,.*', '^prt,.*', '^pulsedlight,.*', '^purism,.*', '^puya,.*', '^qca,.*', '^qcom,.*', '^qemu,.*', '^qi,.*', '^qiaodian,.*', '^qihua,.*', '^qishenglong,.*', '^qnap,.*', '^quanta,.*', '^radxa,.*', '^raidsonic,.*', '^ralink,.*', '^ramtron,.*', '^raspberrypi,.*', '^raydium,.*', '^rda,.*', '^realtek,.*', '^relfor,.*', '^remarkable,.*', '^renesas,.*', '^rervision,.*', '^retronix,.*', '^revotics,.*', '^rex,.*', '^richtek,.*', '^ricoh,.*', '^rikomagic,.*', '^riot,.*', '^riscv,.*', '^rockchip,.*', '^rocktech,.*', '^rohm,.*', '^ronbo,.*', '^roofull,.*', '^roseapplepi,.*', '^rve,.*', '^saef,.*', '^sakurapi,.*', '^samsung,.*', '^samtec,.*', '^sancloud,.*', '^sandisk,.*', '^satoz,.*', '^sbs,.*', '^schindler,.*', '^schneider,.*', '^sciosense,.*', '^seagate,.*', '^seeed,.*', '^seirobotics,.*', '^semtech,.*', '^senseair,.*', '^sensirion,.*', '^sensortek,.*', '^sercomm,.*', '^sff,.*', '^sgd,.*', '^sgmicro,.*', '^sgx,.*', '^sharp,.*', '^shift,.*', '^shimafuji,.*', '^shineworld,.*', '^shiratech,.*', '^si-en,.*', '^si-linux,.*', '^siemens,.*', '^sifive,.*', '^siflower,.*', '^sigma,.*', '^sii,.*', '^sil,.*', '^silabs,.*', '^silan,.*', '^silead,.*', '^silergy,.*', '^silex-insight,.*', '^siliconfile,.*', '^siliconmitus,.*', '^silvaco,.*', '^simtek,.*', '^sinlinx,.*', '^sinovoip,.*', '^sinowealth,.*', '^sipeed,.*', '^sirf,.*', '^sis,.*', '^sitronix,.*', '^skov,.*', '^skyworks,.*', '^smartfiber,.*', '^smartlabs,.*', '^smartrg,.*', '^smi,.*', '^smsc,.*', '^snps,.*', '^sochip,.*', '^socionext,.*', '^solidrun,.*', '^solomon,.*', '^sony,.*', '^sophgo,.*', '^sourceparts,.*', '^spacemit,.*', '^spansion,.*', '^sparkfun,.*', '^spinalhdl,.*', '^sprd,.*', '^square,.*', '^ssi,.*', '^sst,.*', '^sstar,.*', '^st,.*', '^st-ericsson,.*', '^starfive,.*', '^starry,.*', '^startek,.*', '^starterkit,.*', '^ste,.*', '^stericsson,.*', '^storlink,.*', '^storm,.*', '^storopack,.*', '^summit,.*', '^sunchip,.*', '^sundance,.*', '^sunplus,.*', '^supermicro,.*', '^swir,.*', '^syna,.*', '^synology,.*', '^synopsys,.*', '^tbs,.*', '^tbs-biometrics,.*', '^tcg,.*', '^tcl,.*', '^tcs,.*', '^tcu,.*', '^tdo,.*', '^team-source-display,.*', '^technexion,.*', '^technologic,.*', '^techstar,.*', '^techwell,.*', '^teejet,.*', '^teltonika,.*', '^tempo,.*', '^terasic,.*', '^tesla,.*', '^test,.*', '^tfc,.*', '^thead,.*', '^thine,.*', '^thingyjp,.*', '^thundercomm,.*', '^thwc,.*', '^ti,.*', '^tianma,.*', '^tlm,.*', '^tmt,.*', '^topeet,.*', '^topic,.*', '^topland,.*', '^toppoly,.*', '^topwise,.*', '^toradex,.*', '^toshiba,.*', '^toumaz,.*', '^tpk,.*', '^tplink,.*', '^tpo,.*', '^tq,.*', '^transpeed,.*', '^traverse,.*', '^tronfy,.*', '^tronsmart,.*', '^truly,.*', '^tsd,.*', '^turing,.*', '^tyan,.*', '^tyhx,.*', '^u-blox,.*', '^u-boot,.*', '^ubnt,.*', '^ucrobotics,.*', '^udoo,.*', '^ufispace,.*', '^ugoos,.*', '^ultratronik,.*', '^uni-t,.*', '^uniwest,.*', '^upisemi,.*', '^urt,.*', '^usi,.*', '^usr,.*', '^utoo,.*', '^v3,.*', '^vaisala,.*', '^vamrs,.*', '^variscite,.*', '^vdl,.*', '^vertexcom,.*', '^via,.*', '^vialab,.*', '^vicor,.*', '^videostrong,.*', '^virtio,.*', '^virtual,.*', '^vishay,.*', '^visionox,.*', '^vitesse,.*', '^vivante,.*', '^vivax,.*', '^vocore,.*', '^voipac,.*', '^voltafield,.*', '^vot,.*', '^vscom,.*', '^vxt,.*', '^wacom,.*', '^wanchanglong,.*', '^wand,.*', '^waveshare,.*', '^wd,.*', '^we,.*', '^welltech,.*', '^wetek,.*', '^wexler,.*', '^whwave,.*', '^wi2wi,.*', '^widora,.*', '^wiligear,.*', '^willsemi,.*', '^winbond,.*', '^wingtech,.*', '^winlink,.*', '^winsen,.*', '^winstar,.*', '^wirelesstag,.*', '^wits,.*', '^wlf,.*', '^wm,.*', '^wobo,.*', '^wolfvision,.*', '^x-powers,.*', '^xen,.*', '^xes,.*', '^xiaomi,.*', '^xillybus,.*', '^xingbangda,.*', '^xinpeng,.*', '^xiphera,.*', '^xlnx,.*', '^xnano,.*', '^xunlong,.*', '^xylon,.*', '^yadro,.*', '^yamaha,.*', '^yes-optoelectronics,.*', '^yic,.*', '^yiming,.*', '^ylm,.*', '^yna,.*', '^yones-toptech,.*', '^ys,.*', '^ysoft,.*', '^yuridenki,.*', '^yuzukihd,.*', '^zarlink,.*', '^zealz,.*', '^zeitec,.*', '^zidoo,.*', '^zii,.*', '^zinitix,.*', '^zkmagic,.*', '^zte,.*', '^zyxel,.*' from schema $id: http://devicetree.org/schemas/vendor-prefixes.yaml# doc reference errors (make refcheckdocs): See https://patchwork.ozlabs.org/project/devicetree-bindings/patch/20250703035639.7252-4-nick.li@foursemi.com The base for the series is generally the latest rc1. A different dependency should be noted in *this* patch. If you already ran 'make dt_binding_check' and didn't see the above error(s), then make sure 'yamllint' is installed and dt-schema is up to date: pip3 install dtschema --upgrade Please check and re-submit after running the above command yourself. Note that DT_SCHEMA_FILES can be set to your schema file to speed up checking your schema. However, it must be unset to test all examples with your schema. ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v1 3/4] ASoC: dt-bindings: Add dt bindings for FS2104/5S audio amplifiers 2025-07-03 3:56 ` [PATCH v1 3/4] ASoC: dt-bindings: Add dt bindings for FS2104/5S audio amplifiers Nick 2025-07-03 5:48 ` Rob Herring (Arm) @ 2025-07-03 6:36 ` Krzysztof Kozlowski 2025-07-04 6:00 ` Nick Li 2025-07-03 7:10 ` Krzysztof Kozlowski 2 siblings, 1 reply; 37+ messages in thread From: Krzysztof Kozlowski @ 2025-07-03 6:36 UTC (permalink / raw) To: Nick, lgirdwood, broonie, robh, krzk+dt, conor+dt, perex, tiwai Cc: like.sin, xiaoming.yang, danyang.zheng, linux-sound, devicetree, linux-kernel On 03/07/2025 05:56, Nick wrote: > From: Nick Li <nick.li@foursemi.com> > > This patch adds bindings for FourSemi FS2104/5S audio amplifiers > which can support both I2S and I2C interface. > > Signed-off-by: Nick Li <nick.li@foursemi.com> Your patches are not correctly ordered. Vendor prefix is supposed to be before is being used. Bindings are before their users. > --- > .../bindings/sound/foursemi,fs210x.yaml | 95 +++++++++++++++++++ > 1 file changed, 95 insertions(+) > create mode 100644 Documentation/devicetree/bindings/sound/foursemi,fs210x.yaml > > diff --git a/Documentation/devicetree/bindings/sound/foursemi,fs210x.yaml b/Documentation/devicetree/bindings/sound/foursemi,fs210x.yaml > new file mode 100644 > index 000000000..3a1aba9b8 > --- /dev/null > +++ b/Documentation/devicetree/bindings/sound/foursemi,fs210x.yaml Choose one compatible as filename. > @@ -0,0 +1,95 @@ > +# SPDX-License-Identifier: GPL-2.0-only > +%YAML 1.2 > +--- > +$id: http://devicetree.org/schemas/sound/foursemi,fs210x.yaml# > +$schema: http://devicetree.org/meta-schemas/core.yaml# > + > +title: FourSemi FS2104/5S Digital Audio Amplifier > + > +maintainers: > + - Nick <nick.li@foursemi.com> > + > +description: | > + The FS2104 is a 15W Inductor-Less, Stereo, Closed-Loop, > + Digital Input Class-D Power Amplifier with Enhanced Signal Processing. > + The FS2105S is a 30W Inductor-Less, Stereo, Closed-Loop, > + Digital Input Class-D Power Amplifier with Enhanced Signal Processing. > + > +properties: > + compatible: > + enum: > + - foursemi,fs2104 > + - foursemi,fs2105s Your driver suggests these are compatible, so express that with fallback. > + > + reg: > + maxItems: 1 > + description: | Do not need '|' unless you need to preserve formatting. > + I2C address of the amplifier. Refer to datasheet for possible values: > + 0x68/0x69/0x6A/0x6B > + > + clocks: > + items: > + - description: The clock of I2S BCLK > + > + clock-names: > + items: > + - const: bclk > + > + '#sound-dai-cells': > + const: 0 > + > + pvdd-supply: > + description: | Do not need '|' unless you need to preserve formatting. > + Regulator for power supply(PVDD in datasheet). > + > + dvdd-supply: > + description: | Do not need '|' unless you need to preserve formatting. > + Regulator for digital supply(DVDD in datasheet). > + > + sdz-gpios: Use standard properties - see gpio-consumer-common > + maxItems: 1 > + description: | > + SDZ(Shut Down) pin is active low, it will power down the chip, > + and then reset the chip to shut down state. > + > + fs,fwm-name: No, use standard properties (see writing bindings) > + description: | > + The name of firmware that should be loaded for this > + instance. The firmware is gernerated by FourSemi's tuning tool. > + $ref: /schemas/types.yaml#/definitions/string > + > + fs,dai-name: No, it's already in dai-common > + description: | > + The name of FS210x DAI, it can be set different name when > + there are multiple FS210x devices in the system. > + $ref: /schemas/types.yaml#/definitions/string > + > +required: > + - compatible > + - reg > + - sdz-gpios > + - fs,fwm-name > + - '#sound-dai-cells' > + Missing ref to dai common > +additionalProperties: false And this should be unevaluatedProperties instead Best regards, Krzysztof ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v1 3/4] ASoC: dt-bindings: Add dt bindings for FS2104/5S audio amplifiers 2025-07-03 6:36 ` Krzysztof Kozlowski @ 2025-07-04 6:00 ` Nick Li 0 siblings, 0 replies; 37+ messages in thread From: Nick Li @ 2025-07-04 6:00 UTC (permalink / raw) To: Krzysztof Kozlowski Cc: lgirdwood, broonie, robh, krzk+dt, conor+dt, perex, tiwai, xiaoming.yang, danyang.zheng, linux-sound, devicetree, linux-kernel On Thu, Jul 03, 2025 at 08:36:42AM +0200, Krzysztof Kozlowski wrote: > On 03/07/2025 05:56, Nick wrote: > > From: Nick Li <nick.li@foursemi.com> > > > > This patch adds bindings for FourSemi FS2104/5S audio amplifiers > > which can support both I2S and I2C interface. > > > > Signed-off-by: Nick Li <nick.li@foursemi.com> > > Your patches are not correctly ordered. Vendor prefix is supposed to be > before is being used. Bindings are before their users. OK, I will update the orders of patches in next version. > > > --- > > .../bindings/sound/foursemi,fs210x.yaml | 95 +++++++++++++++++++ > > 1 file changed, 95 insertions(+) > > create mode 100644 Documentation/devicetree/bindings/sound/foursemi,fs210x.yaml > > > > diff --git a/Documentation/devicetree/bindings/sound/foursemi,fs210x.yaml b/Documentation/devicetree/bindings/sound/foursemi,fs210x.yaml > > new file mode 100644 > > index 000000000..3a1aba9b8 > > --- /dev/null > > +++ b/Documentation/devicetree/bindings/sound/foursemi,fs210x.yaml > > Choose one compatible as filename. OK, I will update the file name to foursemi,fs2105s.yaml > > > @@ -0,0 +1,95 @@ > > +# SPDX-License-Identifier: GPL-2.0-only > > +%YAML 1.2 > > +--- > > +$id: http://devicetree.org/schemas/sound/foursemi,fs210x.yaml# > > +$schema: http://devicetree.org/meta-schemas/core.yaml# > > + > > +title: FourSemi FS2104/5S Digital Audio Amplifier > > + > > +maintainers: > > + - Nick <nick.li@foursemi.com> > > + > > +description: | > > + The FS2104 is a 15W Inductor-Less, Stereo, Closed-Loop, > > + Digital Input Class-D Power Amplifier with Enhanced Signal Processing. > > + The FS2105S is a 30W Inductor-Less, Stereo, Closed-Loop, > > + Digital Input Class-D Power Amplifier with Enhanced Signal Processing. > > + > > +properties: > > + compatible: > > + enum: > > + - foursemi,fs2104 > > + - foursemi,fs2105s > > Your driver suggests these are compatible, so express that with fallback. OK, I will update these code to: oneOf: - items: ... - enum: ... > > > + > > + reg: > > + maxItems: 1 > > + description: | > > Do not need '|' unless you need to preserve formatting. OK, I will delete it in next version. > > > + I2C address of the amplifier. Refer to datasheet for possible values: > > + 0x68/0x69/0x6A/0x6B > > + > > + clocks: > > + items: > > + - description: The clock of I2S BCLK > > + > > + clock-names: > > + items: > > + - const: bclk > > + > > + '#sound-dai-cells': > > + const: 0 > > + > > + pvdd-supply: > > + description: | > > Do not need '|' unless you need to preserve formatting. OK, I will delete it in next version. > > > > + Regulator for power supply(PVDD in datasheet). > > + > > + dvdd-supply: > > + description: | > > Do not need '|' unless you need to preserve formatting. OK, I will delete it. > > > + Regulator for digital supply(DVDD in datasheet). > > + > > + sdz-gpios: > > Use standard properties - see gpio-consumer-common OK, I will update this property to reset-gpios, and update the driver. > > > + maxItems: 1 > > + description: | > > + SDZ(Shut Down) pin is active low, it will power down the chip, > > + and then reset the chip to shut down state. > > + > > + fs,fwm-name: > > No, use standard properties (see writing bindings) OK, I will update this property to firmware-name, and update the driver. > > > + description: | > > + The name of firmware that should be loaded for this > > + instance. The firmware is gernerated by FourSemi's tuning tool. > > + $ref: /schemas/types.yaml#/definitions/string > > + > > + fs,dai-name: > > No, it's already in dai-common OK, delete it in next version, and update the driver. > > > + description: | > > + The name of FS210x DAI, it can be set different name when > > + there are multiple FS210x devices in the system. > > + $ref: /schemas/types.yaml#/definitions/string > > + > > +required: > > + - compatible > > + - reg > > + - sdz-gpios > > + - fs,fwm-name > > + - '#sound-dai-cells' > > + > > Missing ref to dai common OK, reference will be added: allOf: - $ref: dai-common.yaml# > > > +additionalProperties: false > > And this should be unevaluatedProperties instead OK, I will update it to: unevaluatedProperties: false Thanks for your patient response very mush. Best regards, Nick > > > Best regards, > Krzysztof > ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v1 3/4] ASoC: dt-bindings: Add dt bindings for FS2104/5S audio amplifiers 2025-07-03 3:56 ` [PATCH v1 3/4] ASoC: dt-bindings: Add dt bindings for FS2104/5S audio amplifiers Nick 2025-07-03 5:48 ` Rob Herring (Arm) 2025-07-03 6:36 ` Krzysztof Kozlowski @ 2025-07-03 7:10 ` Krzysztof Kozlowski 2025-07-03 7:15 ` Krzysztof Kozlowski 2025-07-04 7:18 ` Nick Li 2 siblings, 2 replies; 37+ messages in thread From: Krzysztof Kozlowski @ 2025-07-03 7:10 UTC (permalink / raw) To: Nick Cc: lgirdwood, broonie, robh, krzk+dt, conor+dt, perex, tiwai, like.sin, xiaoming.yang, danyang.zheng, linux-sound, devicetree, linux-kernel On Thu, Jul 03, 2025 at 11:56:38AM +0800, Nick wrote: > From: Nick Li <nick.li@foursemi.com> > > This patch adds bindings for FourSemi FS2104/5S audio amplifiers > which can support both I2S and I2C interface. > > Signed-off-by: Nick Li <nick.li@foursemi.com> > --- > .../bindings/sound/foursemi,fs210x.yaml | 95 +++++++++++++++++++ > 1 file changed, 95 insertions(+) > create mode 100644 Documentation/devicetree/bindings/sound/foursemi,fs210x.yaml > > diff --git a/Documentation/devicetree/bindings/sound/foursemi,fs210x.yaml b/Documentation/devicetree/bindings/sound/foursemi,fs210x.yaml > new file mode 100644 > index 000000000..3a1aba9b8 > --- /dev/null > +++ b/Documentation/devicetree/bindings/sound/foursemi,fs210x.yaml > @@ -0,0 +1,95 @@ > +# SPDX-License-Identifier: GPL-2.0-only Please run scripts/checkpatch.pl on the patches and fix reported warnings. After that, run also 'scripts/checkpatch.pl --strict' on the patches and (probably) fix more warnings. Some warnings can be ignored, especially from --strict run, but the code here looks like it needs a fix. Feel free to get in touch if the warning is not clear. Best regards, Krzysztof ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v1 3/4] ASoC: dt-bindings: Add dt bindings for FS2104/5S audio amplifiers 2025-07-03 7:10 ` Krzysztof Kozlowski @ 2025-07-03 7:15 ` Krzysztof Kozlowski 2025-07-04 7:26 ` Nick Li 2025-07-04 7:18 ` Nick Li 1 sibling, 1 reply; 37+ messages in thread From: Krzysztof Kozlowski @ 2025-07-03 7:15 UTC (permalink / raw) To: Nick Cc: lgirdwood, broonie, robh, krzk+dt, conor+dt, perex, tiwai, like.sin, xiaoming.yang, danyang.zheng, linux-sound, devicetree, linux-kernel On 03/07/2025 09:10, Krzysztof Kozlowski wrote: > On Thu, Jul 03, 2025 at 11:56:38AM +0800, Nick wrote: >> From: Nick Li <nick.li@foursemi.com> >> >> This patch adds bindings for FourSemi FS2104/5S audio amplifiers >> which can support both I2S and I2C interface. >> >> Signed-off-by: Nick Li <nick.li@foursemi.com> >> --- >> .../bindings/sound/foursemi,fs210x.yaml | 95 +++++++++++++++++++ >> 1 file changed, 95 insertions(+) >> create mode 100644 Documentation/devicetree/bindings/sound/foursemi,fs210x.yaml >> >> diff --git a/Documentation/devicetree/bindings/sound/foursemi,fs210x.yaml b/Documentation/devicetree/bindings/sound/foursemi,fs210x.yaml >> new file mode 100644 >> index 000000000..3a1aba9b8 >> --- /dev/null >> +++ b/Documentation/devicetree/bindings/sound/foursemi,fs210x.yaml >> @@ -0,0 +1,95 @@ >> +# SPDX-License-Identifier: GPL-2.0-only > > Please run scripts/checkpatch.pl on the patches and fix reported > warnings. After that, run also 'scripts/checkpatch.pl --strict' on the > patches and (probably) fix more warnings. Some warnings can be ignored, > especially from --strict run, but the code here looks like it needs a > fix. Feel free to get in touch if the warning is not clear. One more thing: Drop "dt-bindings for" from subject. See also: https://elixir.bootlin.com/linux/v6.7-rc8/source/Documentation/devicetree/bindings/submitting-patches.rst#L18 ... and that was my third email today... I apologize I did not send these as one review email, that's not really professional from my side but somehow coffee did not work good this morning. Best regards, Krzysztof ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v1 3/4] ASoC: dt-bindings: Add dt bindings for FS2104/5S audio amplifiers 2025-07-03 7:15 ` Krzysztof Kozlowski @ 2025-07-04 7:26 ` Nick Li 0 siblings, 0 replies; 37+ messages in thread From: Nick Li @ 2025-07-04 7:26 UTC (permalink / raw) To: Krzysztof Kozlowski Cc: lgirdwood, broonie, robh, krzk+dt, conor+dt, perex, tiwai, xiaoming.yang, danyang.zheng, linux-sound, devicetree, linux-kernel On Thu, Jul 03, 2025 at 09:15:36AM +0200, Krzysztof Kozlowski wrote: > On 03/07/2025 09:10, Krzysztof Kozlowski wrote: > > On Thu, Jul 03, 2025 at 11:56:38AM +0800, Nick wrote: > >> From: Nick Li <nick.li@foursemi.com> > >> > >> This patch adds bindings for FourSemi FS2104/5S audio amplifiers > >> which can support both I2S and I2C interface. > >> > >> Signed-off-by: Nick Li <nick.li@foursemi.com> > >> --- > >> .../bindings/sound/foursemi,fs210x.yaml | 95 +++++++++++++++++++ > >> 1 file changed, 95 insertions(+) > >> create mode 100644 Documentation/devicetree/bindings/sound/foursemi,fs210x.yaml > >> > >> diff --git a/Documentation/devicetree/bindings/sound/foursemi,fs210x.yaml b/Documentation/devicetree/bindings/sound/foursemi,fs210x.yaml > >> new file mode 100644 > >> index 000000000..3a1aba9b8 > >> --- /dev/null > >> +++ b/Documentation/devicetree/bindings/sound/foursemi,fs210x.yaml > >> @@ -0,0 +1,95 @@ > >> +# SPDX-License-Identifier: GPL-2.0-only > > > > Please run scripts/checkpatch.pl on the patches and fix reported > > warnings. After that, run also 'scripts/checkpatch.pl --strict' on the > > patches and (probably) fix more warnings. Some warnings can be ignored, > > especially from --strict run, but the code here looks like it needs a > > fix. Feel free to get in touch if the warning is not clear. > > > One more thing: Drop "dt-bindings for" from subject. > See also: > https://elixir.bootlin.com/linux/v6.7-rc8/source/Documentation/devicetree/bindings/submitting-patches.rst#L18 OK, update it in next version. > > > ... and that was my third email today... I apologize I did not send > these as one review email, that's not really professional from my side > but somehow coffee did not work good this morning. > Thank you for taking your valuable time to review the code. Best regards, Nick > Best regards, > Krzysztof > ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v1 3/4] ASoC: dt-bindings: Add dt bindings for FS2104/5S audio amplifiers 2025-07-03 7:10 ` Krzysztof Kozlowski 2025-07-03 7:15 ` Krzysztof Kozlowski @ 2025-07-04 7:18 ` Nick Li 1 sibling, 0 replies; 37+ messages in thread From: Nick Li @ 2025-07-04 7:18 UTC (permalink / raw) To: Krzysztof Kozlowski Cc: lgirdwood, broonie, robh, krzk+dt, conor+dt, perex, tiwai, xiaoming.yang, danyang.zheng, linux-sound, devicetree, linux-kernel On Thu, Jul 03, 2025 at 09:10:38AM +0200, Krzysztof Kozlowski wrote: > On Thu, Jul 03, 2025 at 11:56:38AM +0800, Nick wrote: > > From: Nick Li <nick.li@foursemi.com> > > > > This patch adds bindings for FourSemi FS2104/5S audio amplifiers > > which can support both I2S and I2C interface. > > > > Signed-off-by: Nick Li <nick.li@foursemi.com> > > --- > > .../bindings/sound/foursemi,fs210x.yaml | 95 +++++++++++++++++++ > > 1 file changed, 95 insertions(+) > > create mode 100644 Documentation/devicetree/bindings/sound/foursemi,fs210x.yaml > > > > diff --git a/Documentation/devicetree/bindings/sound/foursemi,fs210x.yaml b/Documentation/devicetree/bindings/sound/foursemi,fs210x.yaml > > new file mode 100644 > > index 000000000..3a1aba9b8 > > --- /dev/null > > +++ b/Documentation/devicetree/bindings/sound/foursemi,fs210x.yaml > > @@ -0,0 +1,95 @@ > > +# SPDX-License-Identifier: GPL-2.0-only > > Please run scripts/checkpatch.pl on the patches and fix reported > warnings. After that, run also 'scripts/checkpatch.pl --strict' on the > patches and (probably) fix more warnings. Some warnings can be ignored, > especially from --strict run, but the code here looks like it needs a > fix. Feel free to get in touch if the warning is not clear. OK, I will check the patches with option --strict, and fix the warnings/errors in next version. Thanks. Best regards, Nick > > Best regards, > Krzysztof > > ^ permalink raw reply [flat|nested] 37+ messages in thread
* [PATCH v1 4/4] dt-bindings: vendor-prefixes: Add Shanghai FourSemi Semiconductor Co.,Ltd 2025-07-03 3:56 [PATCH v1 0/4] ASoC: codecs: Add support for FourSemi FS2104/5S Nick ` (2 preceding siblings ...) 2025-07-03 3:56 ` [PATCH v1 3/4] ASoC: dt-bindings: Add dt bindings for FS2104/5S audio amplifiers Nick @ 2025-07-03 3:56 ` Nick 2025-07-03 5:48 ` Rob Herring (Arm) 2025-07-08 11:28 ` [PATCH v2 0/4] ASoC: codecs: Add support for FourSemi FS2104/5S Nick Li 4 siblings, 1 reply; 37+ messages in thread From: Nick @ 2025-07-03 3:56 UTC (permalink / raw) To: lgirdwood, broonie, robh, krzk+dt, conor+dt, perex, tiwai, nick.li Cc: like.sin, xiaoming.yang, danyang.zheng, linux-sound, devicetree, linux-kernel From: Nick Li <nick.li@foursemi.com> Add vendor prefix for Shanghai FourSemi Semiconductor Co.,Ltd Link: https://en.foursemi.com/ Signed-off-by: Nick Li <nick.li@foursemi.com> --- Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml index 99e86271c..f692e3b29 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -547,6 +547,8 @@ patternProperties: description: FocalTech Systems Co.,Ltd "^forlinx,.*": description: Baoding Forlinx Embedded Technology Co., Ltd. + "^foursemi,.*": + description: Shanghai FourSemi Semiconductor Co.,Ltd. "^freebox,.*": description: Freebox SAS "^freecom,.*": -- 2.39.5 ^ permalink raw reply related [flat|nested] 37+ messages in thread
* Re: [PATCH v1 4/4] dt-bindings: vendor-prefixes: Add Shanghai FourSemi Semiconductor Co.,Ltd 2025-07-03 3:56 ` [PATCH v1 4/4] dt-bindings: vendor-prefixes: Add Shanghai FourSemi Semiconductor Co.,Ltd Nick @ 2025-07-03 5:48 ` Rob Herring (Arm) 0 siblings, 0 replies; 37+ messages in thread From: Rob Herring (Arm) @ 2025-07-03 5:48 UTC (permalink / raw) To: Nick Cc: krzk+dt, conor+dt, like.sin, devicetree, broonie, tiwai, danyang.zheng, linux-kernel, xiaoming.yang, lgirdwood, perex, linux-sound On Thu, 03 Jul 2025 11:56:39 +0800, Nick wrote: > From: Nick Li <nick.li@foursemi.com> > > Add vendor prefix for Shanghai FourSemi Semiconductor Co.,Ltd > Link: https://en.foursemi.com/ > > Signed-off-by: Nick Li <nick.li@foursemi.com> > --- > Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++ > 1 file changed, 2 insertions(+) > My bot found errors running 'make dt_binding_check' on your patch: yamllint warnings/errors: dtschema/dtc warnings/errors: /builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/sound/foursemi,fs210x.example.dtb: audio-codec@68 (foursemi,fs2105s): 'fs,fwm-name' does not match any of the regexes: '^#.*', '^(at25|bm|devbus|dmacap|dsa|exynos|fsi[ab]|gpio-fan|gpio-key|gpio|gpmc|hdmi|i2c-gpio),.*', '^(keypad|m25p|max8952|max8997|max8998|mpmc),.*', '^(pciclass|pinctrl-single|#pinctrl-single|PowerPC),.*', '^(pl022|pxa-mmc|rcar_sound|rotary-encoder|s5m8767|sdhci),.*', '^(simple-audio-card|st-plgpio|st-spics|ts),.*', '^100ask,.*', '^70mai,.*', '^8dev,.*', '^GEFanuc,.*', '^IBM,.*', '^ORCL,.*', '^SUNW,.*', '^[a-zA-Z0-9#_][a-zA-Z0-9+\\-._@]{0,63}$', '^[a-zA-Z0-9+\\-._]*@[0-9a-zA-Z,]*$', '^abb,.*', '^abilis,.*', '^abracon,.*', '^abt,.*', '^acbel,.*', '^acelink,.*', '^acer,.*', '^acme,.*', '^actions,.*', '^active-semi,.*', '^ad,.*', '^adafruit,.*', '^adapteva,.*', '^adaptrum,.*', '^adh,.*', '^adi,.*', '^adieng,.*', '^admatec,.*', '^advantech,.*', '^aeroflexgaisler,.*', '^aesop,.*', '^airoha,.*', '^al,.*', '^alcatel,.*', '^aldec,.*', '^alfa-network,.*', '^allegro,.*', '^allegromicro,.*', '^alliedvision,.*', '^allo,.*', '^allwinner,.*', '^alphascale,.*', '^alps,.*', '^alt,.*', '^altr,.*', '^amarula,.*', '^amazon,.*', '^amcc,.*', '^amd,.*', '^amediatech,.*', '^amlogic,.*', '^ampere,.*', '^amphenol,.*', '^ampire,.*', '^ams,.*', '^amstaos,.*', '^analogix,.*', '^anbernic,.*', '^andestech,.*', '^anvo,.*', '^aoly,.*', '^aosong,.*', '^apm,.*', '^apple,.*', '^aptina,.*', '^arasan,.*', '^archermind,.*', '^arcom,.*', '^arctic,.*', '^arcx,.*', '^argon40,.*', '^ariaboard,.*', '^aries,.*', '^arm,.*', '^armadeus,.*', '^armsom,.*', '^arrow,.*', '^artesyn,.*', '^asahi-kasei,.*', '^asc,.*', '^asix,.*', '^aspeed,.*', '^asrock,.*', '^asteralabs,.*', '^asus,.*', '^atheros,.*', '^atlas,.*', '^atmel,.*', '^auo,.*', '^auvidea,.*', '^avago,.*', '^avia,.*', '^avic,.*', '^avnet,.*', '^awinic,.*', '^axentia,.*', '^axis,.*', '^azoteq,.*', '^azw,.*', '^baikal,.*', '^bananapi,.*', '^beacon,.*', '^beagle,.*', '^belling,.*', '^bhf,.*', '^bigtreetech,.*', '^bitmain,.*', '^blaize,.*', '^blutek,.*', '^boe,.*', '^bosch,.*', '^boundary,.*', '^brcm,.*', '^broadmobi,.*', '^bsh,.*', '^bticino,.*', '^buffalo,.*', '^bur,.*', '^bytedance,.*', '^calamp,.*', '^calao,.*', '^calaosystems,.*', '^calxeda,.*', '^cameo,.*', '^canaan,.*', '^caninos,.*', '^capella,.*', '^cascoda,.*', '^catalyst,.*', '^cavium,.*', '^cct,.*', '^cdns,.*', '^cdtech,.*', '^cellwise,.*', '^ceva,.*', '^chargebyte,.*', '^checkpoint,.*', '^chefree,.*', '^chipidea,.*', '^chipone,.*', '^chipspark,.*', '^chongzhou,.*', '^chrontel,.*', '^chrp,.*', '^chunghwa,.*', '^chuwi,.*', '^ciaa,.*', '^cirrus,.*', '^cisco,.*', '^clockwork,.*', '^cloos,.*', '^cloudengines,.*', '^cnm,.*', '^cnxt,.*', '^colorfly,.*', '^compulab,.*', '^comvetia,.*', '^congatec,.*', '^coolpi,.*', '^coreriver,.*', '^corpro,.*', '^cortina,.*', '^cosmic,.*', '^crane,.*', '^creative,.*', '^crystalfontz,.*', '^csky,.*', '^csot,.*', '^csq,.*', '^ctera,.*', '^ctu,.*', '^cubietech,.*', '^cudy,.*', '^cui,.*', '^cypress,.*', '^cyx,.*', '^cznic,.*', '^dallas,.*', '^dataimage,.*', '^davicom,.*', '^deepcomputing,.*', '^dell,.*', '^delta,.*', '^densitron,.*', '^denx,.*', '^devantech,.*', '^dfi,.*', '^dfrobot,.*', '^dh,.*', '^difrnce,.*', '^digi,.*', '^digilent,.*', '^dimonoff,.*', '^diodes,.*', '^dioo,.*', '^djn,.*', '^dlc,.*', '^dlg,.*', '^dlink,.*', '^dmo,.*', '^domintech,.*', '^dongwoon,.*', '^dptechnics,.*', '^dragino,.*', '^dream,.*', '^ds,.*', '^dserve,.*', '^dynaimage,.*', '^ea,.*', '^ebang,.*', '^ebbg,.*', '^ebs-systart,.*', '^ebv,.*', '^eckelmann,.*', '^econet,.*', '^edgeble,.*', '^edimax,.*', '^edt,.*', '^ees,.*', '^eeti,.*', '^einfochips,.*', '^eink,.*', '^elan,.*', '^element14,.*', '^elgin,.*', '^elida,.*', '^elimo,.*', '^elpida,.*', '^embedfire,.*', '^embest,.*', '^emcraft,.*', '^emlid,.*', '^emmicro,.*', '^empire-electronix,.*', '^emtrion,.*', '^enclustra,.*', '^endless,.*', '^ene,.*', '^energymicro,.*', '^engicam,.*', '^engleder,.*', '^epcos,.*', '^epfl,.*', '^epson,.*', '^esp,.*', '^est,.*', '^ettus,.*', '^eukrea,.*', '^everest,.*', '^everspin,.*', '^evervision,.*', '^exar,.*', '^excito,.*', '^exegin,.*', '^ezchip,.*', '^facebook,.*', '^fairchild,.*', '^fairphone,.*', '^faraday,.*', '^fascontek,.*', '^fastrax,.*', '^fcs,.*', '^feixin,.*', '^feiyang,.*', '^fii,.*', '^firefly,.*', '^focaltech,.*', '^forlinx,.*', '^foursemi,.*', '^freebox,.*', '^freecom,.*', '^frida,.*', '^friendlyarm,.*', '^fsl,.*', '^fujitsu,.*', '^fxtec,.*', '^galaxycore,.*', '^gameforce,.*', '^gardena,.*', '^gateway,.*', '^gateworks,.*', '^gcw,.*', '^ge,.*', '^geekbuying,.*', '^gef,.*', '^gehc,.*', '^gemei,.*', '^gemtek,.*', '^genesys,.*', '^genexis,.*', '^geniatech,.*', '^giantec,.*', '^giantplus,.*', '^glinet,.*', '^globalscale,.*', '^globaltop,.*', '^gmt,.*', '^gocontroll,.*', '^goldelico,.*', '^goodix,.*', '^google,.*', '^goramo,.*', '^gplus,.*', '^grinn,.*', '^grmn,.*', '^gumstix,.*', '^gw,.*', '^hannstar,.*', '^haochuangyi,.*', '^haoyu,.*', '^hardkernel,.*', '^hechuang,.*', '^hideep,.*', '^himax,.*', '^hirschmann,.*', '^hisi,.*', '^hisilicon,.*', '^hit,.*', '^hitex,.*', '^holt,.*', '^holtek,.*', '^honestar,.*', '^honeywell,.*', '^hoperf,.*', '^hoperun,.*', '^hp,.*', '^hpe,.*', '^hsg,.*', '^htc,.*', '^huawei,.*', '^hugsun,.*', '^huiling,.*', '^hwacom,.*', '^hxt,.*', '^hycon,.*', '^hydis,.*', '^hynitron,.*', '^hynix,.*', '^hyundai,.*', '^i2se,.*', '^ibm,.*', '^icplus,.*', '^idt,.*', '^iei,.*', '^ifi,.*', '^ilitek,.*', '^imagis,.*', '^img,.*', '^imi,.*', '^inanbo,.*', '^incircuit,.*', '^indiedroid,.*', '^inet-tek,.*', '^infineon,.*', '^inforce,.*', '^ingenic,.*', '^ingrasys,.*', '^injoinic,.*', '^innocomm,.*', '^innolux,.*', '^inside-secure,.*', '^insignal,.*', '^inspur,.*', '^intel,.*', '^intercontrol,.*', '^invensense,.*', '^inventec,.*', '^inversepath,.*', '^iom,.*', '^irondevice,.*', '^isee,.*', '^isil,.*', '^issi,.*', '^ite,.*', '^itead,.*', '^itian,.*', '^ivo,.*', '^iwave,.*', '^jadard,.*', '^jasonic,.*', '^jdi,.*', '^jedec,.*', '^jenson,.*', '^jesurun,.*', '^jethome,.*', '^jianda,.*', '^jide,.*', '^joz,.*', '^kam,.*', '^karo,.*', '^keithkoep,.*', '^keymile,.*', '^khadas,.*', '^kiebackpeter,.*', '^kinetic,.*', '^kingdisplay,.*', '^kingnovel,.*', '^kionix,.*', '^kobo,.*', '^kobol,.*', '^koe,.*', '^kontron,.*', '^kosagi,.*', '^kvg,.*', '^kyo,.*', '^lacie,.*', '^laird,.*', '^lamobo,.*', '^lantiq,.*', '^lattice,.*', '^lckfb,.*', '^lctech,.*', '^leadtek,.*', '^leez,.*', '^lego,.*', '^lemaker,.*', '^lenovo,.*', '^lg,.*', '^lgphilips,.*', '^libretech,.*', '^licheepi,.*', '^linaro,.*', '^lincolntech,.*', '^lineartechnology,.*', '^linksprite,.*', '^linksys,.*', '^linutronix,.*', '^linux,.*', '^linx,.*', '^liontron,.*', '^liteon,.*', '^litex,.*', '^lltc,.*', '^logicpd,.*', '^logictechno,.*', '^longcheer,.*', '^lontium,.*', '^loongmasses,.*', '^loongson,.*', '^lsi,.*', '^luckfox,.*', '^lunzn,.*', '^luxul,.*', '^lwn,.*', '^lxa,.*', '^m5stack,.*', '^macnica,.*', '^mantix,.*', '^mapleboard,.*', '^marantec,.*', '^marvell,.*', '^maxbotix,.*', '^maxim,.*', '^maxlinear,.*', '^mbvl,.*', '^mcube,.*', '^meas,.*', '^mecer,.*', '^mediatek,.*', '^megachips,.*', '^mele,.*', '^melexis,.*', '^melfas,.*', '^mellanox,.*', '^memsensing,.*', '^memsic,.*', '^menlo,.*', '^mentor,.*', '^meraki,.*', '^merrii,.*', '^methode,.*', '^micrel,.*', '^microchip,.*', '^microcrystal,.*', '^micron,.*', '^microsoft,.*', '^microsys,.*', '^microtips,.*', '^mikroe,.*', '^mikrotik,.*', '^milkv,.*', '^miniand,.*', '^minix,.*', '^mips,.*', '^miramems,.*', '^mitsubishi,.*', '^mitsumi,.*', '^mixel,.*', '^miyoo,.*', '^mntre,.*', '^mobileye,.*', '^modtronix,.*', '^moortec,.*', '^mosaixtech,.*', '^motorcomm,.*', '^motorola,.*', '^moxa,.*', '^mpl,.*', '^mps,.*', '^mqmaker,.*', '^mrvl,.*', '^mscc,.*', '^msi,.*', '^mstar,.*', '^mti,.*', '^multi-inno,.*', '^mundoreader,.*', '^murata,.*', '^mxic,.*', '^mxicy,.*', '^myir,.*', '^national,.*', '^neardi,.*', '^nec,.*', '^neofidelity,.*', '^neonode,.*', '^netcube,.*', '^netgear,.*', '^netlogic,.*', '^netron-dy,.*', '^netronix,.*', '^netxeon,.*', '^neweast,.*', '^newhaven,.*', '^newvision,.*', '^nexbox,.*', '^nextthing,.*', '^ni,.*', '^nintendo,.*', '^nlt,.*', '^nokia,.*', '^nordic,.*', '^nothing,.*', '^novatek,.*', '^novtech,.*', '^numonyx,.*', '^nutsboard,.*', '^nuvoton,.*', '^nvd,.*', '^nvidia,.*', '^nxp,.*', '^oceanic,.*', '^ocs,.*', '^oct,.*', '^okaya,.*', '^oki,.*', '^olimex,.*', '^olpc,.*', '^oneplus,.*', '^onie,.*', '^onion,.*', '^onnn,.*', '^ontat,.*', '^opalkelly,.*', '^openailab,.*', '^opencores,.*', '^openembed,.*', '^openpandora,.*', '^openrisc,.*', '^openwrt,.*', '^option,.*', '^oranth,.*', '^orisetech,.*', '^ortustech,.*', '^osddisplays,.*', '^osmc,.*', '^ouya,.*', '^overkiz,.*', '^ovti,.*', '^oxsemi,.*', '^ozzmaker,.*', '^panasonic,.*', '^parade,.*', '^parallax,.*', '^pda,.*', '^pegatron,.*', '^pericom,.*', '^pervasive,.*', '^phicomm,.*', '^phytec,.*', '^picochip,.*', '^pinctrl-[0-9]+$', '^pine64,.*', '^pineriver,.*', '^pixcir,.*', '^plantower,.*', '^plathome,.*', '^plda,.*', '^plx,.*', '^ply,.*', '^pni,.*', '^pocketbook,.*', '^polaroid,.*', '^polyhex,.*', '^portwell,.*', '^poslab,.*', '^pov,.*', '^powertip,.*', '^powervr,.*', '^powkiddy,.*', '^pri,.*', '^primeview,.*', '^primux,.*', '^probox2,.*', '^prt,.*', '^pulsedlight,.*', '^purism,.*', '^puya,.*', '^qca,.*', '^qcom,.*', '^qemu,.*', '^qi,.*', '^qiaodian,.*', '^qihua,.*', '^qishenglong,.*', '^qnap,.*', '^quanta,.*', '^radxa,.*', '^raidsonic,.*', '^ralink,.*', '^ramtron,.*', '^raspberrypi,.*', '^raydium,.*', '^rda,.*', '^realtek,.*', '^relfor,.*', '^remarkable,.*', '^renesas,.*', '^rervision,.*', '^retronix,.*', '^revotics,.*', '^rex,.*', '^richtek,.*', '^ricoh,.*', '^rikomagic,.*', '^riot,.*', '^riscv,.*', '^rockchip,.*', '^rocktech,.*', '^rohm,.*', '^ronbo,.*', '^roofull,.*', '^roseapplepi,.*', '^rve,.*', '^saef,.*', '^sakurapi,.*', '^samsung,.*', '^samtec,.*', '^sancloud,.*', '^sandisk,.*', '^satoz,.*', '^sbs,.*', '^schindler,.*', '^schneider,.*', '^sciosense,.*', '^seagate,.*', '^seeed,.*', '^seirobotics,.*', '^semtech,.*', '^senseair,.*', '^sensirion,.*', '^sensortek,.*', '^sercomm,.*', '^sff,.*', '^sgd,.*', '^sgmicro,.*', '^sgx,.*', '^sharp,.*', '^shift,.*', '^shimafuji,.*', '^shineworld,.*', '^shiratech,.*', '^si-en,.*', '^si-linux,.*', '^siemens,.*', '^sifive,.*', '^siflower,.*', '^sigma,.*', '^sii,.*', '^sil,.*', '^silabs,.*', '^silan,.*', '^silead,.*', '^silergy,.*', '^silex-insight,.*', '^siliconfile,.*', '^siliconmitus,.*', '^silvaco,.*', '^simtek,.*', '^sinlinx,.*', '^sinovoip,.*', '^sinowealth,.*', '^sipeed,.*', '^sirf,.*', '^sis,.*', '^sitronix,.*', '^skov,.*', '^skyworks,.*', '^smartfiber,.*', '^smartlabs,.*', '^smartrg,.*', '^smi,.*', '^smsc,.*', '^snps,.*', '^sochip,.*', '^socionext,.*', '^solidrun,.*', '^solomon,.*', '^sony,.*', '^sophgo,.*', '^sourceparts,.*', '^spacemit,.*', '^spansion,.*', '^sparkfun,.*', '^spinalhdl,.*', '^sprd,.*', '^square,.*', '^ssi,.*', '^sst,.*', '^sstar,.*', '^st,.*', '^st-ericsson,.*', '^starfive,.*', '^starry,.*', '^startek,.*', '^starterkit,.*', '^ste,.*', '^stericsson,.*', '^storlink,.*', '^storm,.*', '^storopack,.*', '^summit,.*', '^sunchip,.*', '^sundance,.*', '^sunplus,.*', '^supermicro,.*', '^swir,.*', '^syna,.*', '^synology,.*', '^synopsys,.*', '^tbs,.*', '^tbs-biometrics,.*', '^tcg,.*', '^tcl,.*', '^tcs,.*', '^tcu,.*', '^tdo,.*', '^team-source-display,.*', '^technexion,.*', '^technologic,.*', '^techstar,.*', '^techwell,.*', '^teejet,.*', '^teltonika,.*', '^tempo,.*', '^terasic,.*', '^tesla,.*', '^test,.*', '^tfc,.*', '^thead,.*', '^thine,.*', '^thingyjp,.*', '^thundercomm,.*', '^thwc,.*', '^ti,.*', '^tianma,.*', '^tlm,.*', '^tmt,.*', '^topeet,.*', '^topic,.*', '^topland,.*', '^toppoly,.*', '^topwise,.*', '^toradex,.*', '^toshiba,.*', '^toumaz,.*', '^tpk,.*', '^tplink,.*', '^tpo,.*', '^tq,.*', '^transpeed,.*', '^traverse,.*', '^tronfy,.*', '^tronsmart,.*', '^truly,.*', '^tsd,.*', '^turing,.*', '^tyan,.*', '^tyhx,.*', '^u-blox,.*', '^u-boot,.*', '^ubnt,.*', '^ucrobotics,.*', '^udoo,.*', '^ufispace,.*', '^ugoos,.*', '^ultratronik,.*', '^uni-t,.*', '^uniwest,.*', '^upisemi,.*', '^urt,.*', '^usi,.*', '^usr,.*', '^utoo,.*', '^v3,.*', '^vaisala,.*', '^vamrs,.*', '^variscite,.*', '^vdl,.*', '^vertexcom,.*', '^via,.*', '^vialab,.*', '^vicor,.*', '^videostrong,.*', '^virtio,.*', '^virtual,.*', '^vishay,.*', '^visionox,.*', '^vitesse,.*', '^vivante,.*', '^vivax,.*', '^vocore,.*', '^voipac,.*', '^voltafield,.*', '^vot,.*', '^vscom,.*', '^vxt,.*', '^wacom,.*', '^wanchanglong,.*', '^wand,.*', '^waveshare,.*', '^wd,.*', '^we,.*', '^welltech,.*', '^wetek,.*', '^wexler,.*', '^whwave,.*', '^wi2wi,.*', '^widora,.*', '^wiligear,.*', '^willsemi,.*', '^winbond,.*', '^wingtech,.*', '^winlink,.*', '^winsen,.*', '^winstar,.*', '^wirelesstag,.*', '^wits,.*', '^wlf,.*', '^wm,.*', '^wobo,.*', '^wolfvision,.*', '^x-powers,.*', '^xen,.*', '^xes,.*', '^xiaomi,.*', '^xillybus,.*', '^xingbangda,.*', '^xinpeng,.*', '^xiphera,.*', '^xlnx,.*', '^xnano,.*', '^xunlong,.*', '^xylon,.*', '^yadro,.*', '^yamaha,.*', '^yes-optoelectronics,.*', '^yic,.*', '^yiming,.*', '^ylm,.*', '^yna,.*', '^yones-toptech,.*', '^ys,.*', '^ysoft,.*', '^yuridenki,.*', '^yuzukihd,.*', '^zarlink,.*', '^zealz,.*', '^zeitec,.*', '^zidoo,.*', '^zii,.*', '^zinitix,.*', '^zkmagic,.*', '^zte,.*', '^zyxel,.*' from schema $id: http://devicetree.org/schemas/vendor-prefixes.yaml# doc reference errors (make refcheckdocs): See https://patchwork.ozlabs.org/project/devicetree-bindings/patch/20250703035639.7252-5-nick.li@foursemi.com The base for the series is generally the latest rc1. A different dependency should be noted in *this* patch. If you already ran 'make dt_binding_check' and didn't see the above error(s), then make sure 'yamllint' is installed and dt-schema is up to date: pip3 install dtschema --upgrade Please check and re-submit after running the above command yourself. Note that DT_SCHEMA_FILES can be set to your schema file to speed up checking your schema. However, it must be unset to test all examples with your schema. ^ permalink raw reply [flat|nested] 37+ messages in thread
* [PATCH v2 0/4] ASoC: codecs: Add support for FourSemi FS2104/5S 2025-07-03 3:56 [PATCH v1 0/4] ASoC: codecs: Add support for FourSemi FS2104/5S Nick ` (3 preceding siblings ...) 2025-07-03 3:56 ` [PATCH v1 4/4] dt-bindings: vendor-prefixes: Add Shanghai FourSemi Semiconductor Co.,Ltd Nick @ 2025-07-08 11:28 ` Nick Li 2025-07-08 11:28 ` [PATCH v2 1/4] dt-bindings: vendor-prefixes: Add Shanghai FourSemi Semiconductor Co.,Ltd Nick Li ` (3 more replies) 4 siblings, 4 replies; 37+ messages in thread From: Nick Li @ 2025-07-08 11:28 UTC (permalink / raw) To: lgirdwood, broonie, robh, krzk+dt, conor+dt, perex, tiwai, nick.li Cc: xiaoming.yang, danyang.zheng, like.xy, linux-sound, devicetree, linux-kernel The FS2104/5S are Inductor-Less, Stereo, Closed-Loop, Digital Input Class-D Power Amplifiers with Enhanced Signal Processing FS2104 can deliver 2x15W into 4ohm BTL speaker loads, FS2105S can deliver 2x30W into 8ohm BTL speaker loads. Most functions have been built and tested on EVB boards: ARMv8-A, Linux version 6.16.0-rc4-v8 v1 -> v2: - Adjust the order of patches according to the dependency relationship - Rename yaml file to foursemi,fs2105s.yaml - Fix some properties and error definitions in foursemi,fs2105s.yaml: sdz-gpios -> reset->gpios fs,fwm-name -> firmware-name Delete fs,dai-name - Drop "dt-bindings for" from subject - Update the driver code according to the update of DT schema - Fix warnings/errors reported by running checkpatch.pl --strict - Fix warnings/errors reported by running make dt_bindings_check Nick Li (4): dt-bindings: vendor-prefixes: Add Shanghai FourSemi Semiconductor Co.,Ltd ASoC: dt-bindings: Add schema for FS2104/5S audio amplifiers ASoC: codecs: Add library for FourSemi audio amplifiers ASoC: codecs: Add FourSemi FS2104/5S audio amplifier driver .../bindings/sound/foursemi,fs2105s.yaml | 100 + .../devicetree/bindings/vendor-prefixes.yaml | 2 + sound/soc/codecs/Kconfig | 14 + sound/soc/codecs/Makefile | 4 + sound/soc/codecs/fs-amp-lib.c | 265 +++ sound/soc/codecs/fs-amp-lib.h | 150 ++ sound/soc/codecs/fs210x.c | 1610 +++++++++++++++++ sound/soc/codecs/fs210x.h | 79 + 8 files changed, 2224 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/foursemi,fs2105s.yaml create mode 100644 sound/soc/codecs/fs-amp-lib.c create mode 100644 sound/soc/codecs/fs-amp-lib.h create mode 100644 sound/soc/codecs/fs210x.c create mode 100644 sound/soc/codecs/fs210x.h base-commit: 870bd70790beeaff6ef016aece922e540675836e -- 2.17.1 ^ permalink raw reply [flat|nested] 37+ messages in thread
* [PATCH v2 1/4] dt-bindings: vendor-prefixes: Add Shanghai FourSemi Semiconductor Co.,Ltd 2025-07-08 11:28 ` [PATCH v2 0/4] ASoC: codecs: Add support for FourSemi FS2104/5S Nick Li @ 2025-07-08 11:28 ` Nick Li 2025-07-09 10:36 ` Krzysztof Kozlowski 2025-07-08 11:28 ` [PATCH v2 2/4] ASoC: dt-bindings: Add schema for FS2104/5S audio amplifiers Nick Li ` (2 subsequent siblings) 3 siblings, 1 reply; 37+ messages in thread From: Nick Li @ 2025-07-08 11:28 UTC (permalink / raw) To: lgirdwood, broonie, robh, krzk+dt, conor+dt, perex, tiwai, nick.li Cc: xiaoming.yang, danyang.zheng, like.xy, linux-sound, devicetree, linux-kernel Add vendor prefix for Shanghai FourSemi Semiconductor Co.,Ltd Link: https://en.foursemi.com/ Signed-off-by: Nick Li <nick.li@foursemi.com> --- Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml index 5d2a7a8d3..e42d54313 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -543,6 +543,8 @@ patternProperties: description: FocalTech Systems Co.,Ltd "^forlinx,.*": description: Baoding Forlinx Embedded Technology Co., Ltd. + "^foursemi,.*": + description: Shanghai FourSemi Semiconductor Co.,Ltd. "^freebox,.*": description: Freebox SAS "^freecom,.*": -- 2.17.1 ^ permalink raw reply related [flat|nested] 37+ messages in thread
* Re: [PATCH v2 1/4] dt-bindings: vendor-prefixes: Add Shanghai FourSemi Semiconductor Co.,Ltd 2025-07-08 11:28 ` [PATCH v2 1/4] dt-bindings: vendor-prefixes: Add Shanghai FourSemi Semiconductor Co.,Ltd Nick Li @ 2025-07-09 10:36 ` Krzysztof Kozlowski 0 siblings, 0 replies; 37+ messages in thread From: Krzysztof Kozlowski @ 2025-07-09 10:36 UTC (permalink / raw) To: Nick Li Cc: lgirdwood, broonie, robh, krzk+dt, conor+dt, perex, tiwai, xiaoming.yang, danyang.zheng, like.xy, linux-sound, devicetree, linux-kernel On Tue, Jul 08, 2025 at 07:28:58PM +0800, Nick Li wrote: > Add vendor prefix for Shanghai FourSemi Semiconductor Co.,Ltd > Link: https://en.foursemi.com/ > Do not attach (thread) your patchsets to some other threads (unrelated or older versions). This buries them deep in the mailbox and might interfere with applying entire sets. See also: https://elixir.bootlin.com/linux/v6.16-rc2/source/Documentation/process/submitting-patches.rst#L830 Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org> Best regards, Krzysztof ^ permalink raw reply [flat|nested] 37+ messages in thread
* [PATCH v2 2/4] ASoC: dt-bindings: Add schema for FS2104/5S audio amplifiers 2025-07-08 11:28 ` [PATCH v2 0/4] ASoC: codecs: Add support for FourSemi FS2104/5S Nick Li 2025-07-08 11:28 ` [PATCH v2 1/4] dt-bindings: vendor-prefixes: Add Shanghai FourSemi Semiconductor Co.,Ltd Nick Li @ 2025-07-08 11:28 ` Nick Li 2025-07-09 10:40 ` Krzysztof Kozlowski 2025-07-09 10:42 ` Krzysztof Kozlowski 2025-07-08 11:29 ` [PATCH v2 3/4] ASoC: codecs: Add library for FourSemi " Nick Li 2025-07-08 11:29 ` [PATCH v2 4/4] ASoC: codecs: Add FourSemi FS2104/5S audio amplifier driver Nick Li 3 siblings, 2 replies; 37+ messages in thread From: Nick Li @ 2025-07-08 11:28 UTC (permalink / raw) To: lgirdwood, broonie, robh, krzk+dt, conor+dt, perex, tiwai, nick.li Cc: xiaoming.yang, danyang.zheng, like.xy, linux-sound, devicetree, linux-kernel Add a DT schema for describing FourSemi FS2104/5S audio amplifiers which support both I2S and I2C interface. Signed-off-by: Nick Li <nick.li@foursemi.com> --- .../bindings/sound/foursemi,fs2105s.yaml | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/foursemi,fs2105s.yaml diff --git a/Documentation/devicetree/bindings/sound/foursemi,fs2105s.yaml b/Documentation/devicetree/bindings/sound/foursemi,fs2105s.yaml new file mode 100644 index 000000000..5211f9fe1 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/foursemi,fs2105s.yaml @@ -0,0 +1,100 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/foursemi,fs2105s.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: FourSemi FS2104/5S Digital Audio Amplifier + +maintainers: + - Nick Li <nick.li@foursemi.com> + +description: + The FS2104 is a 15W Inductor-Less, Stereo, Closed-Loop, + Digital Input Class-D Power Amplifier with Enhanced Signal Processing. + The FS2105S is a 30W Inductor-Less, Stereo, Closed-Loop, + Digital Input Class-D Power Amplifier with Enhanced Signal Processing. + +properties: + compatible: + oneOf: + - items: + - enum: + - foursemi,fs2104 + - const: foursemi,fs2105s + - enum: + - foursemi,fs2105s + + reg: + maxItems: 1 + description: + I2C address of the device. Refer to datasheet for possible values + + clocks: + description: The clock of I2S BCLK + + clock-names: + items: + - const: bclk + + interrupts: + maxItems: 1 + + '#sound-dai-cells': + const: 0 + + pvdd-supply: + description: + Regulator for power supply(PVDD in datasheet). + + dvdd-supply: + description: + Regulator for digital supply(DVDD in datasheet). + + reset-gpios: + maxItems: 1 + description: + It's the SDZ pin in datasheet, the pin is active low, + it will power down and reset the chip to shut down state. + + firmware-name: + maxItems: 1 + description: | + The firmware(*.bin) contains: + a. Register initialization settings + b. DSP effect parameters + c. Multi-scene sound effect configurations(optional) + It's gernerated by FourSemi's tuning tool. + +required: + - compatible + - reg + - reset-gpios + - firmware-name + - '#sound-dai-cells' + +allOf: + - $ref: dai-common.yaml# + +unevaluatedProperties: false + +examples: + - | + #include <dt-bindings/gpio/gpio.h> + i2c { + #address-cells = <1>; + #size-cells = <0>; + audio-codec@68 { + compatible = "foursemi,fs2105s"; + reg = <0x68>; + pvdd-supply = <&pvdd_supply>; + dvdd-supply = <&dvdd_supply>; + reset-gpios = <&gpio 18 GPIO_ACTIVE_LOW>; + firmware-name = "fs2105s-btl-2p0-0s.bin"; + pinctrl-names = "default"; + pinctrl-0 = <&fs210x_pins_default>; + clocks = <&clocks 18>; + clock-names = "bclk"; + #sound-dai-cells = <0>; + }; + }; -- 2.17.1 ^ permalink raw reply related [flat|nested] 37+ messages in thread
* Re: [PATCH v2 2/4] ASoC: dt-bindings: Add schema for FS2104/5S audio amplifiers 2025-07-08 11:28 ` [PATCH v2 2/4] ASoC: dt-bindings: Add schema for FS2104/5S audio amplifiers Nick Li @ 2025-07-09 10:40 ` Krzysztof Kozlowski 2025-07-10 8:11 ` Nick Li 2025-07-09 10:42 ` Krzysztof Kozlowski 1 sibling, 1 reply; 37+ messages in thread From: Krzysztof Kozlowski @ 2025-07-09 10:40 UTC (permalink / raw) To: Nick Li Cc: lgirdwood, broonie, robh, krzk+dt, conor+dt, perex, tiwai, xiaoming.yang, danyang.zheng, like.xy, linux-sound, devicetree, linux-kernel On Tue, Jul 08, 2025 at 07:28:59PM +0800, Nick Li wrote: > +description: > + The FS2104 is a 15W Inductor-Less, Stereo, Closed-Loop, > + Digital Input Class-D Power Amplifier with Enhanced Signal Processing. > + The FS2105S is a 30W Inductor-Less, Stereo, Closed-Loop, > + Digital Input Class-D Power Amplifier with Enhanced Signal Processing. > + > +properties: > + compatible: > + oneOf: > + - items: > + - enum: > + - foursemi,fs2104 > + - const: foursemi,fs2105s > + - enum: > + - foursemi,fs2105s > + > + reg: > + maxItems: 1 > + description: > + I2C address of the device. Refer to datasheet for possible values Now the description is entirely redundant, brings no value. Drop. > + > + clocks: > + description: The clock of I2S BCLK This was different... Previous code was correct, this is not correct. And nothing in changelog explains this. Do not make random changes after review. > + > + clock-names: > + items: > + - const: bclk > + > + interrupts: > + maxItems: 1 > + > + '#sound-dai-cells': > + const: 0 > + > + pvdd-supply: > + description: > + Regulator for power supply(PVDD in datasheet). > + > + dvdd-supply: > + description: > + Regulator for digital supply(DVDD in datasheet). > + > + reset-gpios: > + maxItems: 1 > + description: > + It's the SDZ pin in datasheet, the pin is active low, > + it will power down and reset the chip to shut down state. > + > + firmware-name: > + maxItems: 1 > + description: | > + The firmware(*.bin) contains: > + a. Register initialization settings > + b. DSP effect parameters > + c. Multi-scene sound effect configurations(optional) > + It's gernerated by FourSemi's tuning tool. > + > +required: > + - compatible > + - reg > + - reset-gpios > + - firmware-name > + - '#sound-dai-cells' Keep the same order as in list of properties. OTOH, missing supplies. > + > +allOf: > + - $ref: dai-common.yaml# > + > +unevaluatedProperties: false Best regards, Krzysztof ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v2 2/4] ASoC: dt-bindings: Add schema for FS2104/5S audio amplifiers 2025-07-09 10:40 ` Krzysztof Kozlowski @ 2025-07-10 8:11 ` Nick Li 2025-07-10 8:27 ` Krzysztof Kozlowski 0 siblings, 1 reply; 37+ messages in thread From: Nick Li @ 2025-07-10 8:11 UTC (permalink / raw) To: Krzysztof Kozlowski Cc: lgirdwood, broonie, robh, krzk+dt, conor+dt, perex, tiwai, xiaoming.yang, danyang.zheng, like.xy, linux-sound, devicetree, linux-kernel On Wed, Jul 09, 2025 at 12:40:33PM +0200, Krzysztof Kozlowski wrote: > On Tue, Jul 08, 2025 at 07:28:59PM +0800, Nick Li wrote: > > +description: > > + The FS2104 is a 15W Inductor-Less, Stereo, Closed-Loop, > > + Digital Input Class-D Power Amplifier with Enhanced Signal Processing. > > + The FS2105S is a 30W Inductor-Less, Stereo, Closed-Loop, > > + Digital Input Class-D Power Amplifier with Enhanced Signal Processing. > > + > > +properties: > > + compatible: > > + oneOf: > > + - items: > > + - enum: > > + - foursemi,fs2104 > > + - const: foursemi,fs2105s > > + - enum: > > + - foursemi,fs2105s > > + > > + reg: > > + maxItems: 1 > > + description: > > + I2C address of the device. Refer to datasheet for possible values > > Now the description is entirely redundant, brings no value. Drop. OK. > > > + > > + clocks: > > + description: The clock of I2S BCLK > > This was different... Previous code was correct, this is not correct. > And nothing in changelog explains this. Do not make random changes after > review. > OK, I will recover it to version v1. > > + > > + clock-names: > > + items: > > + - const: bclk > > + > > + interrupts: > > + maxItems: 1 > > + > > + '#sound-dai-cells': > > + const: 0 > > + > > + pvdd-supply: > > + description: > > + Regulator for power supply(PVDD in datasheet). > > + > > + dvdd-supply: > > + description: > > + Regulator for digital supply(DVDD in datasheet). > > + > > + reset-gpios: > > + maxItems: 1 > > + description: > > + It's the SDZ pin in datasheet, the pin is active low, > > + it will power down and reset the chip to shut down state. > > + > > + firmware-name: > > + maxItems: 1 > > + description: | > > + The firmware(*.bin) contains: > > + a. Register initialization settings > > + b. DSP effect parameters > > + c. Multi-scene sound effect configurations(optional) > > + It's gernerated by FourSemi's tuning tool. > > + > > +required: > > + - compatible > > + - reg > > + - reset-gpios > > + - firmware-name > > + - '#sound-dai-cells' > > Keep the same order as in list of properties. OTOH, missing supplies. OK, we will fix the order, but the supplies may not be used as regulator, we mark them as required, is it OK? Best regards, Nick > > > + > > +allOf: > > + - $ref: dai-common.yaml# > > + > > +unevaluatedProperties: false > > Best regards, > Krzysztof > > ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v2 2/4] ASoC: dt-bindings: Add schema for FS2104/5S audio amplifiers 2025-07-10 8:11 ` Nick Li @ 2025-07-10 8:27 ` Krzysztof Kozlowski 2025-07-10 9:26 ` Nick Li 0 siblings, 1 reply; 37+ messages in thread From: Krzysztof Kozlowski @ 2025-07-10 8:27 UTC (permalink / raw) To: Nick Li Cc: lgirdwood, broonie, robh, krzk+dt, conor+dt, perex, tiwai, xiaoming.yang, danyang.zheng, like.xy, linux-sound, devicetree, linux-kernel On 10/07/2025 10:11, Nick Li wrote: >>> + >>> +required: >>> + - compatible >>> + - reg >>> + - reset-gpios >>> + - firmware-name >>> + - '#sound-dai-cells' >> >> Keep the same order as in list of properties. OTOH, missing supplies. > > OK, we will fix the order, but the supplies may not be used as regulator, Hm? What does it mean in terms of hardware? > we mark them as required, is it OK? How codec driver can work without power? Best regards, Krzysztof ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v2 2/4] ASoC: dt-bindings: Add schema for FS2104/5S audio amplifiers 2025-07-10 8:27 ` Krzysztof Kozlowski @ 2025-07-10 9:26 ` Nick Li 0 siblings, 0 replies; 37+ messages in thread From: Nick Li @ 2025-07-10 9:26 UTC (permalink / raw) To: Krzysztof Kozlowski Cc: lgirdwood, broonie, robh, krzk+dt, conor+dt, perex, tiwai, xiaoming.yang, danyang.zheng, like.xy, linux-sound, devicetree, linux-kernel On Thu, Jul 10, 2025 at 10:27:59AM +0200, Krzysztof Kozlowski wrote: > On 10/07/2025 10:11, Nick Li wrote: > >>> + > >>> +required: > >>> + - compatible > >>> + - reg > >>> + - reset-gpios > >>> + - firmware-name > >>> + - '#sound-dai-cells' > >> > >> Keep the same order as in list of properties. OTOH, missing supplies. > > > > OK, we will fix the order, but the supplies may not be used as regulator, > > Hm? What does it mean in terms of hardware? > > > we mark them as required, is it OK? > > How codec driver can work without power? The power may be connected to the baterry/adapter directly, it may not be under the control of the software, in this case, the supplies are use as dummy regulators? Best regards, Nick > > > Best regards, > Krzysztof > ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v2 2/4] ASoC: dt-bindings: Add schema for FS2104/5S audio amplifiers 2025-07-08 11:28 ` [PATCH v2 2/4] ASoC: dt-bindings: Add schema for FS2104/5S audio amplifiers Nick Li 2025-07-09 10:40 ` Krzysztof Kozlowski @ 2025-07-09 10:42 ` Krzysztof Kozlowski 2025-07-10 8:02 ` Nick Li 1 sibling, 1 reply; 37+ messages in thread From: Krzysztof Kozlowski @ 2025-07-09 10:42 UTC (permalink / raw) To: Nick Li Cc: lgirdwood, broonie, robh, krzk+dt, conor+dt, perex, tiwai, xiaoming.yang, danyang.zheng, like.xy, linux-sound, devicetree, linux-kernel On Tue, Jul 08, 2025 at 07:28:59PM +0800, Nick Li wrote: > Add a DT schema for describing FourSemi FS2104/5S > audio amplifiers which support both I2S and I2C interface. > Another unexpected change from v1: subject: why did you add "schema"? I asked to drop it and gave you reference explaining this. Did you read it? So again, same template: A nit, subject: drop second/last, redundant "schema for". The "dt-bindings" prefix is already stating that these are bindings in schema format, because they cannot be anything else. See also: https://elixir.bootlin.com/linux/v6.7-rc8/source/Documentation/devicetree/bindings/submitting-patches.rst#L18 Best regards, Krzysztof ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v2 2/4] ASoC: dt-bindings: Add schema for FS2104/5S audio amplifiers 2025-07-09 10:42 ` Krzysztof Kozlowski @ 2025-07-10 8:02 ` Nick Li 0 siblings, 0 replies; 37+ messages in thread From: Nick Li @ 2025-07-10 8:02 UTC (permalink / raw) To: Krzysztof Kozlowski Cc: lgirdwood, broonie, robh, krzk+dt, conor+dt, perex, tiwai, xiaoming.yang, danyang.zheng, like.xy, linux-sound, devicetree, linux-kernel On Wed, Jul 09, 2025 at 12:42:35PM +0200, Krzysztof Kozlowski wrote: > On Tue, Jul 08, 2025 at 07:28:59PM +0800, Nick Li wrote: > > Add a DT schema for describing FourSemi FS2104/5S > > audio amplifiers which support both I2S and I2C interface. > > > > Another unexpected change from v1: subject: why did you add "schema"? > > I asked to drop it and gave you reference explaining this. Did you read > it? > > So again, same template: > > A nit, subject: drop second/last, redundant "schema for". The > "dt-bindings" prefix is already stating that these are bindings in > schema format, because they cannot be anything else. > See also: > https://elixir.bootlin.com/linux/v6.7-rc8/source/Documentation/devicetree/bindings/submitting-patches.rst#L18 > Sorry, I misunderstood, I will fix it by dropping "schema for ". Best regards, Nick > Best regards, > Krzysztof > > ^ permalink raw reply [flat|nested] 37+ messages in thread
* [PATCH v2 3/4] ASoC: codecs: Add library for FourSemi audio amplifiers 2025-07-08 11:28 ` [PATCH v2 0/4] ASoC: codecs: Add support for FourSemi FS2104/5S Nick Li 2025-07-08 11:28 ` [PATCH v2 1/4] dt-bindings: vendor-prefixes: Add Shanghai FourSemi Semiconductor Co.,Ltd Nick Li 2025-07-08 11:28 ` [PATCH v2 2/4] ASoC: dt-bindings: Add schema for FS2104/5S audio amplifiers Nick Li @ 2025-07-08 11:29 ` Nick Li 2025-07-08 11:29 ` [PATCH v2 4/4] ASoC: codecs: Add FourSemi FS2104/5S audio amplifier driver Nick Li 3 siblings, 0 replies; 37+ messages in thread From: Nick Li @ 2025-07-08 11:29 UTC (permalink / raw) To: lgirdwood, broonie, robh, krzk+dt, conor+dt, perex, tiwai, nick.li Cc: xiaoming.yang, danyang.zheng, like.xy, linux-sound, devicetree, linux-kernel This patch adds firmware loading and parsing support for FourSemi audio amplifiers. The library handles firmware file (*.bin) generated by the FourSemi tuning tool, which contains: - Register initialization settings - DSP effect parameters - Multi-scene sound effect switching configurations(optional) The firmware is required for proper initialization and configuration of FourSemi amplifier devices. Signed-off-by: Nick Li <nick.li@foursemi.com> --- sound/soc/codecs/Kconfig | 3 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/fs-amp-lib.c | 265 ++++++++++++++++++++++++++++++++++ sound/soc/codecs/fs-amp-lib.h | 150 +++++++++++++++++++ 4 files changed, 420 insertions(+) create mode 100644 sound/soc/codecs/fs-amp-lib.c create mode 100644 sound/soc/codecs/fs-amp-lib.h diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 6d7e4725d..ecdc05ef3 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -1232,6 +1232,9 @@ config SND_SOC_FRAMER To compile this driver as a module, choose M here: the module will be called snd-soc-framer. +config SND_SOC_FS_AMP_LIB + select CRC16 + tristate config SND_SOC_GTM601 tristate 'GTM601 UMTS modem audio codec' diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index a68c3d192..646e017a8 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -137,6 +137,7 @@ snd-soc-es8328-spi-y := es8328-spi.o snd-soc-es8375-y := es8375.o snd-soc-es8389-y := es8389.o snd-soc-framer-y := framer-codec.o +snd-soc-fs-amp-lib-y := fs-amp-lib.o snd-soc-gtm601-y := gtm601.o snd-soc-hdac-hdmi-y := hdac_hdmi.o snd-soc-hdac-hda-y := hdac_hda.o @@ -562,6 +563,7 @@ obj-$(CONFIG_SND_SOC_ES8328_SPI)+= snd-soc-es8328-spi.o obj-$(CONFIG_SND_SOC_ES8375) += snd-soc-es8375.o obj-$(CONFIG_SND_SOC_ES8389) += snd-soc-es8389.o obj-$(CONFIG_SND_SOC_FRAMER) += snd-soc-framer.o +obj-$(CONFIG_SND_SOC_FS_AMP_LIB)+= snd-soc-fs-amp-lib.o obj-$(CONFIG_SND_SOC_GTM601) += snd-soc-gtm601.o obj-$(CONFIG_SND_SOC_HDAC_HDMI) += snd-soc-hdac-hdmi.o obj-$(CONFIG_SND_SOC_HDAC_HDA) += snd-soc-hdac-hda.o diff --git a/sound/soc/codecs/fs-amp-lib.c b/sound/soc/codecs/fs-amp-lib.c new file mode 100644 index 000000000..75d8d5082 --- /dev/null +++ b/sound/soc/codecs/fs-amp-lib.c @@ -0,0 +1,265 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// fs-amp-lib.c --- Common library for FourSemi Audio Amplifiers +// +// Copyright (C) 2016-2025 Shanghai FourSemi Semiconductor Co.,Ltd. + +#include <linux/crc16.h> +#include <linux/device.h> +#include <linux/firmware.h> +#include <linux/module.h> +#include <linux/slab.h> + +#include "fs-amp-lib.h" + +static int fs_get_scene_count(struct fs_amp_lib *amp_lib) +{ + const struct fs_fwm_table *table; + int count; + + if (!amp_lib || !amp_lib->dev) + return -EINVAL; + + table = amp_lib->table[FS_INDEX_SCENE]; + if (!table) + return -EFAULT; + + count = table->size / sizeof(struct fs_scene_index); + if (count < 1 || count > FS_SCENE_COUNT_MAX) { + dev_err(amp_lib->dev, "Invalid scene count: %d\n", count); + return -ERANGE; + } + + return count; +} + +static void fs_get_fwm_string(struct fs_amp_lib *amp_lib, + int offset, const char **pstr) +{ + const struct fs_fwm_table *table; + + if (!amp_lib || !amp_lib->dev || !pstr) + return; + + table = amp_lib->table[FS_INDEX_STRING]; + if (table && offset > 0 && offset < table->size + sizeof(*table)) + *pstr = (char *)table + offset; + else + *pstr = NULL; +} + +static void fs_get_scene_reg(struct fs_amp_lib *amp_lib, + int offset, struct fs_amp_scene *scene) +{ + const struct fs_fwm_table *table; + + if (!amp_lib || !amp_lib->dev || !scene) + return; + + table = amp_lib->table[FS_INDEX_REG]; + if (table && offset > 0 && offset < table->size + sizeof(*table)) + scene->reg = (struct fs_reg_table *)((char *)table + offset); + else + scene->reg = NULL; +} + +static void fs_get_scene_model(struct fs_amp_lib *amp_lib, + int offset, struct fs_amp_scene *scene) +{ + const struct fs_fwm_table *table; + const char *ptr; + + if (!amp_lib || !amp_lib->dev || !scene) + return; + + table = amp_lib->table[FS_INDEX_MODEL]; + ptr = (char *)table; + if (table && offset > 0 && offset < table->size + sizeof(*table)) + scene->model = (struct fs_file_table *)(ptr + offset); + else + scene->model = NULL; +} + +static void fs_get_scene_effect(struct fs_amp_lib *amp_lib, + int offset, struct fs_amp_scene *scene) +{ + const struct fs_fwm_table *table; + const char *ptr; + + if (!amp_lib || !amp_lib->dev || !scene) + return; + + table = amp_lib->table[FS_INDEX_EFFECT]; + ptr = (char *)table; + if (table && offset > 0 && offset < table->size + sizeof(*table)) + scene->effect = (struct fs_file_table *)(ptr + offset); + else + scene->effect = NULL; +} + +static int fs_parse_scene_tables(struct fs_amp_lib *amp_lib) +{ + const struct fs_scene_index *scene_index; + const struct fs_fwm_table *table; + struct fs_amp_scene *scene; + int idx, count; + + if (!amp_lib || !amp_lib->dev) + return -EINVAL; + + count = fs_get_scene_count(amp_lib); + if (count <= 0) + return -EFAULT; + + scene = devm_kzalloc(amp_lib->dev, count * sizeof(*scene), GFP_KERNEL); + if (!scene) + return -ENOMEM; + + amp_lib->scene_count = count; + amp_lib->scene = scene; + + table = amp_lib->table[FS_INDEX_SCENE]; + scene_index = (struct fs_scene_index *)table->buf; + + for (idx = 0; idx < count; idx++) { + fs_get_fwm_string(amp_lib, scene_index->name, &scene->name); + if (!scene->name) + scene->name = devm_kasprintf(amp_lib->dev, + GFP_KERNEL, "S%d", idx); + dev_dbg(amp_lib->dev, "scene.%d name: %s\n", idx, scene->name); + fs_get_scene_reg(amp_lib, scene_index->reg, scene); + fs_get_scene_model(amp_lib, scene_index->model, scene); + fs_get_scene_effect(amp_lib, scene_index->effect, scene); + scene++; + scene_index++; + } + + return 0; +} + +static int fs_parse_all_tables(struct fs_amp_lib *amp_lib) +{ + const struct fs_fwm_table *table; + const struct fs_fwm_index *index; + const char *ptr; + int idx, count; + int ret; + + if (!amp_lib || !amp_lib->dev || !amp_lib->hdr) + return -EINVAL; + + /* Parse all fwm tables */ + table = (struct fs_fwm_table *)amp_lib->hdr->params; + index = (struct fs_fwm_index *)table->buf; + count = table->size / sizeof(*index); + + for (idx = 0; idx < count; idx++, index++) { + if (index->type >= FS_INDEX_MAX) + return -ERANGE; + ptr = (char *)table + (int)index->offset; + amp_lib->table[index->type] = (struct fs_fwm_table *)ptr; + } + + /* Parse all scene tables */ + ret = fs_parse_scene_tables(amp_lib); + if (ret) + dev_err(amp_lib->dev, "Failed to parse scene: %d\n", ret); + + return ret; +} + +static int fs_verify_firmware(struct fs_amp_lib *amp_lib) +{ + const struct fs_fwm_header *hdr; + int crcsum; + + if (!amp_lib || !amp_lib->dev || !amp_lib->hdr) + return -EINVAL; + + hdr = amp_lib->hdr; + + /* Verify the crcsum code */ + crcsum = crc16(0x0000, (const char *)&hdr->crc_size, hdr->crc_size); + if (crcsum != hdr->crc16) { + dev_err(amp_lib->dev, "Failed to checksum: %x-%x\n", + crcsum, hdr->crc16); + return -EFAULT; + } + + /* Verify the devid(chip_type) */ + if (amp_lib->devid != LO_U16(hdr->chip_type)) { + dev_err(amp_lib->dev, "DEVID dismatch: %04X#%04X\n", + amp_lib->devid, hdr->chip_type); + return -EINVAL; + } + + return 0; +} + +static void fs_print_firmware_info(struct fs_amp_lib *amp_lib) +{ + const struct fs_fwm_header *hdr; + const char *pro_name = NULL; + const char *dev_name = NULL; + + if (!amp_lib || !amp_lib->dev || !amp_lib->hdr) + return; + + hdr = amp_lib->hdr; + + fs_get_fwm_string(amp_lib, hdr->project, &pro_name); + fs_get_fwm_string(amp_lib, hdr->device, &dev_name); + + dev_info(amp_lib->dev, "Project: %s Device: %s\n", + pro_name ? pro_name : "null", + dev_name ? dev_name : "null"); + + dev_info(amp_lib->dev, "Date: %04d%02d%02d-%02d%02d\n", + hdr->date.year, hdr->date.month, hdr->date.day, + hdr->date.hour, hdr->date.minute); +} + +int fs_amp_load_firmware(struct fs_amp_lib *amp_lib, const char *name) +{ + const struct firmware *cont; + struct fs_fwm_header *hdr; + int ret; + + if (!amp_lib || !amp_lib->dev || !name) + return -EINVAL; + + ret = request_firmware(&cont, name, amp_lib->dev); + if (ret) { + dev_err(amp_lib->dev, "Failed to request %s: %d\n", name, ret); + return ret; + } + + dev_info(amp_lib->dev, "Loading %s - size: %zu\n", name, cont->size); + + hdr = devm_kmemdup(amp_lib->dev, cont->data, cont->size, GFP_KERNEL); + release_firmware(cont); + if (!hdr) + return -ENOMEM; + + amp_lib->hdr = hdr; + ret = fs_verify_firmware(amp_lib); + if (ret) { + amp_lib->hdr = NULL; + return ret; + } + + ret = fs_parse_all_tables(amp_lib); + if (ret) { + amp_lib->hdr = NULL; + return ret; + } + + fs_print_firmware_info(amp_lib); + + return 0; +} +EXPORT_SYMBOL_GPL(fs_amp_load_firmware); + +MODULE_AUTHOR("Nick Li <nick.li@foursemi.com>"); +MODULE_DESCRIPTION("FourSemi audio amplifier library"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/fs-amp-lib.h b/sound/soc/codecs/fs-amp-lib.h new file mode 100644 index 000000000..4a77c7b38 --- /dev/null +++ b/sound/soc/codecs/fs-amp-lib.h @@ -0,0 +1,150 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * fs-amp-lib.h --- Common library for FourSemi Audio Amplifiers + * + * Copyright (C) 2016-2025 Shanghai FourSemi Semiconductor Co.,Ltd. + */ + +#ifndef __FS_AMP_LIB_H__ +#define __FS_AMP_LIB_H__ + +#define HI_U16(a) (((a) >> 8) & 0xFF) +#define LO_U16(a) ((a) & 0xFF) +#define FS_TABLE_NAME_LEN (4) +#define FS_SCENE_COUNT_MAX (16) +#define FS_CMD_DELAY_MS_MAX (100) /* 100ms */ + +#define FS_CMD_DELAY (0xFF) +#define FS_CMD_BURST (0xFE) +#define FS_CMD_UPDATE (0xFD) + +#define FS_SOC_ENUM_EXT(xname, xhandler_info, xhandler_get, xhandler_put) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = xhandler_info, \ + .get = xhandler_get, .put = xhandler_put \ +} + +enum fs_index_type { + FS_INDEX_INFO = 0, + FS_INDEX_STCOEF, + FS_INDEX_SCENE, + FS_INDEX_MODEL, + FS_INDEX_REG, + FS_INDEX_EFFECT, + FS_INDEX_STRING, + FS_INDEX_WOOFER, + FS_INDEX_MAX, +}; + +#pragma pack(push, 1) + +struct fs_reg_val { + u8 reg; + u16 val; +}; + +struct fs_reg_bits { + u8 cmd; /* FS_CMD_UPDATE */ + u8 reg; + u16 val; + u16 mask; +}; + +struct fs_cmd_pkg { + union { + u8 cmd; + struct fs_reg_val regv; + struct fs_reg_bits regb; + }; +}; + +struct fs_fwm_index { + /* Index type */ + u16 type; + /* Offset address starting from the end of header */ + u16 offset; +}; + +struct fs_fwm_table { + char name[FS_TABLE_NAME_LEN]; + u16 size; /* size of buf */ + u8 buf[]; +}; + +struct fs_scene_index { + /* Offset address(scene name) in string table */ + u16 name; + /* Offset address(scene reg) in register table */ + u16 reg; + /* Offset address(scene model) in model table */ + u16 model; + /* Offset address(scene effect) in effect table */ + u16 effect; +}; + +struct fs_reg_table { + u16 size; /* size of buf */ + u8 buf[]; +}; + +struct fs_file_table { + u16 name; + u16 size; /* size of buf */ + u8 buf[]; +}; + +struct fs_fwm_date { + u32 year:12; + u32 month:4; + u32 day:5; + u32 hour:5; + u32 minute:6; +}; + +struct fs_fwm_header { + u16 version; + u16 project; /* Offset address(project name) in string table */ + u16 device; /* Offset address(device name) in string table */ + struct fs_fwm_date date; + u16 crc16; + u16 crc_size; /* Starting position for CRC checking */ + u16 chip_type; + u16 addr; /* 7-bit i2c address */ + u16 spkid; + u16 rsvd[6]; + u8 params[]; +}; + +#pragma pack(pop) + +struct fs_i2s_srate { + u32 srate; /* Sample rate */ + u16 i2ssr; /* Value of Bit field[I2SSR] */ +}; + +struct fs_pll_div { + unsigned int bclk; /* Rate of bit clock */ + u16 pll1; + u16 pll2; + u16 pll3; +}; + +struct fs_amp_scene { + const char *name; + const struct fs_reg_table *reg; + const struct fs_file_table *model; + const struct fs_file_table *effect; +}; + +struct fs_amp_lib { + const struct fs_fwm_header *hdr; + const struct fs_fwm_table *table[FS_INDEX_MAX]; + struct fs_amp_scene *scene; + struct device *dev; + int scene_count; + u16 devid; +}; + +int fs_amp_load_firmware(struct fs_amp_lib *amp_lib, const char *name); + +#endif // __FS_AMP_LIB_H__ -- 2.17.1 ^ permalink raw reply related [flat|nested] 37+ messages in thread
* [PATCH v2 4/4] ASoC: codecs: Add FourSemi FS2104/5S audio amplifier driver 2025-07-08 11:28 ` [PATCH v2 0/4] ASoC: codecs: Add support for FourSemi FS2104/5S Nick Li ` (2 preceding siblings ...) 2025-07-08 11:29 ` [PATCH v2 3/4] ASoC: codecs: Add library for FourSemi " Nick Li @ 2025-07-08 11:29 ` Nick Li 2025-07-09 10:55 ` Krzysztof Kozlowski 2025-07-09 11:29 ` Mark Brown 3 siblings, 2 replies; 37+ messages in thread From: Nick Li @ 2025-07-08 11:29 UTC (permalink / raw) To: lgirdwood, broonie, robh, krzk+dt, conor+dt, perex, tiwai, nick.li Cc: xiaoming.yang, danyang.zheng, like.xy, linux-sound, devicetree, linux-kernel The FS2104/5S are FourSemi digital audio amplifiers with I2C control. They are Inductor-Less, Stereo, Closed-Loop, Digital Input Class-D Power Amplifiers with Enhanced Signal Processing. Signed-off-by: Nick Li <nick.li@foursemi.com> --- sound/soc/codecs/Kconfig | 11 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/fs210x.c | 1610 +++++++++++++++++++++++++++++++++++++ sound/soc/codecs/fs210x.h | 79 ++ 4 files changed, 1702 insertions(+) create mode 100644 sound/soc/codecs/fs210x.c create mode 100644 sound/soc/codecs/fs210x.h diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index ecdc05ef3..43fe512a5 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -125,6 +125,7 @@ config SND_SOC_ALL_CODECS imply SND_SOC_ES7134 imply SND_SOC_ES7241 imply SND_SOC_FRAMER + imply SND_SOC_FS210X imply SND_SOC_GTM601 imply SND_SOC_HDAC_HDMI imply SND_SOC_HDAC_HDA @@ -1236,6 +1237,16 @@ config SND_SOC_FS_AMP_LIB select CRC16 tristate +config SND_SOC_FS210X + tristate 'FourSemi FS2104/5S digital audio amplifier' + depends on I2C + select GPIOLIB + select REGMAP_I2C + select SND_SOC_FS_AMP_LIB + help + Enable support for FourSemi FS2104/5S digital audio amplifier + with I2C control. + config SND_SOC_GTM601 tristate 'GTM601 UMTS modem audio codec' diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 646e017a8..3f97afaaa 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -138,6 +138,7 @@ snd-soc-es8375-y := es8375.o snd-soc-es8389-y := es8389.o snd-soc-framer-y := framer-codec.o snd-soc-fs-amp-lib-y := fs-amp-lib.o +snd-soc-fs210x-y := fs210x.o snd-soc-gtm601-y := gtm601.o snd-soc-hdac-hdmi-y := hdac_hdmi.o snd-soc-hdac-hda-y := hdac_hda.o @@ -564,6 +565,7 @@ obj-$(CONFIG_SND_SOC_ES8375) += snd-soc-es8375.o obj-$(CONFIG_SND_SOC_ES8389) += snd-soc-es8389.o obj-$(CONFIG_SND_SOC_FRAMER) += snd-soc-framer.o obj-$(CONFIG_SND_SOC_FS_AMP_LIB)+= snd-soc-fs-amp-lib.o +obj-$(CONFIG_SND_SOC_FS210X) += snd-soc-fs210x.o obj-$(CONFIG_SND_SOC_GTM601) += snd-soc-gtm601.o obj-$(CONFIG_SND_SOC_HDAC_HDMI) += snd-soc-hdac-hdmi.o obj-$(CONFIG_SND_SOC_HDAC_HDA) += snd-soc-hdac-hda.o diff --git a/sound/soc/codecs/fs210x.c b/sound/soc/codecs/fs210x.c new file mode 100644 index 000000000..5176b3399 --- /dev/null +++ b/sound/soc/codecs/fs210x.c @@ -0,0 +1,1610 @@ +// 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/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/of_gpio.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_DRV_VERSION "v1.0.6" +#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_VOLUME_MIN 0 +#define FS210X_VOLUME_MAX 511 +#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; + unsigned int bclk; + unsigned int srate; + int scene_id; + u16 devid; + u16 vol[2]; /* L/R channels volume */ + bool is_inited; + bool is_suspended; + bool is_bclk_on; + bool is_playing; +}; + +static DEFINE_MUTEX(fs210x_mutex); + +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->clk_bclk) + return 0; + + if ((fs210x->is_bclk_on ^ on) == 0) + return 0; + + dev_dbg(fs210x->dev, "bclk switch %s\n", on ? "on" : "off"); + + if (on) { + clk_set_rate(fs210x->clk_bclk, fs210x->bclk); + ret = clk_prepare_enable(fs210x->clk_bclk); + fs210x->is_bclk_on = true; + usleep_range(2000, 2050); /* >= 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; + } + + dev_dbg(fs210x->dev, "RW: %02x %04x\n", reg, val); + + 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; + } + + if (pval) + *pval = (u16)val; + + dev_dbg(fs210x->dev, "RR: %02x %04x\n", reg, 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; + } + + dev_dbg(fs210x->dev, "RU: %02x %04x %04x\n", reg, mask, val); + + 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; + } + + dev_dbg(fs210x->dev, "BW: %02x %d\n", reg, size); + + return 0; +} + +static int fs210x_reg_dump(struct fs210x_priv *fs210x) +{ + u16 val[8]; /* 8 registers per line */ + u8 idx, reg; + int ret; + + if (!fs210x || !fs210x->dev) + return -EINVAL; + +#define FS210X_REG_DUMP_MAX 0xCF + for (reg = 0x00; reg <= FS210X_REG_DUMP_MAX; reg++) { + /* Print 8 register values per line */ + idx = (reg & 0x7); + ret = fs210x_reg_read(fs210x, reg, val + idx); + if (ret) + break; + if (idx != 0x7 && reg != FS210X_REG_DUMP_MAX) + continue; + dev_info(fs210x->dev, + "%02Xh: %04x %04x %04x %04x %04x %04x %04x %04x\n", + (reg & 0xF8), val[0], val[1], val[2], + val[3], val[4], val[5], val[6], val[7]); + } + + return ret; +} + +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; + usleep_range(delay_us, delay_us + 50); + *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; + + usleep_range(10000, 10050); /* >= 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; + + usleep_range(30000, 30050); /* >= 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); + } + + usleep_range(10000, 10050); /* >= 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 inline int volume_is_valid(u16 vol) +{ + return (vol <= FS210X_VOLUME_MAX); +} + +static int fs210x_set_pcm_volume(struct fs210x_priv *fs210x) +{ + u16 vol[2]; + int ret; + + if (fs210x->devid == FS2105S_DEVICE_ID) { + /* + * FS2105SE volume registers: 39h(Left)/3Ah(Right): + * VOL[15:7]: 0 dB to -95.8125dB in steps of -0.1875dB + * 1FFh: 0dB + * 000h: -95.8125dB + */ + vol[0] = fs210x->vol[0] << FS2105S_39H_VOL_SHIFT; + vol[1] = fs210x->vol[1] << FS2105S_3AH_VOL_SHIFT; + } else { + /* + * FS2104 volume registers: 39h(Left)/3Ah(Right): + * VOL[15:6]: 0 dB to -131.8125dB in steps of -0.1875dB + * 2BFh: 0dB + * 0C0h: -95.8125dB + * 000h: -131.8125dB + * Offset between FS2104 and FS2105S: 2BFh-1FFh=0C0h + */ +#define VOL_OFFSET (0x2BF - 0x1FF) + vol[0] = (fs210x->vol[0] + VOL_OFFSET) << FS210X_39H_VOL_SHIFT; + vol[1] = (fs210x->vol[1] + VOL_OFFSET) << FS210X_3AH_VOL_SHIFT; + } + + ret = fs210x_reg_write(fs210x, FS210X_39H_LVOLCTRL, vol[0]); + ret |= fs210x_reg_write(fs210x, FS210X_3AH_RVOLCTRL, vol[1]); + + return ret; +} + +static int fs210x_init_chip(struct fs210x_priv *fs210x) +{ + int scene_id; + int ret; + + /* i2c reset */ + ret = fs210x_reg_write(fs210x, FS210X_10H_PWRCTRL, + FS210X_10H_I2C_RESET); + if (ret) + goto tag_power_down; + + /* Wait >= 10ms for i2c resetting */ + usleep_range(10000, 10050); + + /* 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; + } + + ret = fs210x_set_pcm_volume(fs210x); + +tag_power_down: + /* Power down the device */ + ret |= fs210x_reg_write(fs210x, FS210X_11H_SYSCTRL, + FS210X_11H_DPS_PWDN); + if (!ret) + fs210x->is_inited = true; + + return ret; +} + +static void fs210x_sdz_pin_set(struct fs210x_priv *fs210x, bool active) +{ + if (!fs210x || !fs210x->gpio_sdz) + return; + + /* + * The SDZ pin(Shut Down) is active low, when it is pulled low, + * it will trigger two procedures sequentially: + * - Power down the chip; + * - Reset the chip to shut down state. + * NOTE: + * The I2C communication is nack until the SDZ pin + * is pulled up and wait 10ms at least. + */ + if (active) { + gpiod_set_value_cansleep(fs210x->gpio_sdz, 1); + dev_dbg(fs210x->dev, "SDZ pin is OFF\n"); + usleep_range(30000, 30050); /* >= 30ms */ + } else { + gpiod_set_value_cansleep(fs210x->gpio_sdz, 0); + dev_dbg(fs210x->dev, "SDZ pin is ON\n"); + usleep_range(10000, 10050); /* >= 10ms */ + } +} + +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_mute(struct fs210x_priv *fs210x, bool mute) +{ + int ret; + + if (mute) { + cancel_delayed_work_sync(&fs210x->fault_check_work); + cancel_delayed_work_sync(&fs210x->start_work); + mutex_lock(&fs210x_mutex); + ret = fs210x_dev_stop(fs210x); + mutex_unlock(&fs210x_mutex); + return ret; + } + + /* + * According to the power up/down sequence of FS210x, + * the FS210x requests the I2S clock has been present + * and stable(>= 2ms) before it playing. + */ + if (fs210x->clk_bclk) { + mutex_lock(&fs210x_mutex); + ret = fs210x_dev_play(fs210x); + mutex_unlock(&fs210x_mutex); + } else { + schedule_delayed_work(&fs210x->start_work, + msecs_to_jiffies(FS210X_START_DELAY_MS)); + ret = 0; + } + + schedule_delayed_work(&fs210x->fault_check_work, + msecs_to_jiffies(FS210X_FAULT_CHECK_INTERVAL_MS)); + + return ret; +} + +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); + + dev_dbg(fs210x->dev, "fmt: %x\n", fmt); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBC_CFC: + /* Only supports slave mode */ + break; + default: + dev_err(fs210x->dev, "Only supports slave 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 */ + + dev_info(fs210x->dev, "hw params: %d-%d-%d\n", + fs210x->srate, chn_num, fs210x->bclk); + + /* The FS2105S can't support 16kHz sample rate. */ + if (fs210x->devid == FS2105S_DEVICE_ID && fs210x->srate == 16000) + return -EOPNOTSUPP; + + mutex_lock(&fs210x_mutex); + ret = fs210x_set_hw_params(fs210x); + mutex_unlock(&fs210x_mutex); + 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; + int ret; + + if (stream != SNDRV_PCM_STREAM_PLAYBACK) + return 0; + + fs210x = snd_soc_component_get_drvdata(dai->component); + + if (!fs210x->is_inited) + return -EFAULT; + + if (mute) { + ret = fs210x_mute(fs210x, true); + fs210x_bclk_set(fs210x, false); + dev_info(fs210x->dev, "playback mute\n"); + } else { + fs210x_bclk_set(fs210x, true); + ret = fs210x_mute(fs210x, false); + dev_info(fs210x->dev, "playback unmute\n"); + } + + return ret; +} + +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_mutex); + + ret = fs210x_dev_play(fs210x); + if (ret) + dev_err(fs210x->dev, "Failed to start playing: %d\n", ret); + + mutex_unlock(&fs210x_mutex); +} + +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_mutex); + if (!fs210x->is_inited || !fs210x->is_playing) { + mutex_unlock(&fs210x_mutex); + return; + } + + ret = fs210x_reg_read(fs210x, FS210X_05H_ANASTAT, &status); + mutex_unlock(&fs210x_mutex); + 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_err(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"); + + if (status != FS210X_05H_ANASTAT_OK) + fs210x_reg_dump(fs210x); + + schedule_delayed_work(&fs210x->fault_check_work, + msecs_to_jiffies(FS210X_FAULT_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_pcm_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + + uinfo->value.integer.min = FS210X_VOLUME_MIN; + uinfo->value.integer.max = FS210X_VOLUME_MAX; + + return 0; +} + +static int fs210x_pcm_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct fs210x_priv *fs210x; + int ret; + + ret = fs210x_get_drvdata_from_kctrl(kcontrol, &fs210x); + if (ret) { + pr_err("pcm_volume_get: fs210x is null\n"); + return -EINVAL; + } + + mutex_lock(&fs210x_mutex); + ucontrol->value.integer.value[0] = fs210x->vol[0]; + ucontrol->value.integer.value[1] = fs210x->vol[1]; + mutex_unlock(&fs210x_mutex); + + return 0; +} + +static int fs210x_pcm_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct fs210x_priv *fs210x; + long *pval; + int ret; + + ret = fs210x_get_drvdata_from_kctrl(kcontrol, &fs210x); + if (ret || !fs210x->dev) { + pr_err("pcm_volume_put: fs210x is null\n"); + return -EINVAL; + } + + pval = ucontrol->value.integer.value; + if (!volume_is_valid(*pval) || !volume_is_valid(*(pval + 1))) { + dev_err(fs210x->dev, "Invalid volume: %ld-%ld\n", + *pval, *(pval + 1)); + return -EINVAL; + } + + fs210x->vol[0] = (u16)*pval; + fs210x->vol[1] = (u16)*(pval + 1); + dev_dbg(fs210x->dev, "Set volume: %d-%d\n", + fs210x->vol[0], fs210x->vol[1]); + + mutex_lock(&fs210x_mutex); + + if (fs210x->is_suspended) { + mutex_unlock(&fs210x_mutex); + return 0; + } + + ret = fs210x_set_pcm_volume(fs210x); + if (ret) + dev_err(fs210x->dev, "Failed to set volume: %d\n", ret); + + mutex_unlock(&fs210x_mutex); + + return ret; +} + +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); + dev_dbg(fs210x->dev, "Scene name.%d: %s\n", idx, name); + + 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_mutex); + /* + * 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; + dev_dbg(fs210x->dev, "Get scene index: %d\n", index); + mutex_unlock(&fs210x_mutex); + + 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; + 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_mutex); + + /* + * 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; + if (fs210x->is_suspended) { + fs210x->scene_id = scene_id; + mutex_unlock(&fs210x_mutex); + return 0; + } + + ret = fs210x_set_scene(fs210x, scene_id); + if (ret) + dev_err(fs210x->dev, "Failed to set scene: %d\n", ret); + + mutex_unlock(&fs210x_mutex); + + return ret; +} + +static const struct snd_soc_dai_ops fs210x_dai_ops = { + .set_fmt = fs210x_dai_set_fmt, + .hw_params = fs210x_dai_hw_params, + .mute_stream = fs210x_dai_mute, +}; + +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 struct snd_kcontrol_new fs210x_controls[] = { + FS_SOC_ENUM_EXT("PCM Playback Volume", + fs210x_pcm_volume_info, + fs210x_pcm_volume_get, + fs210x_pcm_volume_put), +}; + +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("AIF IN", "Playback", 0, SND_SOC_NOPM, 0, 0), + 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_optional_controls(struct fs210x_priv *fs210x, + struct snd_soc_component *cmpnt) +{ + int count; + + if (!fs210x || !cmpnt) + return -EINVAL; + + /* + * 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; + + count = ARRAY_SIZE(fs210x_scene_control); + + return snd_soc_add_component_controls(cmpnt, + fs210x_scene_control, + count); +} + +static int fs210x_get_bclk(struct fs210x_priv *fs210x, + struct snd_soc_component *cmpnt) +{ + struct clk *bclk; + int ret; + + bclk = devm_clk_get(fs210x->dev, "bclk"); + if (IS_ERR_OR_NULL(bclk)) { + ret = bclk ? PTR_ERR(bclk) : -ENODATA; + if (ret == -EPROBE_DEFER) + return ret; + /* + * If the SOC doesn't have the bclk clock source, + * we skip setting the bclk clock. + */ + return 0; + } + + fs210x->clk_bclk = bclk; + dev_dbg(fs210x->dev, "Got I2S bclk clock\n"); + + return 0; +} + +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) { + dev_err(fs210x->dev, "Failed to load firmware: %d\n", ret); + return ret; + } + + ret = fs210x_add_optional_controls(fs210x, cmpnt); + if (ret) { + dev_err(fs210x->dev, "Failed to add opt-ctrl: %d\n", ret); + return ret; + } + + INIT_DELAYED_WORK(&fs210x->fault_check_work, fs210x_fault_check_work); + INIT_DELAYED_WORK(&fs210x->start_work, fs210x_start_work); + + fs210x_get_bclk(fs210x, cmpnt); + + mutex_lock(&fs210x_mutex); + ret = fs210x_init_chip(fs210x); + mutex_unlock(&fs210x_mutex); + if (ret) + dev_err(fs210x->dev, "Failed to probe: %d\n", ret); + + 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); + + dev_dbg(fs210x->dev, "Codec removed\n"); +} + +#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; + + cancel_delayed_work_sync(&fs210x->start_work); + cancel_delayed_work_sync(&fs210x->fault_check_work); + + mutex_lock(&fs210x_mutex); + fs210x->cur_scene = NULL; + fs210x->is_inited = false; + fs210x->is_playing = false; + fs210x->is_suspended = true; + + fs210x_sdz_pin_set(fs210x, true); + mutex_unlock(&fs210x_mutex); + + ret = regulator_bulk_disable(FS210X_NUM_SUPPLIES, fs210x->supplies); + if (ret) { + dev_err(fs210x->dev, "Failed to suspend: %d\n", ret); + return ret; + } + + dev_info(fs210x->dev, "pm suspended\n"); + + 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_mutex); + fs210x_sdz_pin_set(fs210x, false); + + fs210x->is_suspended = false; + ret = fs210x_init_chip(fs210x); + mutex_unlock(&fs210x_mutex); + + dev_info(fs210x->dev, "pm resumed\n"); + + return ret; +} +#else +#define fs210x_suspend NULL +#define fs210x_resume NULL +#endif // CONFIG_PM + +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), + .use_pmdown_time = 1, +}; + +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_NONE, +}; + +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_OR_NULL(fs210x->gpio_sdz)) { + ret = fs210x->gpio_sdz ? PTR_ERR(fs210x->gpio_sdz) : -ENODATA; + fs210x->gpio_sdz = NULL; + if (ret == -EPROBE_DEFER) + return ret; + dev_dbg(fs210x->dev, "Assuming reset-gpios is unused\n"); + } else { + dev_dbg(fs210x->dev, "reset-gpios: %d\n", + desc_to_gpio(fs210x->gpio_sdz)); + } + + 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) { + dev_err(fs210x->dev, "Failed to get supplies: %d\n", ret); + return ret; + } + + return 0; +} + +static int fs210x_parse_platdata(struct fs210x_priv *fs210x) +{ + struct fs210x_platform_data *pdata; + int ret; + + pdata = &fs210x->pdata; + ret = fs210x_parse_dts(fs210x, pdata); + if (ret) { + dev_err(fs210x->dev, "Failed to parse dts: %d\n", ret); + return ret; + } + + return 0; +} + +static void fs210x_deinit(struct fs210x_priv *fs210x) +{ + fs210x_sdz_pin_set(fs210x, true); + regulator_bulk_disable(FS210X_NUM_SUPPLIES, fs210x->supplies); +} + +static int fs210x_init(struct fs210x_priv *fs210x) +{ + int ret; + + ret = fs210x_parse_platdata(fs210x); + if (ret) { + dev_err(fs210x->dev, "Failed to parse platdata: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(FS210X_NUM_SUPPLIES, fs210x->supplies); + if (ret) { + dev_err(fs210x->dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + + /* Make sure the SDZ pin is pulled down enough time. */ + usleep_range(10000, 10050); /* >= 10ms */ + fs210x_sdz_pin_set(fs210x, false); + + 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->vol[0] = FS210X_VOLUME_MAX; + fs210x->vol[1] = FS210X_VOLUME_MAX; + + return 0; +} + +static int fs210x_register_snd_component(struct fs210x_priv *fs210x) +{ + struct snd_soc_dai_driver *dai_drv; + int ret; + + dai_drv = devm_kmemdup(fs210x->dev, &fs210x_dai, + sizeof(fs210x_dai), GFP_KERNEL); + if (!dai_drv) + return -ENOMEM; + + 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 int fs210x_i2c_probe(struct i2c_client *client) +{ + struct fs210x_priv *fs210x; + int ret; + + dev_info(&client->dev, "version: %s\n", FS210X_DRV_VERSION); + + 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_OR_NULL(fs210x->regmap)) { + dev_err(fs210x->dev, "Failed to get regmap\n"); + ret = fs210x->regmap ? PTR_ERR(fs210x->regmap) : -ENODATA; + return ret; + } + + mutex_lock(&fs210x_mutex); + ret = fs210x_init(fs210x); + mutex_unlock(&fs210x_mutex); + if (ret) + return ret; + + ret = fs210x_register_snd_component(fs210x); + if (ret) { + fs210x_deinit(fs210x); + return ret; + } + + 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); + +#ifdef CONFIG_OF +static const struct of_device_id fs210x_of_match[] = { + { .compatible = "foursemi,fs2104", }, + { .compatible = "foursemi,fs2105s", }, + {}, +}; +MODULE_DEVICE_TABLE(of, fs210x_of_match); +#endif // CONFIG_OF + +static struct i2c_driver fs210x_i2c_driver = { + .driver = { + .name = "fs210x", + .of_match_table = of_match_ptr(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_VERSION(FS210X_DRV_VERSION); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/fs210x.h b/sound/soc/codecs/fs210x.h new file mode 100644 index 000000000..a64f8d37f --- /dev/null +++ b/sound/soc/codecs/fs210x.h @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * fs210x.h -- Driver for the FS2104/5S Audio Amplifier + * + * Copyright (C) 2016-2025 Shanghai FourSemi Semiconductor Co.,Ltd. + */ + +#ifndef __FS210X_H__ +#define __FS210X_H__ + +#define FS210X_00H_STATUS 0x00 +#define FS210X_03H_DEVID 0x03 +#define FS210X_05H_ANASTAT 0x05 +#define FS210X_06H_DIGSTAT 0x06 +#define FS210X_0BH_ACCKEY 0x0B +#define FS210X_10H_PWRCTRL 0x10 +#define FS210X_11H_SYSCTRL 0x11 +#define FS210X_17H_I2SCTRL 0x17 +#define FS210X_30H_DACCTRL 0x30 +#define FS210X_39H_LVOLCTRL 0x39 +#define FS210X_3AH_RVOLCTRL 0x3A +#define FS210X_42H_DACEQWL 0x42 +#define FS210X_46H_DACEQA 0x46 +#define FS210X_A1H_PLLCTRL1 0xA1 +#define FS210X_A2H_PLLCTRL2 0xA2 +#define FS210X_A3H_PLLCTRL3 0xA3 +#define FS210X_ABH_INTSTAT 0xAB +#define FS210X_ACH_INTSTATR 0xAC + +#define FS210X_05H_PVDD_SHIFT 14 +#define FS210X_05H_PVDD_MASK BIT(14) +#define FS210X_05H_OCDL_SHIFT 13 +#define FS210X_05H_OCDL_MASK BIT(13) +#define FS210X_05H_UVDL_SHIFT 12 +#define FS210X_05H_UVDL_MASK BIT(12) +#define FS210X_05H_OVDL_SHIFT 11 +#define FS210X_05H_OVDL_MASK BIT(11) +#define FS210X_05H_OTPDL_SHIFT 10 +#define FS210X_05H_OTPDL_MASK BIT(10) +#define FS210X_05H_OCRDL_SHIFT 9 +#define FS210X_05H_OCRDL_MASK BIT(9) +#define FS210X_05H_OCLDL_SHIFT 8 +#define FS210X_05H_OCLDL_MASK BIT(8) +#define FS210X_05H_DCRDL_SHIFT 7 +#define FS210X_05H_DCRDL_MASK BIT(7) +#define FS210X_05H_DCLDL_SHIFT 6 +#define FS210X_05H_DCLDL_MASK BIT(6) +#define FS210X_05H_SRDL_SHIFT 5 +#define FS210X_05H_SRDL_MASK BIT(5) +#define FS210X_05H_OTWDL_SHIFT 4 +#define FS210X_05H_OTWDL_MASK BIT(4) +#define FS210X_05H_AMPS_SHIFT 3 +#define FS210X_05H_AMPS_MASK BIT(3) +#define FS210X_05H_PLLS_SHIFT 1 +#define FS210X_05H_PLLS_MASK BIT(1) +#define FS210X_05H_ANAS_SHIFT 0 +#define FS210X_05H_ANAS_MASK BIT(0) +#define FS210X_17H_I2SSR_SHIFT 12 +#define FS210X_17H_I2SSR_MASK GENMASK(15, 12) +#define FS210X_30H_RMUTE_SHIFT 8 +#define FS210X_30H_LMUTE_SHIFT 4 +#define FS210X_39H_VOL_SHIFT 6 +#define FS210X_3AH_VOL_SHIFT 6 +#define FS2105S_39H_VOL_SHIFT 7 +#define FS2105S_3AH_VOL_SHIFT 7 + +#define FS210X_05H_ANASTAT_OK 0x400B +#define FS210X_0BH_ACCKEY_ON 0x0091 +#define FS210X_0BH_ACCKEY_OFF 0x0000 +#define FS210X_10H_I2C_RESET 0x0002 +#define FS210X_11H_DPS_HIZ 0x0100 +#define FS210X_11H_DPS_PWDN 0x0000 +#define FS210X_11H_DPS_PLAY 0x0300 +#define FS210X_46H_CAM_BURST_L 0x8000 +#define FS210X_46H_CAM_BURST_R 0x8200 +#define FS2105S_46H_CAM_BURST_W 0x8400 +#define FS210X_46H_CAM_CLEAR 0x0000 + +#endif /* __FS210X_H__ */ -- 2.17.1 ^ permalink raw reply related [flat|nested] 37+ messages in thread
* Re: [PATCH v2 4/4] ASoC: codecs: Add FourSemi FS2104/5S audio amplifier driver 2025-07-08 11:29 ` [PATCH v2 4/4] ASoC: codecs: Add FourSemi FS2104/5S audio amplifier driver Nick Li @ 2025-07-09 10:55 ` Krzysztof Kozlowski 2025-07-10 7:56 ` Nick Li 2025-07-09 11:29 ` Mark Brown 1 sibling, 1 reply; 37+ messages in thread From: Krzysztof Kozlowski @ 2025-07-09 10:55 UTC (permalink / raw) To: Nick Li Cc: lgirdwood, broonie, robh, krzk+dt, conor+dt, perex, tiwai, xiaoming.yang, danyang.zheng, like.xy, linux-sound, devicetree, linux-kernel On Tue, Jul 08, 2025 at 07:29:01PM +0800, Nick Li wrote: > @@ -564,6 +565,7 @@ obj-$(CONFIG_SND_SOC_ES8375) += snd-soc-es8375.o > obj-$(CONFIG_SND_SOC_ES8389) += snd-soc-es8389.o > obj-$(CONFIG_SND_SOC_FRAMER) += snd-soc-framer.o > obj-$(CONFIG_SND_SOC_FS_AMP_LIB)+= snd-soc-fs-amp-lib.o > +obj-$(CONFIG_SND_SOC_FS210X) += snd-soc-fs210x.o > obj-$(CONFIG_SND_SOC_GTM601) += snd-soc-gtm601.o > obj-$(CONFIG_SND_SOC_HDAC_HDMI) += snd-soc-hdac-hdmi.o > obj-$(CONFIG_SND_SOC_HDAC_HDA) += snd-soc-hdac-hda.o > diff --git a/sound/soc/codecs/fs210x.c b/sound/soc/codecs/fs210x.c > new file mode 100644 > index 000000000..5176b3399 > --- /dev/null > +++ b/sound/soc/codecs/fs210x.c > @@ -0,0 +1,1610 @@ > +// 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/module.h> > +#include <linux/mutex.h> > +#include <linux/of.h> > +#include <linux/of_gpio.h> Where do you use it? > +#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_DRV_VERSION "v1.0.6" > +#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_VOLUME_MIN 0 > +#define FS210X_VOLUME_MAX 511 > +#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; > + unsigned int bclk; > + unsigned int srate; > + int scene_id; > + u16 devid; > + u16 vol[2]; /* L/R channels volume */ > + bool is_inited; > + bool is_suspended; > + bool is_bclk_on; > + bool is_playing; > +}; > + > +static DEFINE_MUTEX(fs210x_mutex); Why is this file-scope? Why two independent codecs cannot work in parallel? > + > +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->clk_bclk) > + return 0; > + > + if ((fs210x->is_bclk_on ^ on) == 0) > + return 0; > + > + dev_dbg(fs210x->dev, "bclk switch %s\n", on ? "on" : "off"); > + > + if (on) { > + clk_set_rate(fs210x->clk_bclk, fs210x->bclk); > + ret = clk_prepare_enable(fs210x->clk_bclk); > + fs210x->is_bclk_on = true; > + usleep_range(2000, 2050); /* >= 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; > + } > + > + dev_dbg(fs210x->dev, "RW: %02x %04x\n", reg, val); > + > + return 0; > +} ... > + > +static int fs210x_add_optional_controls(struct fs210x_priv *fs210x, > + struct snd_soc_component *cmpnt) > +{ > + int count; > + > + if (!fs210x || !cmpnt) > + return -EINVAL; > + > + /* > + * 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; > + > + count = ARRAY_SIZE(fs210x_scene_control); > + > + return snd_soc_add_component_controls(cmpnt, > + fs210x_scene_control, > + count); > +} > + > +static int fs210x_get_bclk(struct fs210x_priv *fs210x, > + struct snd_soc_component *cmpnt) > +{ > + struct clk *bclk; > + int ret; > + > + bclk = devm_clk_get(fs210x->dev, "bclk"); > + if (IS_ERR_OR_NULL(bclk)) { > + ret = bclk ? PTR_ERR(bclk) : -ENODATA; Same pattern as regulators, eh... > + if (ret == -EPROBE_DEFER) No. Stop handling own probe deferrals. Look how other drivers do it. > + return ret; > + /* > + * If the SOC doesn't have the bclk clock source, > + * we skip setting the bclk clock. > + */ > + return 0; What is the point of this entire code? You got NULL, so assign NULL. Can clk API handle NULLs? Answer this to yourself and write obvious, simple code. > + } > + > + fs210x->clk_bclk = bclk; > + dev_dbg(fs210x->dev, "Got I2S bclk clock\n"); Drop. Really, your debugs here and further are meaningless. You debug static, well know, things - DTB. No, debug unexpected pieces, not something which is 100% predictable because DT schema told you this already. > + > + return 0; > +} > + > +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) { > + dev_err(fs210x->dev, "Failed to load firmware: %d\n", ret); > + return ret; > + } > + > + ret = fs210x_add_optional_controls(fs210x, cmpnt); > + if (ret) { > + dev_err(fs210x->dev, "Failed to add opt-ctrl: %d\n", ret); > + return ret; > + } > + > + INIT_DELAYED_WORK(&fs210x->fault_check_work, fs210x_fault_check_work); > + INIT_DELAYED_WORK(&fs210x->start_work, fs210x_start_work); > + > + fs210x_get_bclk(fs210x, cmpnt); > + > + mutex_lock(&fs210x_mutex); > + ret = fs210x_init_chip(fs210x); > + mutex_unlock(&fs210x_mutex); > + if (ret) > + dev_err(fs210x->dev, "Failed to probe: %d\n", ret); Oh wait, it is the FOURTH time you print same error message. Sorry, this error handling is terrible. Obfuscated and overcomplicated. Error should be printed only once. Look at other recent codecs. > + > + 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); > + > + dev_dbg(fs210x->dev, "Codec removed\n"); No, drop all such simple probe enter/exit debugs. This is really useless debug. > +} > + > +#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; > + > + cancel_delayed_work_sync(&fs210x->start_work); > + cancel_delayed_work_sync(&fs210x->fault_check_work); > + > + mutex_lock(&fs210x_mutex); > + fs210x->cur_scene = NULL; > + fs210x->is_inited = false; > + fs210x->is_playing = false; > + fs210x->is_suspended = true; > + > + fs210x_sdz_pin_set(fs210x, true); > + mutex_unlock(&fs210x_mutex); > + > + ret = regulator_bulk_disable(FS210X_NUM_SUPPLIES, fs210x->supplies); > + if (ret) { > + dev_err(fs210x->dev, "Failed to suspend: %d\n", ret); > + return ret; > + } > + > + dev_info(fs210x->dev, "pm suspended\n"); No, drop all such simple probe enter/exit debugs. > + > + 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_mutex); > + fs210x_sdz_pin_set(fs210x, false); > + > + fs210x->is_suspended = false; > + ret = fs210x_init_chip(fs210x); > + mutex_unlock(&fs210x_mutex); > + > + dev_info(fs210x->dev, "pm resumed\n"); No, drop. > + > + return ret; > +} > +#else > +#define fs210x_suspend NULL > +#define fs210x_resume NULL > +#endif // CONFIG_PM > + > +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), > + .use_pmdown_time = 1, > +}; > + > +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_NONE, > +}; > + > +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_OR_NULL(fs210x->gpio_sdz)) { > + ret = fs210x->gpio_sdz ? PTR_ERR(fs210x->gpio_sdz) : -ENODATA; Weird dance. Why assigning to ret enodata? > + fs210x->gpio_sdz = NULL; > + if (ret == -EPROBE_DEFER) > + return ret; > + dev_dbg(fs210x->dev, "Assuming reset-gpios is unused\n"); > + } else { > + dev_dbg(fs210x->dev, "reset-gpios: %d\n", > + desc_to_gpio(fs210x->gpio_sdz)); > + } This is over-complicated way of getting simple optional gpio. > + > + 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) { > + dev_err(fs210x->dev, "Failed to get supplies: %d\n", ret); Syntax is return dev_err_probe. > + return ret; > + } > + > + return 0; > +} > + > +static int fs210x_parse_platdata(struct fs210x_priv *fs210x) I do not understand why you have so many functions doing simple OF parsing. fs210x_init, fs210x_parse_platdata, fs210x_parse_dts... and this one here does nothing. > +{ > + struct fs210x_platform_data *pdata; > + int ret; > + > + pdata = &fs210x->pdata; > + ret = fs210x_parse_dts(fs210x, pdata); > + if (ret) { > + dev_err(fs210x->dev, "Failed to parse dts: %d\n", ret); > + return ret; > + } > + > + return 0; > +} > + > +static void fs210x_deinit(struct fs210x_priv *fs210x) > +{ > + fs210x_sdz_pin_set(fs210x, true); > + regulator_bulk_disable(FS210X_NUM_SUPPLIES, fs210x->supplies); > +} > + > +static int fs210x_init(struct fs210x_priv *fs210x) > +{ > + int ret; > + > + ret = fs210x_parse_platdata(fs210x); > + if (ret) { > + dev_err(fs210x->dev, "Failed to parse platdata: %d\n", ret); So you print SAME ERROR three times? > + return ret; > + } > + > + ret = regulator_bulk_enable(FS210X_NUM_SUPPLIES, fs210x->supplies); > + if (ret) { > + dev_err(fs210x->dev, "Failed to enable supplies: %d\n", ret); > + return ret; > + } > + > + /* Make sure the SDZ pin is pulled down enough time. */ > + usleep_range(10000, 10050); /* >= 10ms */ > + fs210x_sdz_pin_set(fs210x, false); > + > + 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->vol[0] = FS210X_VOLUME_MAX; > + fs210x->vol[1] = FS210X_VOLUME_MAX; > + > + return 0; > +} > + > +static int fs210x_register_snd_component(struct fs210x_priv *fs210x) > +{ > + struct snd_soc_dai_driver *dai_drv; > + int ret; > + > + dai_drv = devm_kmemdup(fs210x->dev, &fs210x_dai, > + sizeof(fs210x_dai), GFP_KERNEL); > + if (!dai_drv) > + return -ENOMEM; > + > + 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 int fs210x_i2c_probe(struct i2c_client *client) > +{ > + struct fs210x_priv *fs210x; > + int ret; > + > + dev_info(&client->dev, "version: %s\n", FS210X_DRV_VERSION); > + > + 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_OR_NULL(fs210x->regmap)) { Can devm_regmap_init_i2c() return NULL? No, it cannot. > + dev_err(fs210x->dev, "Failed to get regmap\n"); > + ret = fs210x->regmap ? PTR_ERR(fs210x->regmap) : -ENODATA; Syntax is return dev_err_probe and drop NULL check. > + return ret; > + } > + > + mutex_lock(&fs210x_mutex); > + ret = fs210x_init(fs210x); > + mutex_unlock(&fs210x_mutex); Why do you need to lock it? Who and how can access device at this point? > + if (ret) > + return ret; > + > + ret = fs210x_register_snd_component(fs210x); > + if (ret) { > + fs210x_deinit(fs210x); > + return ret; > + } > + > + 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); > + > +#ifdef CONFIG_OF > +static const struct of_device_id fs210x_of_match[] = { > + { .compatible = "foursemi,fs2104", }, I asked to express the fallback. Drop this, it is complete redundant. > + { .compatible = "foursemi,fs2105s", }, > + {}, > +}; > +MODULE_DEVICE_TABLE(of, fs210x_of_match); > +#endif // CONFIG_OF All these ifdefs and of_match_ptr should be dropped, not really helpful. Best regards, Krzysztof ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v2 4/4] ASoC: codecs: Add FourSemi FS2104/5S audio amplifier driver 2025-07-09 10:55 ` Krzysztof Kozlowski @ 2025-07-10 7:56 ` Nick Li 2025-07-10 8:27 ` Krzysztof Kozlowski 0 siblings, 1 reply; 37+ messages in thread From: Nick Li @ 2025-07-10 7:56 UTC (permalink / raw) To: Krzysztof Kozlowski Cc: lgirdwood, broonie, robh, krzk+dt, conor+dt, perex, tiwai, xiaoming.yang, danyang.zheng, like.xy, linux-sound, devicetree, linux-kernel On Wed, Jul 09, 2025 at 12:55:11PM +0200, Krzysztof Kozlowski wrote: > On Tue, Jul 08, 2025 at 07:29:01PM +0800, Nick Li wrote: > > @@ -564,6 +565,7 @@ obj-$(CONFIG_SND_SOC_ES8375) += snd-soc-es8375.o > > obj-$(CONFIG_SND_SOC_ES8389) += snd-soc-es8389.o > > obj-$(CONFIG_SND_SOC_FRAMER) += snd-soc-framer.o > > obj-$(CONFIG_SND_SOC_FS_AMP_LIB)+= snd-soc-fs-amp-lib.o > > +obj-$(CONFIG_SND_SOC_FS210X) += snd-soc-fs210x.o > > obj-$(CONFIG_SND_SOC_GTM601) += snd-soc-gtm601.o > > obj-$(CONFIG_SND_SOC_HDAC_HDMI) += snd-soc-hdac-hdmi.o > > obj-$(CONFIG_SND_SOC_HDAC_HDA) += snd-soc-hdac-hda.o > > diff --git a/sound/soc/codecs/fs210x.c b/sound/soc/codecs/fs210x.c > > new file mode 100644 > > index 000000000..5176b3399 > > --- /dev/null > > +++ b/sound/soc/codecs/fs210x.c > > @@ -0,0 +1,1610 @@ > > +// 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/module.h> > > +#include <linux/mutex.h> > > +#include <linux/of.h> > > +#include <linux/of_gpio.h> > > Where do you use it? We will drop it. > > > +#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_DRV_VERSION "v1.0.6" > > +#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_VOLUME_MIN 0 > > +#define FS210X_VOLUME_MAX 511 > > +#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; > > + unsigned int bclk; > > + unsigned int srate; > > + int scene_id; > > + u16 devid; > > + u16 vol[2]; /* L/R channels volume */ > > + bool is_inited; > > + bool is_suspended; > > + bool is_bclk_on; > > + bool is_playing; > > +}; > > + > > +static DEFINE_MUTEX(fs210x_mutex); > > Why is this file-scope? Why two independent codecs cannot work in parallel? The driver module may be loaded asynchronously, if the reset pin/supplies are shared by the devices, we should protect the process of detecting devices. We tend to have each device is configured in a continuous manner. > > > + > > +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->clk_bclk) > > + return 0; > > + > > + if ((fs210x->is_bclk_on ^ on) == 0) > > + return 0; > > + > > + dev_dbg(fs210x->dev, "bclk switch %s\n", on ? "on" : "off"); > > + > > + if (on) { > > + clk_set_rate(fs210x->clk_bclk, fs210x->bclk); > > + ret = clk_prepare_enable(fs210x->clk_bclk); > > + fs210x->is_bclk_on = true; > > + usleep_range(2000, 2050); /* >= 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; > > + } > > + > > + dev_dbg(fs210x->dev, "RW: %02x %04x\n", reg, val); > > + > > + return 0; > > +} > > ... > > > + > > +static int fs210x_add_optional_controls(struct fs210x_priv *fs210x, > > + struct snd_soc_component *cmpnt) > > +{ > > + int count; > > + > > + if (!fs210x || !cmpnt) > > + return -EINVAL; > > + > > + /* > > + * 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; > > + > > + count = ARRAY_SIZE(fs210x_scene_control); > > + > > + return snd_soc_add_component_controls(cmpnt, > > + fs210x_scene_control, > > + count); > > +} > > + > > +static int fs210x_get_bclk(struct fs210x_priv *fs210x, > > + struct snd_soc_component *cmpnt) > > +{ > > + struct clk *bclk; > > + int ret; > > + > > + bclk = devm_clk_get(fs210x->dev, "bclk"); > > + if (IS_ERR_OR_NULL(bclk)) { > > + ret = bclk ? PTR_ERR(bclk) : -ENODATA; > > Same pattern as regulators, eh... Ok, we will update it. > > > + if (ret == -EPROBE_DEFER) > > No. Stop handling own probe deferrals. Look how other drivers do it. Broonie recommanded to get clock in bus probe before, and we will call it in i2c probe, is it possible the clock isn't ready when we get it in bus probe? we found some drivers do the probe deferral after getting clock. > > > + return ret; > > + /* > > + * If the SOC doesn't have the bclk clock source, > > + * we skip setting the bclk clock. > > + */ > > + return 0; > > What is the point of this entire code? You got NULL, so assign NULL. Can > clk API handle NULLs? Answer this to yourself and write obvious, simple > code. Before we calling clk API in fs210x_bclk_set, we check the clk_bclk is NULL or not firstly, In clk_set_rate/clk_prepare_enable/clk_disable_prepare, they will check it: if (!clk) or if (IS_ERR_OR_NULL(clk)) > > > + } > > + > > + fs210x->clk_bclk = bclk; > > + dev_dbg(fs210x->dev, "Got I2S bclk clock\n"); > > Drop. Really, your debugs here and further are meaningless. You debug > static, well know, things - DTB. No, debug unexpected pieces, not > something which is 100% predictable because DT schema told you this > already. We will drop the debug log. > > > + > > + return 0; > > +} > > + > > +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) { > > + dev_err(fs210x->dev, "Failed to load firmware: %d\n", ret); > > + return ret; > > + } > > + > > + ret = fs210x_add_optional_controls(fs210x, cmpnt); > > + if (ret) { > > + dev_err(fs210x->dev, "Failed to add opt-ctrl: %d\n", ret); > > + return ret; > > + } > > + > > + INIT_DELAYED_WORK(&fs210x->fault_check_work, fs210x_fault_check_work); > > + INIT_DELAYED_WORK(&fs210x->start_work, fs210x_start_work); > > + > > + fs210x_get_bclk(fs210x, cmpnt); > > + > > + mutex_lock(&fs210x_mutex); > > + ret = fs210x_init_chip(fs210x); > > + mutex_unlock(&fs210x_mutex); > > + if (ret) > > + dev_err(fs210x->dev, "Failed to probe: %d\n", ret); > > Oh wait, it is the FOURTH time you print same error message. Sorry, this > error handling is terrible. Obfuscated and overcomplicated. Error should > be printed only once. Look at other recent codecs. OK, we will check and reduce the error logs when the API has error logs. > > > + > > + 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); > > + > > + dev_dbg(fs210x->dev, "Codec removed\n"); > > No, drop all such simple probe enter/exit debugs. This is really useless > debug. We will drop the debug log. > > > +} > > + > > +#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; > > + > > + cancel_delayed_work_sync(&fs210x->start_work); > > + cancel_delayed_work_sync(&fs210x->fault_check_work); > > + > > + mutex_lock(&fs210x_mutex); > > + fs210x->cur_scene = NULL; > > + fs210x->is_inited = false; > > + fs210x->is_playing = false; > > + fs210x->is_suspended = true; > > + > > + fs210x_sdz_pin_set(fs210x, true); > > + mutex_unlock(&fs210x_mutex); > > + > > + ret = regulator_bulk_disable(FS210X_NUM_SUPPLIES, fs210x->supplies); > > + if (ret) { > > + dev_err(fs210x->dev, "Failed to suspend: %d\n", ret); > > + return ret; > > + } > > + > > + dev_info(fs210x->dev, "pm suspended\n"); > > No, drop all such simple probe enter/exit debugs. We will drop the debug log. > > > + > > + 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_mutex); > > + fs210x_sdz_pin_set(fs210x, false); > > + > > + fs210x->is_suspended = false; > > + ret = fs210x_init_chip(fs210x); > > + mutex_unlock(&fs210x_mutex); > > + > > + dev_info(fs210x->dev, "pm resumed\n"); > > No, drop. We will drop the debug log. > > > + > > + return ret; > > +} > > +#else > > +#define fs210x_suspend NULL > > +#define fs210x_resume NULL > > +#endif // CONFIG_PM > > + > > +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), > > + .use_pmdown_time = 1, > > +}; > > + > > +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_NONE, > > +}; > > + > > +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_OR_NULL(fs210x->gpio_sdz)) { > > + ret = fs210x->gpio_sdz ? PTR_ERR(fs210x->gpio_sdz) : -ENODATA; > > > Weird dance. Why assigning to ret enodata? If we get the gpio_sdz and it's NULL, we assume it's unused. If the error code is unbefitting, which one should we use? > > > + fs210x->gpio_sdz = NULL; > > + if (ret == -EPROBE_DEFER) > > + return ret; > > + dev_dbg(fs210x->dev, "Assuming reset-gpios is unused\n"); > > + } else { > > + dev_dbg(fs210x->dev, "reset-gpios: %d\n", > > + desc_to_gpio(fs210x->gpio_sdz)); > > + } > > This is over-complicated way of getting simple optional gpio. We want to cover the following possibilities: 1. The reset gpio is unused 2. The reset pin is shared by multiple deivces 3. The reset pins are independent 4. The gpio pin is unready > > > + > > + 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) { > > + dev_err(fs210x->dev, "Failed to get supplies: %d\n", ret); > > Syntax is return dev_err_probe. We can port the driver into older kernel easily without dev_err_probe, the older kernel may not have this API. If it is recommended, we will update it. > > > + return ret; > > + } > > + > > + return 0; > > +} > > + > > +static int fs210x_parse_platdata(struct fs210x_priv *fs210x) > > I do not understand why you have so many functions doing simple OF > parsing. fs210x_init, fs210x_parse_platdata, fs210x_parse_dts... and > this one here does nothing. We parsed the acpi table in parse_platdata before v1, but we didn't have the environment to check, then we removed the code. If it's possible, we will add it in the future. Also we tend to implment the functions shortly to reduce the complexity. > > > +{ > > + struct fs210x_platform_data *pdata; > > + int ret; > > + > > + pdata = &fs210x->pdata; > > + ret = fs210x_parse_dts(fs210x, pdata); > > + if (ret) { > > + dev_err(fs210x->dev, "Failed to parse dts: %d\n", ret); > > + return ret; > > + } > > + > > + return 0; > > +} > > + > > +static void fs210x_deinit(struct fs210x_priv *fs210x) > > +{ > > + fs210x_sdz_pin_set(fs210x, true); > > + regulator_bulk_disable(FS210X_NUM_SUPPLIES, fs210x->supplies); > > +} > > + > > +static int fs210x_init(struct fs210x_priv *fs210x) > > +{ > > + int ret; > > + > > + ret = fs210x_parse_platdata(fs210x); > > + if (ret) { > > + dev_err(fs210x->dev, "Failed to parse platdata: %d\n", ret); > > So you print SAME ERROR three times? We will check and reduce the logs when the api has logs. > > > + return ret; > > + } > > + > > + ret = regulator_bulk_enable(FS210X_NUM_SUPPLIES, fs210x->supplies); > > + if (ret) { > > + dev_err(fs210x->dev, "Failed to enable supplies: %d\n", ret); > > + return ret; > > + } > > + > > + /* Make sure the SDZ pin is pulled down enough time. */ > > + usleep_range(10000, 10050); /* >= 10ms */ > > + fs210x_sdz_pin_set(fs210x, false); > > + > > + 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->vol[0] = FS210X_VOLUME_MAX; > > + fs210x->vol[1] = FS210X_VOLUME_MAX; > > + > > + return 0; > > +} > > + > > +static int fs210x_register_snd_component(struct fs210x_priv *fs210x) > > +{ > > + struct snd_soc_dai_driver *dai_drv; > > + int ret; > > + > > + dai_drv = devm_kmemdup(fs210x->dev, &fs210x_dai, > > + sizeof(fs210x_dai), GFP_KERNEL); > > + if (!dai_drv) > > + return -ENOMEM; > > + > > + 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 int fs210x_i2c_probe(struct i2c_client *client) > > +{ > > + struct fs210x_priv *fs210x; > > + int ret; > > + > > + dev_info(&client->dev, "version: %s\n", FS210X_DRV_VERSION); > > + > > + 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_OR_NULL(fs210x->regmap)) { > > Can devm_regmap_init_i2c() return NULL? No, it cannot. OK, we will remove the judgment of NULL pointor > > > + dev_err(fs210x->dev, "Failed to get regmap\n"); > > + ret = fs210x->regmap ? PTR_ERR(fs210x->regmap) : -ENODATA; > > Syntax is return dev_err_probe and drop NULL check. Refer to the reply in regulator get. > > > + return ret; > > + } > > + > > + mutex_lock(&fs210x_mutex); > > + ret = fs210x_init(fs210x); > > + mutex_unlock(&fs210x_mutex); > > Why do you need to lock it? Who and how can access device at this point? If the system has more than 1 devices: the module may be loaded asynchronously, if the gpio/supplies are shared, it's better to protect the detection with lock? > > > + if (ret) > > + return ret; > > + > > + ret = fs210x_register_snd_component(fs210x); > > + if (ret) { > > + fs210x_deinit(fs210x); > > + return ret; > > + } > > + > > + 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); > > + > > +#ifdef CONFIG_OF > > +static const struct of_device_id fs210x_of_match[] = { > > + { .compatible = "foursemi,fs2104", }, > > I asked to express the fallback. Drop this, it is complete redundant. OK, will drop this line. > > > + { .compatible = "foursemi,fs2105s", }, > > + {}, > > +}; > > +MODULE_DEVICE_TABLE(of, fs210x_of_match); > > +#endif // CONFIG_OF > > All these ifdefs and of_match_ptr should be dropped, not really helpful. OK. Thanks. Best regards, Nick > > Best regards, > Krzysztof > > ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v2 4/4] ASoC: codecs: Add FourSemi FS2104/5S audio amplifier driver 2025-07-10 7:56 ` Nick Li @ 2025-07-10 8:27 ` Krzysztof Kozlowski 2025-07-10 11:20 ` Nick Li 0 siblings, 1 reply; 37+ messages in thread From: Krzysztof Kozlowski @ 2025-07-10 8:27 UTC (permalink / raw) To: Nick Li Cc: lgirdwood, broonie, robh, krzk+dt, conor+dt, perex, tiwai, xiaoming.yang, danyang.zheng, like.xy, linux-sound, devicetree, linux-kernel On 10/07/2025 09:56, Nick Li wrote: >>> +}; >>> + >>> +static DEFINE_MUTEX(fs210x_mutex); >> >> Why is this file-scope? Why two independent codecs cannot work in parallel? > > The driver module may be loaded asynchronously, > if the reset pin/supplies are shared by the devices, > we should protect the process of detecting devices. No, that's not the job of this driver. Your driver must not protect from imaginary resource conflicts, because it will not even solve it properly. It is impossible. What if foo,AS9911 codec also shares these pins supplies? And supplies needs synchronization? About pins you got point, but here clearly you are wrong. So no, drop all this global mutex, move it to device state container and DOCUMENT what it exactly protects (see checkpatch). > We tend to have each device is configured in a continuous manner. No. That's wrong assumption and wrong idea. We want the async. > >> >>> + >>> +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 }, >>> +}; >>> + ... >>> + >>> + /* >>> + * 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; >>> + >>> + count = ARRAY_SIZE(fs210x_scene_control); >>> + >>> + return snd_soc_add_component_controls(cmpnt, >>> + fs210x_scene_control, >>> + count); >>> +} >>> + >>> +static int fs210x_get_bclk(struct fs210x_priv *fs210x, >>> + struct snd_soc_component *cmpnt) >>> +{ >>> + struct clk *bclk; >>> + int ret; >>> + >>> + bclk = devm_clk_get(fs210x->dev, "bclk"); >>> + if (IS_ERR_OR_NULL(bclk)) { >>> + ret = bclk ? PTR_ERR(bclk) : -ENODATA; >> >> Same pattern as regulators, eh... > > Ok, we will update it. > >> >>> + if (ret == -EPROBE_DEFER) >> >> No. Stop handling own probe deferrals. Look how other drivers do it. > > Broonie recommanded to get clock in bus probe before, > and we will call it in i2c probe, > is it possible the clock isn't ready when we get it in bus probe? > we found some drivers do the probe deferral after getting clock. Look how others drivers do it. You should not handle it differently - you always return. The core handles deferred probe. > >> >>> + return ret; >>> + /* >>> + * If the SOC doesn't have the bclk clock source, >>> + * we skip setting the bclk clock. >>> + */ >>> + return 0; >> >> What is the point of this entire code? You got NULL, so assign NULL. Can >> clk API handle NULLs? Answer this to yourself and write obvious, simple >> code. > > Before we calling clk API in fs210x_bclk_set, we check the clk_bclk is NULL or not firstly, But it makes no sense. Clock core does it. > In clk_set_rate/clk_prepare_enable/clk_disable_prepare, they will check it: > if (!clk) or if (IS_ERR_OR_NULL(clk)) ? What does it mean? ... >>> + >>> +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_OR_NULL(fs210x->gpio_sdz)) { >>> + ret = fs210x->gpio_sdz ? PTR_ERR(fs210x->gpio_sdz) : -ENODATA; >> >> >> Weird dance. Why assigning to ret enodata? > > If we get the gpio_sdz and it's NULL, we assume it's unused. > If the error code is unbefitting, which one should we use? No error code. You requested optional for a reason. > >> >>> + fs210x->gpio_sdz = NULL; >>> + if (ret == -EPROBE_DEFER) >>> + return ret; >>> + dev_dbg(fs210x->dev, "Assuming reset-gpios is unused\n"); >>> + } else { >>> + dev_dbg(fs210x->dev, "reset-gpios: %d\n", >>> + desc_to_gpio(fs210x->gpio_sdz)); >>> + } >> >> This is over-complicated way of getting simple optional gpio. > > We want to cover the following possibilities: > 1. The reset gpio is unused And simple optional call is all you need. > 2. The reset pin is shared by multiple deivces You cannot. They cannot be shared, try by yourself. It is not a supported setup. You can switch to reset gpio driver, see my slides from last year OSSNA. > 3. The reset pins are independent I don't understand that. > 4. The gpio pin is unready There is no such thing. The only thing you need to do is devm_gpiod_get_optional(), if IS_ERR() return dev_err_probe. ONLY. For shared GPIOs, you cannot use it at all, see reset gpios driver usecases in some Qcom WSA codecs. > >> >>> + >>> + 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) { >>> + dev_err(fs210x->dev, "Failed to get supplies: %d\n", ret); >> >> Syntax is return dev_err_probe. > > We can port the driver into older kernel easily without dev_err_probe, But we don't want that. We work only on upstream. > the older kernel may not have this API. No, we must not accept poor code because you have customer who wants to work on obsolete and buggy and vulnerable kernel. > If it is recommended, we will update it. It is really, really a strong requirement. Actually, it is beneficial that it won't be possible to port to ancient kernels, because you won't be tempted to use some 10 year old patterns in other places. > >> >>> + return ret; >>> + } >>> + >>> + return 0; >>> +} >>> + >>> +static int fs210x_parse_platdata(struct fs210x_priv *fs210x) >> >> I do not understand why you have so many functions doing simple OF >> parsing. fs210x_init, fs210x_parse_platdata, fs210x_parse_dts... and >> this one here does nothing. > > We parsed the acpi table in parse_platdata before v1, > but we didn't have the environment to check, then we removed the code. > If it's possible, we will add it in the future. > Also we tend to implment the functions shortly to reduce the complexity. > >> >>> +{ >>> + struct fs210x_platform_data *pdata; >>> + int ret; >>> + >>> + pdata = &fs210x->pdata; >>> + ret = fs210x_parse_dts(fs210x, pdata); >>> + if (ret) { >>> + dev_err(fs210x->dev, "Failed to parse dts: %d\n", ret); >>> + return ret; >>> + } >>> + >>> + return 0; >>> +} >>> + >>> +static void fs210x_deinit(struct fs210x_priv *fs210x) >>> +{ >>> + fs210x_sdz_pin_set(fs210x, true); >>> + regulator_bulk_disable(FS210X_NUM_SUPPLIES, fs210x->supplies); >>> +} >>> + >>> +static int fs210x_init(struct fs210x_priv *fs210x) >>> +{ >>> + int ret; >>> + >>> + ret = fs210x_parse_platdata(fs210x); >>> + if (ret) { >>> + dev_err(fs210x->dev, "Failed to parse platdata: %d\n", ret); >> >> So you print SAME ERROR three times? > > We will check and reduce the logs when the api has logs. > >> >>> + return ret; >>> + } >>> + >>> + ret = regulator_bulk_enable(FS210X_NUM_SUPPLIES, fs210x->supplies); >>> + if (ret) { >>> + dev_err(fs210x->dev, "Failed to enable supplies: %d\n", ret); >>> + return ret; >>> + } >>> + >>> + /* Make sure the SDZ pin is pulled down enough time. */ >>> + usleep_range(10000, 10050); /* >= 10ms */ >>> + fs210x_sdz_pin_set(fs210x, false); >>> + >>> + 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->vol[0] = FS210X_VOLUME_MAX; >>> + fs210x->vol[1] = FS210X_VOLUME_MAX; >>> + >>> + return 0; >>> +} >>> + >>> +static int fs210x_register_snd_component(struct fs210x_priv *fs210x) >>> +{ >>> + struct snd_soc_dai_driver *dai_drv; >>> + int ret; >>> + >>> + dai_drv = devm_kmemdup(fs210x->dev, &fs210x_dai, >>> + sizeof(fs210x_dai), GFP_KERNEL); >>> + if (!dai_drv) >>> + return -ENOMEM; >>> + >>> + 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 int fs210x_i2c_probe(struct i2c_client *client) >>> +{ >>> + struct fs210x_priv *fs210x; >>> + int ret; >>> + >>> + dev_info(&client->dev, "version: %s\n", FS210X_DRV_VERSION); >>> + >>> + 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_OR_NULL(fs210x->regmap)) { >> >> Can devm_regmap_init_i2c() return NULL? No, it cannot. > > OK, we will remove the judgment of NULL pointor > >> >>> + dev_err(fs210x->dev, "Failed to get regmap\n"); >>> + ret = fs210x->regmap ? PTR_ERR(fs210x->regmap) : -ENODATA; >> >> Syntax is return dev_err_probe and drop NULL check. > > Refer to the reply in regulator get. > >> >>> + return ret; >>> + } >>> + >>> + mutex_lock(&fs210x_mutex); >>> + ret = fs210x_init(fs210x); >>> + mutex_unlock(&fs210x_mutex); >> >> Why do you need to lock it? Who and how can access device at this point? > > If the system has more than 1 devices: > the module may be loaded asynchronously, if the gpio/supplies are shared, What? No. It's just cannot happen. Core handles it. > it's better to protect the detection with lock? You protected here nothing. 1. Concurrent SHARED GPIO reset: you replaced concurrent into step-by-step-breaking-your-device-because-other-just-probed-and-reset-you 2. supplies: core handles it. Do you see such needs anywhere in other recent codecs who share pins? I understand it might be tricky to find it... but trust me, there is no except legacy poor choices... Best regards, Krzysztof ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v2 4/4] ASoC: codecs: Add FourSemi FS2104/5S audio amplifier driver 2025-07-10 8:27 ` Krzysztof Kozlowski @ 2025-07-10 11:20 ` Nick Li 0 siblings, 0 replies; 37+ messages in thread From: Nick Li @ 2025-07-10 11:20 UTC (permalink / raw) To: Krzysztof Kozlowski Cc: lgirdwood, broonie, robh, krzk+dt, conor+dt, perex, tiwai, xiaoming.yang, danyang.zheng, like.xy, linux-sound, devicetree, linux-kernel On Thu, Jul 10, 2025 at 10:27:15AM +0200, Krzysztof Kozlowski wrote: > On 10/07/2025 09:56, Nick Li wrote: > >>> +}; > >>> + > >>> +static DEFINE_MUTEX(fs210x_mutex); > >> > >> Why is this file-scope? Why two independent codecs cannot work in parallel? > > > > The driver module may be loaded asynchronously, > > if the reset pin/supplies are shared by the devices, > > we should protect the process of detecting devices. > > No, that's not the job of this driver. Your driver must not protect from > imaginary resource conflicts, because it will not even solve it > properly. It is impossible. What if foo,AS9911 codec also shares these > pins supplies? > > And supplies needs synchronization? About pins you got point, but here > clearly you are wrong. > > So no, drop all this global mutex, move it to device state container and > DOCUMENT what it exactly protects (see checkpatch). OK, we will use a private lock for mutual exclusion with work queues/mixers/... > > > > We tend to have each device is configured in a continuous manner. > > No. That's wrong assumption and wrong idea. We want the async. OK. > > > > >> > >>> + > >>> +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 }, > >>> +}; > >>> + > > > ... > > >>> + > >>> + /* > >>> + * 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; > >>> + > >>> + count = ARRAY_SIZE(fs210x_scene_control); > >>> + > >>> + return snd_soc_add_component_controls(cmpnt, > >>> + fs210x_scene_control, > >>> + count); > >>> +} > >>> + > >>> +static int fs210x_get_bclk(struct fs210x_priv *fs210x, > >>> + struct snd_soc_component *cmpnt) > >>> +{ > >>> + struct clk *bclk; > >>> + int ret; > >>> + > >>> + bclk = devm_clk_get(fs210x->dev, "bclk"); > >>> + if (IS_ERR_OR_NULL(bclk)) { > >>> + ret = bclk ? PTR_ERR(bclk) : -ENODATA; > >> > >> Same pattern as regulators, eh... > > > > Ok, we will update it. > > > >> > >>> + if (ret == -EPROBE_DEFER) > >> > >> No. Stop handling own probe deferrals. Look how other drivers do it. > > > > Broonie recommanded to get clock in bus probe before, > > and we will call it in i2c probe, > > is it possible the clock isn't ready when we get it in bus probe? > > we found some drivers do the probe deferral after getting clock. > > Look how others drivers do it. You should not handle it differently - > you always return. The core handles deferred probe. OK, the core will handle it, we don't need to do. > > > > >> > >>> + return ret; > >>> + /* > >>> + * If the SOC doesn't have the bclk clock source, > >>> + * we skip setting the bclk clock. > >>> + */ > >>> + return 0; > >> > >> What is the point of this entire code? You got NULL, so assign NULL. Can > >> clk API handle NULLs? Answer this to yourself and write obvious, simple > >> code. > > > > Before we calling clk API in fs210x_bclk_set, we check the clk_bclk is NULL or not firstly, > > But it makes no sense. Clock core does it. > > > In clk_set_rate/clk_prepare_enable/clk_disable_prepare, they will check it: > > if (!clk) or if (IS_ERR_OR_NULL(clk)) > > ? What does it mean? Uh, I got it. The clock core does the checking. > > ... > > >>> + > >>> +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_OR_NULL(fs210x->gpio_sdz)) { > >>> + ret = fs210x->gpio_sdz ? PTR_ERR(fs210x->gpio_sdz) : -ENODATA; > >> > >> > >> Weird dance. Why assigning to ret enodata? > > > > If we get the gpio_sdz and it's NULL, we assume it's unused. > > If the error code is unbefitting, which one should we use? > > No error code. You requested optional for a reason. OK, just assign the PTR_ERR(xxx) to ret > > > > >> > >>> + fs210x->gpio_sdz = NULL; > >>> + if (ret == -EPROBE_DEFER) > >>> + return ret; > >>> + dev_dbg(fs210x->dev, "Assuming reset-gpios is unused\n"); > >>> + } else { > >>> + dev_dbg(fs210x->dev, "reset-gpios: %d\n", > >>> + desc_to_gpio(fs210x->gpio_sdz)); > >>> + } > >> > >> This is over-complicated way of getting simple optional gpio. > > > > We want to cover the following possibilities: > > 1. The reset gpio is unused > > And simple optional call is all you need. OK. > > > 2. The reset pin is shared by multiple deivces > > You cannot. They cannot be shared, try by yourself. It is not a > supported setup. It will report -EBUSY when we request the same gpio. > > You can switch to reset gpio driver, see my slides from last year OSSNA. OK, I have the honour to read it. > > > 3. The reset pins are independent > > I don't understand that. Each device has its own reset pin, it's a general case. > > > 4. The gpio pin is unready > > There is no such thing. OK. > > The only thing you need to do is devm_gpiod_get_optional(), if IS_ERR() > return dev_err_probe. > > ONLY. OK. > > For shared GPIOs, you cannot use it at all, see reset gpios driver > usecases in some Qcom WSA codecs. OK, I have found the driver and will learn about it later. > > > > >> > >>> + > >>> + 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) { > >>> + dev_err(fs210x->dev, "Failed to get supplies: %d\n", ret); > >> > >> Syntax is return dev_err_probe. > > > > We can port the driver into older kernel easily without dev_err_probe, > > But we don't want that. We work only on upstream. OK. > > > the older kernel may not have this API. > > No, we must not accept poor code because you have customer who wants to > work on obsolete and buggy and vulnerable kernel. OK. > > > If it is recommended, we will update it. > > It is really, really a strong requirement. Actually, it is beneficial > that it won't be possible to port to ancient kernels, because you won't > be tempted to use some 10 year old patterns in other places. OK. > > > > >> > >>> + return ret; > >>> + } > >>> + > >>> + return 0; > >>> +} > >>> + > >>> +static int fs210x_parse_platdata(struct fs210x_priv *fs210x) > >> > >> I do not understand why you have so many functions doing simple OF > >> parsing. fs210x_init, fs210x_parse_platdata, fs210x_parse_dts... and > >> this one here does nothing. > > > > We parsed the acpi table in parse_platdata before v1, > > but we didn't have the environment to check, then we removed the code. > > If it's possible, we will add it in the future. > > Also we tend to implment the functions shortly to reduce the complexity. > > > >> > >>> +{ > >>> + struct fs210x_platform_data *pdata; > >>> + int ret; > >>> + > >>> + pdata = &fs210x->pdata; > >>> + ret = fs210x_parse_dts(fs210x, pdata); > >>> + if (ret) { > >>> + dev_err(fs210x->dev, "Failed to parse dts: %d\n", ret); > >>> + return ret; > >>> + } > >>> + > >>> + return 0; > >>> +} > >>> + > >>> +static void fs210x_deinit(struct fs210x_priv *fs210x) > >>> +{ > >>> + fs210x_sdz_pin_set(fs210x, true); > >>> + regulator_bulk_disable(FS210X_NUM_SUPPLIES, fs210x->supplies); > >>> +} > >>> + > >>> +static int fs210x_init(struct fs210x_priv *fs210x) > >>> +{ > >>> + int ret; > >>> + > >>> + ret = fs210x_parse_platdata(fs210x); > >>> + if (ret) { > >>> + dev_err(fs210x->dev, "Failed to parse platdata: %d\n", ret); > >> > >> So you print SAME ERROR three times? > > > > We will check and reduce the logs when the api has logs. > > > >> > >>> + return ret; > >>> + } > >>> + > >>> + ret = regulator_bulk_enable(FS210X_NUM_SUPPLIES, fs210x->supplies); > >>> + if (ret) { > >>> + dev_err(fs210x->dev, "Failed to enable supplies: %d\n", ret); > >>> + return ret; > >>> + } > >>> + > >>> + /* Make sure the SDZ pin is pulled down enough time. */ > >>> + usleep_range(10000, 10050); /* >= 10ms */ > >>> + fs210x_sdz_pin_set(fs210x, false); > >>> + > >>> + 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->vol[0] = FS210X_VOLUME_MAX; > >>> + fs210x->vol[1] = FS210X_VOLUME_MAX; > >>> + > >>> + return 0; > >>> +} > >>> + > >>> +static int fs210x_register_snd_component(struct fs210x_priv *fs210x) > >>> +{ > >>> + struct snd_soc_dai_driver *dai_drv; > >>> + int ret; > >>> + > >>> + dai_drv = devm_kmemdup(fs210x->dev, &fs210x_dai, > >>> + sizeof(fs210x_dai), GFP_KERNEL); > >>> + if (!dai_drv) > >>> + return -ENOMEM; > >>> + > >>> + 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 int fs210x_i2c_probe(struct i2c_client *client) > >>> +{ > >>> + struct fs210x_priv *fs210x; > >>> + int ret; > >>> + > >>> + dev_info(&client->dev, "version: %s\n", FS210X_DRV_VERSION); > >>> + > >>> + 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_OR_NULL(fs210x->regmap)) { > >> > >> Can devm_regmap_init_i2c() return NULL? No, it cannot. > > > > OK, we will remove the judgment of NULL pointor > > > >> > >>> + dev_err(fs210x->dev, "Failed to get regmap\n"); > >>> + ret = fs210x->regmap ? PTR_ERR(fs210x->regmap) : -ENODATA; > >> > >> Syntax is return dev_err_probe and drop NULL check. > > > > Refer to the reply in regulator get. > > > >> > >>> + return ret; > >>> + } > >>> + > >>> + mutex_lock(&fs210x_mutex); > >>> + ret = fs210x_init(fs210x); > >>> + mutex_unlock(&fs210x_mutex); > >> > >> Why do you need to lock it? Who and how can access device at this point? > > > > If the system has more than 1 devices: > > the module may be loaded asynchronously, if the gpio/supplies are shared, > > What? No. It's just cannot happen. Core handles it. OK. > > > it's better to protect the detection with lock? > > You protected here nothing. > 1. Concurrent SHARED GPIO reset: you replaced concurrent into > step-by-step-breaking-your-device-because-other-just-probed-and-reset-you > 2. supplies: core handles it. > > Do you see such needs anywhere in other recent codecs who share pins? I > understand it might be tricky to find it... but trust me, there is no > except legacy poor choices... Hardware engineer wants all the reset(or irq) pins are shared, they explain the soc has not enough gpio pins to be used, especially when we use 4~8 audio amplifiers in a system, if we use the separate reset & interrupt pins, they're too much pins. We try to drop the related logic of shared pins. Thank you very much. Best regards, Nick > > Best regards, > Krzysztof > ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v2 4/4] ASoC: codecs: Add FourSemi FS2104/5S audio amplifier driver 2025-07-08 11:29 ` [PATCH v2 4/4] ASoC: codecs: Add FourSemi FS2104/5S audio amplifier driver Nick Li 2025-07-09 10:55 ` Krzysztof Kozlowski @ 2025-07-09 11:29 ` Mark Brown 2025-07-10 8:57 ` Nick Li 1 sibling, 1 reply; 37+ messages in thread From: Mark Brown @ 2025-07-09 11:29 UTC (permalink / raw) To: Nick Li Cc: lgirdwood, robh, krzk+dt, conor+dt, perex, tiwai, xiaoming.yang, danyang.zheng, like.xy, linux-sound, devicetree, linux-kernel [-- Attachment #1: Type: text/plain, Size: 2150 bytes --] On Tue, Jul 08, 2025 at 07:29:01PM +0800, Nick Li wrote: > The FS2104/5S are FourSemi digital audio amplifiers > with I2C control. They are Inductor-Less, Stereo, Closed-Loop, > Digital Input Class-D Power Amplifiers with Enhanced Signal Processing. This looks broadly OK - there's a few more fairly small issues below in addition to those that Krzysztof flagged. > +static int fs210x_set_pcm_volume(struct fs210x_priv *fs210x) > +{ > + u16 vol[2]; > + int ret; > + > + if (fs210x->devid == FS2105S_DEVICE_ID) { A swtich statement is better style here, it makes it easier to add more variants. > + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { _CLOCK_PROVIDER_MASK > + case SND_SOC_DAIFMT_CBC_CFC: > + /* Only supports slave mode */ consumer mode. > + dev_err(fs210x->dev, "Only supports slave mode\n"); consumer mode. > +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; There's no configuration for capture? Should the driver be setting symmetric_rates and then not reconfiguring if active instead, it looks a lot like the driver is relying on userspace starting playback before capture to configure? > +static int fs210x_pcm_volume_put(struct snd_kcontrol *kcontrol, > + struct snd_ctl_elem_value *ucontrol) > +{ > + fs210x->vol[0] = (u16)*pval; > + fs210x->vol[1] = (u16)*(pval + 1); > + dev_dbg(fs210x->dev, "Set volume: %d-%d\n", > + fs210x->vol[0], fs210x->vol[1]); > + The driver should return 1 if there's a change in the values and 0 otherwise (plus it can skip a bunch of work if there's nothing to do). The mixer-test test will spot this for you. > +static int fs210x_effect_scene_put(struct snd_kcontrol *kcontrol, > + struct snd_ctl_elem_value *ucontrol) > +{ > + ret = fs210x_set_scene(fs210x, scene_id); > + if (ret) > + dev_err(fs210x->dev, "Failed to set scene: %d\n", ret); > + > + mutex_unlock(&fs210x_mutex); > + > + return ret; Same issue with flagging changes. [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 488 bytes --] ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v2 4/4] ASoC: codecs: Add FourSemi FS2104/5S audio amplifier driver 2025-07-09 11:29 ` Mark Brown @ 2025-07-10 8:57 ` Nick Li 2025-07-10 9:29 ` Mark Brown 0 siblings, 1 reply; 37+ messages in thread From: Nick Li @ 2025-07-10 8:57 UTC (permalink / raw) To: Mark Brown Cc: lgirdwood, robh, krzk+dt, conor+dt, perex, tiwai, xiaoming.yang, danyang.zheng, like.xy, linux-sound, devicetree, linux-kernel On Wed, Jul 09, 2025 at 12:29:30PM +0100, Mark Brown wrote: > On Tue, Jul 08, 2025 at 07:29:01PM +0800, Nick Li wrote: > > > The FS2104/5S are FourSemi digital audio amplifiers > > with I2C control. They are Inductor-Less, Stereo, Closed-Loop, > > Digital Input Class-D Power Amplifiers with Enhanced Signal Processing. > > This looks broadly OK - there's a few more fairly small issues below in > addition to those that Krzysztof flagged. > > > +static int fs210x_set_pcm_volume(struct fs210x_priv *fs210x) > > +{ > > + u16 vol[2]; > > + int ret; > > + > > + if (fs210x->devid == FS2105S_DEVICE_ID) { > > A swtich statement is better style here, it makes it easier to add more > variants. Good idea. > > > + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { > > _CLOCK_PROVIDER_MASK > > > + case SND_SOC_DAIFMT_CBC_CFC: > > + /* Only supports slave mode */ > > consumer mode. You had metioned in v1, will fix next version. > > > + dev_err(fs210x->dev, "Only supports slave mode\n"); > > consumer mode. Got it. > > > +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; > > There's no configuration for capture? Should the driver be setting > symmetric_rates and then not reconfiguring if active instead, it looks a > lot like the driver is relying on userspace starting playback before > capture to configure? In capture path: The device can output EC reference signal for AEC, it can't output the signal without playing audio(start device in playback). We had set symmetric_rates = 1 in fs210x_dai > > > +static int fs210x_pcm_volume_put(struct snd_kcontrol *kcontrol, > > + struct snd_ctl_elem_value *ucontrol) > > +{ > > > + fs210x->vol[0] = (u16)*pval; > > + fs210x->vol[1] = (u16)*(pval + 1); > > + dev_dbg(fs210x->dev, "Set volume: %d-%d\n", > > + fs210x->vol[0], fs210x->vol[1]); > > + > > The driver should return 1 if there's a change in the values and 0 > otherwise (plus it can skip a bunch of work if there's nothing to do). > > The mixer-test test will spot this for you. BTW, what's the mixer-test? We tested it by using alsamixer and amixer. > > > +static int fs210x_effect_scene_put(struct snd_kcontrol *kcontrol, > > + struct snd_ctl_elem_value *ucontrol) > > +{ > > > + ret = fs210x_set_scene(fs210x, scene_id); > > + if (ret) > > + dev_err(fs210x->dev, "Failed to set scene: %d\n", ret); > > + > > + mutex_unlock(&fs210x_mutex); > > + > > + return ret; > > Same issue with flagging changes. Got it, we will update what you mentioned in here and previous mail, the next version may need to test for a few days, I'm sorry for my late response. Thanks. Best regards, Nick ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v2 4/4] ASoC: codecs: Add FourSemi FS2104/5S audio amplifier driver 2025-07-10 8:57 ` Nick Li @ 2025-07-10 9:29 ` Mark Brown 0 siblings, 0 replies; 37+ messages in thread From: Mark Brown @ 2025-07-10 9:29 UTC (permalink / raw) To: Nick Li Cc: lgirdwood, robh, krzk+dt, conor+dt, perex, tiwai, xiaoming.yang, danyang.zheng, like.xy, linux-sound, devicetree, linux-kernel [-- Attachment #1: Type: text/plain, Size: 286 bytes --] On Thu, Jul 10, 2025 at 04:57:03PM +0800, Nick Li wrote: > On Wed, Jul 09, 2025 at 12:29:30PM +0100, Mark Brown wrote: > > The mixer-test test will spot this for you. > BTW, what's the mixer-test? We tested it by using alsamixer and amixer. tools/testing/selftests/alsa/mixer-test.c [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 488 bytes --] ^ permalink raw reply [flat|nested] 37+ messages in thread
end of thread, other threads:[~2025-07-10 11:21 UTC | newest] Thread overview: 37+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2025-07-03 3:56 [PATCH v1 0/4] ASoC: codecs: Add support for FourSemi FS2104/5S Nick 2025-07-03 3:56 ` [PATCH v1 1/4] ASoC: codecs: Add library for FourSemi audio amplifiers Nick 2025-07-03 3:56 ` [PATCH v1 2/4] ASoC: codecs: Add FourSemi FS2104/5S audio amplifier driver Nick 2025-07-03 14:59 ` Mark Brown 2025-07-04 11:12 ` Nick Li 2025-07-04 14:37 ` Mark Brown 2025-07-07 8:34 ` Nick Li 2025-07-07 20:42 ` Mark Brown 2025-07-03 3:56 ` [PATCH v1 3/4] ASoC: dt-bindings: Add dt bindings for FS2104/5S audio amplifiers Nick 2025-07-03 5:48 ` Rob Herring (Arm) 2025-07-03 6:36 ` Krzysztof Kozlowski 2025-07-04 6:00 ` Nick Li 2025-07-03 7:10 ` Krzysztof Kozlowski 2025-07-03 7:15 ` Krzysztof Kozlowski 2025-07-04 7:26 ` Nick Li 2025-07-04 7:18 ` Nick Li 2025-07-03 3:56 ` [PATCH v1 4/4] dt-bindings: vendor-prefixes: Add Shanghai FourSemi Semiconductor Co.,Ltd Nick 2025-07-03 5:48 ` Rob Herring (Arm) 2025-07-08 11:28 ` [PATCH v2 0/4] ASoC: codecs: Add support for FourSemi FS2104/5S Nick Li 2025-07-08 11:28 ` [PATCH v2 1/4] dt-bindings: vendor-prefixes: Add Shanghai FourSemi Semiconductor Co.,Ltd Nick Li 2025-07-09 10:36 ` Krzysztof Kozlowski 2025-07-08 11:28 ` [PATCH v2 2/4] ASoC: dt-bindings: Add schema for FS2104/5S audio amplifiers Nick Li 2025-07-09 10:40 ` Krzysztof Kozlowski 2025-07-10 8:11 ` Nick Li 2025-07-10 8:27 ` Krzysztof Kozlowski 2025-07-10 9:26 ` Nick Li 2025-07-09 10:42 ` Krzysztof Kozlowski 2025-07-10 8:02 ` Nick Li 2025-07-08 11:29 ` [PATCH v2 3/4] ASoC: codecs: Add library for FourSemi " Nick Li 2025-07-08 11:29 ` [PATCH v2 4/4] ASoC: codecs: Add FourSemi FS2104/5S audio amplifier driver Nick Li 2025-07-09 10:55 ` Krzysztof Kozlowski 2025-07-10 7:56 ` Nick Li 2025-07-10 8:27 ` Krzysztof Kozlowski 2025-07-10 11:20 ` Nick Li 2025-07-09 11:29 ` Mark Brown 2025-07-10 8:57 ` Nick Li 2025-07-10 9:29 ` Mark Brown
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).