ASoC: codecs: Add support for FourSemi FS2104/5S

Merge series from Nick Li <nick.li@foursemi.com>:

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-rc6-v8
This commit is contained in:
Mark Brown 2025-08-12 16:30:15 +01:00
commit 54401b5d9f
No known key found for this signature in database
GPG Key ID: 24D68B725D5487D0
9 changed files with 2204 additions and 0 deletions

View File

@ -0,0 +1,101 @@
# 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
clocks:
items:
- 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
- '#sound-dai-cells'
- pvdd-supply
- dvdd-supply
- reset-gpios
- firmware-name
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>;
clocks = <&clocks 18>;
clock-names = "bclk";
#sound-dai-cells = <0>;
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>;
};
};

View File

@ -552,6 +552,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,.*":

View File

@ -9573,6 +9573,14 @@ F: lib/tests/memcpy_kunit.c
K: \bunsafe_memcpy\b
K: \b__NO_FORTIFY\b
FOURSEMI AUDIO AMPLIFIER DRIVER
M: Nick Li <nick.li@foursemi.com>
L: linux-sound@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/sound/foursemi,fs2105s.yaml
F: sound/soc/codecs/fs-amp-lib.*
F: sound/soc/codecs/fs210x.*
FPGA DFL DRIVERS
M: Xu Yilun <yilun.xu@intel.com>
R: Tom Rix <trix@redhat.com>

View File

@ -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
@ -1232,6 +1233,21 @@ 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_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.
The FS2104/5S are Inductor-Less, Stereo, Closed-Loop,
Digital Input Class-D Power Amplifiers with Enhanced Signal Processing.
The amplifiers support I2C and I2S/TDM.
config SND_SOC_GTM601
tristate 'GTM601 UMTS modem audio codec'

View File

@ -137,6 +137,8 @@ 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-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
@ -562,6 +564,8 @@ 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_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

View File

@ -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");

View File

@ -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__

1583
sound/soc/codecs/fs210x.c Normal file

File diff suppressed because it is too large Load Diff

75
sound/soc/codecs/fs210x.h Normal file
View File

@ -0,0 +1,75 @@
/* 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_0FH_I2CADDR 0x0F
#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_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__ */