diff --git a/Documentation/devicetree/bindings/sound/fsl,micfil.yaml b/Documentation/devicetree/bindings/sound/fsl,micfil.yaml index c1e9803fc113c0..c47b7a09749067 100644 --- a/Documentation/devicetree/bindings/sound/fsl,micfil.yaml +++ b/Documentation/devicetree/bindings/sound/fsl,micfil.yaml @@ -25,6 +25,7 @@ properties: - fsl,imx8mm-micfil - fsl,imx8mp-micfil - fsl,imx93-micfil + - fsl,imx943-micfil reg: maxItems: 1 diff --git a/Documentation/devicetree/bindings/sound/ti,pcm1681.yaml b/Documentation/devicetree/bindings/sound/ti,pcm1681.yaml index 5aa00617291c95..1f0e6787a74642 100644 --- a/Documentation/devicetree/bindings/sound/ti,pcm1681.yaml +++ b/Documentation/devicetree/bindings/sound/ti,pcm1681.yaml @@ -4,7 +4,7 @@ $id: http://devicetree.org/schemas/sound/ti,pcm1681.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: Texas Instruments PCM1681 8-channel PWM Processor +title: Texas Instruments PCM1681 8-channel Digital-to-Analog Converter maintainers: - Shenghao Ding diff --git a/MAINTAINERS b/MAINTAINERS index b1d33f3f7ce54f..ab4a27bfdf3c05 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8948,14 +8948,16 @@ L: linux-input@vger.kernel.org S: Maintained F: drivers/input/joystick/fsia6b.c -FOCUSRITE SCARLETT2 MIXER DRIVER (Scarlett Gen 2+ and Clarett) +FOCUSRITE CONTROL PROTOCOL/SCARLETT2 MIXER DRIVERS (Scarlett Gen 2+, Clarett, and Vocaster) M: Geoffrey D. Bennett L: linux-sound@vger.kernel.org S: Maintained -W: https://github.com/geoffreybennett/scarlett-gen2 -B: https://github.com/geoffreybennett/scarlett-gen2/issues -T: git https://github.com/geoffreybennett/scarlett-gen2.git +W: https://github.com/geoffreybennett/linux-fcp +B: https://github.com/geoffreybennett/linux-fcp/issues +T: git https://github.com/geoffreybennett/linux-fcp.git +F: include/uapi/sound/fcp.h F: include/uapi/sound/scarlett2.h +F: sound/usb/fcp.c F: sound/usb/mixer_scarlett2.c FORCEDETH GIGABIT ETHERNET DRIVER diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index 74dcccdc6482b8..55a9a3d5afa841 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -1769,6 +1769,7 @@ static bool acpi_device_enumeration_by_parent(struct acpi_device *device) {"CSC3557", }, {"INT33FE", }, {"INT3515", }, + {"TXNW2781", }, /* Non-conforming _HID for Cirrus Logic already released */ {"CLSA0100", }, {"CLSA0101", }, diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index f2843f81467515..197c79b6682834 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -1557,24 +1557,40 @@ static int _regmap_select_page(struct regmap *map, unsigned int *reg, return -EINVAL; } - /* It is possible to have selector register inside data window. - In that case, selector register is located on every page and - it needs no page switching, when accessed alone. */ + /* + * It is possible to have selector register inside data window. + * In that case, selector register is located on every page and it + * needs no page switching, when accessed alone. + * + * Nevertheless we should synchronize the cache values for it. + */ if (val_num > 1 || range->window_start + win_offset != range->selector_reg) { + unsigned int page_off = win_page * range->window_len; + unsigned int sel_offset = range->selector_reg - range->window_start; + unsigned int sel_register = range->range_min + page_off + sel_offset; + unsigned int val = win_page << range->selector_shift; + unsigned int mask = range->selector_mask; + /* Use separate work_buf during page switching */ orig_work_buf = map->work_buf; map->work_buf = map->selector_work_buf; - ret = _regmap_update_bits(map, range->selector_reg, - range->selector_mask, - win_page << range->selector_shift, + ret = _regmap_update_bits(map, range->selector_reg, mask, val, &page_chg, false); map->work_buf = orig_work_buf; if (ret != 0) return ret; + + /* + * If selector register has been just updated, update the respective + * virtual copy as well. + */ + if (page_chg && + in_range(range->selector_reg, range->window_start, range->window_len)) + _regmap_update_bits(map, sel_register, mask, val, NULL, false); } *reg = range->window_start + win_offset; diff --git a/drivers/platform/x86/serial-multi-instantiate.c b/drivers/platform/x86/serial-multi-instantiate.c index ed6b28505cd66f..db030b0f176a24 100644 --- a/drivers/platform/x86/serial-multi-instantiate.c +++ b/drivers/platform/x86/serial-multi-instantiate.c @@ -384,6 +384,17 @@ static const struct smi_node cs35l57_hda = { .bus_type = SMI_AUTO_DETECT, }; +static const struct smi_node tas2781_hda = { + .instances = { + { "tas2781-hda", IRQ_RESOURCE_AUTO, 0 }, + { "tas2781-hda", IRQ_RESOURCE_AUTO, 0 }, + { "tas2781-hda", IRQ_RESOURCE_AUTO, 0 }, + { "tas2781-hda", IRQ_RESOURCE_AUTO, 0 }, + {} + }, + .bus_type = SMI_AUTO_DETECT, +}; + /* * Note new device-ids must also be added to ignore_serial_bus_ids in * drivers/acpi/scan.c: acpi_device_enumeration_by_parent(). @@ -396,6 +407,7 @@ static const struct acpi_device_id smi_acpi_ids[] = { { "CSC3556", (unsigned long)&cs35l56_hda }, { "CSC3557", (unsigned long)&cs35l57_hda }, { "INT3515", (unsigned long)&int3515_data }, + { "TXNW2781", (unsigned long)&tas2781_hda }, /* Non-conforming _HID for Cirrus Logic already released */ { "CLSA0100", (unsigned long)&cs35l41_hda }, { "CLSA0101", (unsigned long)&cs35l41_hda }, diff --git a/include/sound/rawmidi.h b/include/sound/rawmidi.h index 6f2e95298fc769..6916f71335977c 100644 --- a/include/sound/rawmidi.h +++ b/include/sound/rawmidi.h @@ -191,4 +191,13 @@ long snd_rawmidi_kernel_read(struct snd_rawmidi_substream *substream, long snd_rawmidi_kernel_write(struct snd_rawmidi_substream *substream, const unsigned char *buf, long count); +/* set up the tied devices */ +static inline void snd_rawmidi_tie_devices(struct snd_rawmidi *r1, + struct snd_rawmidi *r2) +{ + /* tied_device field keeps the device+1 (so that 0 being unknown) */ + r1->tied_device = r2->device + 1; + r2->tied_device = r1->device + 1; +} + #endif /* __SOUND_RAWMIDI_H */ diff --git a/include/sound/simple_card_utils.h b/include/sound/simple_card_utils.h index f6bfd485c31a93..892f70532363e7 100644 --- a/include/sound/simple_card_utils.h +++ b/include/sound/simple_card_utils.h @@ -89,6 +89,13 @@ struct simple_util_priv { #define simple_props_to_dai_codec(props, i) ((props)->codec_dai + i) #define simple_props_to_codec_conf(props, i) ((props)->codec_conf + i) +/* has the same effect as simple_priv_to_props(). Preferred over + * simple_priv_to_props() when dealing with PCM runtime data as + * the ID stored in rtd->id may not be a valid array index. + */ +#define runtime_simple_priv_to_props(priv, rtd) \ + ((priv)->dai_props + ((rtd)->dai_link - (priv)->dai_link)) + #define for_each_prop_dlc_cpus(props, i, cpu) \ for ((i) = 0; \ ((i) < (props)->num.cpus) && \ diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h index aab57c19f62b23..a11501752637cf 100644 --- a/include/sound/soc-dai.h +++ b/include/sound/soc-dai.h @@ -193,6 +193,9 @@ int snd_soc_dai_set_channel_map(struct snd_soc_dai *dai, int snd_soc_dai_set_tristate(struct snd_soc_dai *dai, int tristate); +int snd_soc_dai_prepare(struct snd_soc_dai *dai, + struct snd_pcm_substream *substream); + /* Digital Audio Interface mute */ int snd_soc_dai_digital_mute(struct snd_soc_dai *dai, int mute, int direction); diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h index 000b36c0e2b9e8..5a049eeaeccea5 100644 --- a/include/uapi/sound/asound.h +++ b/include/uapi/sound/asound.h @@ -730,7 +730,7 @@ enum { #define SNDRV_RAWMIDI_INFO_UMP 0x00000008 #define SNDRV_RAWMIDI_INFO_STREAM_INACTIVE 0x00000010 -#define SNDRV_RAWMIDI_DEVICE_UNKNOWN -1 +#define SNDRV_RAWMIDI_DEVICE_UNKNOWN 0 struct snd_rawmidi_info { unsigned int device; /* RO/WR (control): device number */ diff --git a/include/uapi/sound/fcp.h b/include/uapi/sound/fcp.h new file mode 100644 index 00000000000000..aea428956d8fd6 --- /dev/null +++ b/include/uapi/sound/fcp.h @@ -0,0 +1,120 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * Focusrite Control Protocol Driver for ALSA + * + * Copyright (c) 2024-2025 by Geoffrey D. Bennett + */ +/* + * DOC: FCP (Focusrite Control Protocol) User-Space API + * + * This header defines the interface between the FCP kernel driver and + * user-space programs to enable the use of the proprietary features + * available in Focusrite USB audio interfaces. This includes Scarlett + * 2nd Gen, 3rd Gen, 4th Gen, Clarett USB, Clarett+, and Vocaster + * series devices. + * + * The interface is provided via ALSA's hwdep interface. Opening the + * hwdep device requires CAP_SYS_RAWIO privileges as this interface + * provides near-direct access. + * + * For details on the FCP protocol, refer to the kernel scarlett2 + * driver in sound/usb/mixer_scarlett2.c and the fcp-support project + * at https://github.com/geoffreybennett/fcp-support + * + * For examples of using these IOCTLs, see the fcp-server source in + * the fcp-support project. + * + * IOCTL Interface + * -------------- + * FCP_IOCTL_PVERSION: + * Returns the protocol version supported by the driver. + * + * FCP_IOCTL_INIT: + * Initialises the protocol and synchronises sequence numbers + * between the driver and device. Must be called at least once + * before sending commands. Can be safely called again at any time. + * + * FCP_IOCTL_CMD: + * Sends an FCP command to the device and returns the response. + * Requires prior initialisation via FCP_IOCTL_INIT. + * + * FCP_IOCTL_SET_METER_MAP: + * Configures the Level Meter control's mapping between device + * meters and control channels. Requires FCP_IOCTL_INIT to have been + * called first. The map size and number of slots cannot be changed + * after initial configuration, although the map itself can be + * updated. Once configured, the Level Meter remains functional even + * after the hwdep device is closed. + * + * FCP_IOCTL_SET_METER_LABELS: + * Set the labels for the Level Meter control. Requires + * FCP_IOCTL_SET_METER_MAP to have been called first. labels[] + * should contain a sequence of null-terminated labels corresponding + * to the control's channels. + */ +#ifndef __UAPI_SOUND_FCP_H +#define __UAPI_SOUND_FCP_H + +#include +#include + +#define FCP_HWDEP_MAJOR 2 +#define FCP_HWDEP_MINOR 0 +#define FCP_HWDEP_SUBMINOR 0 + +#define FCP_HWDEP_VERSION \ + ((FCP_HWDEP_MAJOR << 16) | \ + (FCP_HWDEP_MINOR << 8) | \ + FCP_HWDEP_SUBMINOR) + +#define FCP_HWDEP_VERSION_MAJOR(v) (((v) >> 16) & 0xFF) +#define FCP_HWDEP_VERSION_MINOR(v) (((v) >> 8) & 0xFF) +#define FCP_HWDEP_VERSION_SUBMINOR(v) ((v) & 0xFF) + +/* Get protocol version */ +#define FCP_IOCTL_PVERSION _IOR('S', 0x60, int) + +/* Start the protocol */ + +/* Step 0 and step 2 responses are variable length and placed in + * resp[] one after the other. + */ +struct fcp_init { + __u16 step0_resp_size; + __u16 step2_resp_size; + __u32 init1_opcode; + __u32 init2_opcode; + __u8 resp[]; +} __attribute__((packed)); + +#define FCP_IOCTL_INIT _IOWR('S', 0x64, struct fcp_init) + +/* Perform a command */ + +/* The request data is placed in data[] and the response data will + * overwrite it. + */ +struct fcp_cmd { + __u32 opcode; + __u16 req_size; + __u16 resp_size; + __u8 data[]; +} __attribute__((packed)); +#define FCP_IOCTL_CMD _IOWR('S', 0x65, struct fcp_cmd) + +/* Set the meter map */ +struct fcp_meter_map { + __u16 map_size; + __u16 meter_slots; + __s16 map[]; +} __attribute__((packed)); +#define FCP_IOCTL_SET_METER_MAP _IOW('S', 0x66, struct fcp_meter_map) + +/* Set the meter labels */ +struct fcp_meter_labels { + __u16 labels_size; + char labels[]; +} __attribute__((packed)); +#define FCP_IOCTL_SET_METER_LABELS _IOW('S', 0x67, struct fcp_meter_labels) + +#endif /* __UAPI_SOUND_FCP_H */ diff --git a/include/uapi/sound/tlv.h b/include/uapi/sound/tlv.h index b99a2414b53dc8..5bb55c80e0954c 100644 --- a/include/uapi/sound/tlv.h +++ b/include/uapi/sound/tlv.h @@ -18,6 +18,8 @@ #define SNDRV_CTL_TLVT_CHMAP_VAR 0x102 /* channels freely swappable */ #define SNDRV_CTL_TLVT_CHMAP_PAIRED 0x103 /* pair-wise swappable */ +#define SNDRV_CTL_TLVT_FCP_CHANNEL_LABELS 0x110 /* channel labels */ + /* * TLV structure is right behind the struct snd_ctl_tlv: * unsigned int type - see SNDRV_CTL_TLVT_* diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c index 8a681bff4f7f61..70a958ac1112c2 100644 --- a/sound/core/rawmidi.c +++ b/sound/core/rawmidi.c @@ -1837,7 +1837,6 @@ int snd_rawmidi_init(struct snd_rawmidi *rmidi, INIT_LIST_HEAD(&rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substreams); INIT_LIST_HEAD(&rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substreams); rmidi->info_flags = info_flags; - rmidi->tied_device = SNDRV_RAWMIDI_DEVICE_UNKNOWN; if (id != NULL) strscpy(rmidi->id, id, sizeof(rmidi->id)); diff --git a/sound/core/ump.c b/sound/core/ump.c index ff3cc2386ece98..8d8681a42ca5f3 100644 --- a/sound/core/ump.c +++ b/sound/core/ump.c @@ -1382,8 +1382,7 @@ int snd_ump_attach_legacy_rawmidi(struct snd_ump_endpoint *ump, ump_legacy_set_rawmidi_name(ump); update_legacy_names(ump); - rmidi->tied_device = ump->core.device; - ump->core.tied_device = rmidi->device; + snd_rawmidi_tie_devices(rmidi, &ump->core); ump_dbg(ump, "Created a legacy rawmidi #%d (%s)\n", device, id); return 0; diff --git a/sound/hda/hdac_stream.c b/sound/hda/hdac_stream.c index 2670792f43b41b..4e85a838ad7e94 100644 --- a/sound/hda/hdac_stream.c +++ b/sound/hda/hdac_stream.c @@ -492,32 +492,21 @@ static int setup_bdle(struct hdac_bus *bus, } /** - * snd_hdac_stream_setup_periods - set up BDL entries + * snd_hdac_stream_setup_bdle - set up BDL entries * @azx_dev: HD-audio core stream to set up + * @dmab: allocated DMA buffer + * @runtime: substream runtime, optional * * Set up the buffer descriptor table of the given stream based on the * period and buffer sizes of the assigned PCM substream. */ -int snd_hdac_stream_setup_periods(struct hdac_stream *azx_dev) +static int snd_hdac_stream_setup_bdle(struct hdac_stream *azx_dev, struct snd_dma_buffer *dmab, + struct snd_pcm_runtime *runtime) { struct hdac_bus *bus = azx_dev->bus; - struct snd_pcm_substream *substream = azx_dev->substream; - struct snd_compr_stream *cstream = azx_dev->cstream; - struct snd_pcm_runtime *runtime = NULL; - struct snd_dma_buffer *dmab; - __le32 *bdl; int i, ofs, periods, period_bytes; int pos_adj, pos_align; - - if (substream) { - runtime = substream->runtime; - dmab = snd_pcm_get_dma_buf(substream); - } else if (cstream) { - dmab = snd_pcm_get_dma_buf(cstream); - } else { - WARN(1, "No substream or cstream assigned\n"); - return -EINVAL; - } + __le32 *bdl; /* reset BDL address */ snd_hdac_stream_writel(azx_dev, SD_BDLPL, 0); @@ -571,6 +560,33 @@ int snd_hdac_stream_setup_periods(struct hdac_stream *azx_dev) azx_dev->bufsize, period_bytes); return -EINVAL; } + +/** + * snd_hdac_stream_setup_periods - set up BDL entries + * @azx_dev: HD-audio core stream to set up + * + * Set up the buffer descriptor table of the given stream based on the + * period and buffer sizes of the assigned PCM substream. + */ +int snd_hdac_stream_setup_periods(struct hdac_stream *azx_dev) +{ + struct snd_pcm_substream *substream = azx_dev->substream; + struct snd_compr_stream *cstream = azx_dev->cstream; + struct snd_pcm_runtime *runtime = NULL; + struct snd_dma_buffer *dmab; + + if (substream) { + runtime = substream->runtime; + dmab = snd_pcm_get_dma_buf(substream); + } else if (cstream) { + dmab = snd_pcm_get_dma_buf(cstream); + } else { + WARN(1, "No substream or cstream assigned\n"); + return -EINVAL; + } + + return snd_hdac_stream_setup_bdle(azx_dev, dmab, runtime); +} EXPORT_SYMBOL_GPL(snd_hdac_stream_setup_periods); /** @@ -923,7 +939,6 @@ int snd_hdac_dsp_prepare(struct hdac_stream *azx_dev, unsigned int format, unsigned int byte_size, struct snd_dma_buffer *bufp) { struct hdac_bus *bus = azx_dev->bus; - __le32 *bdl; int err; snd_hdac_dsp_lock(azx_dev); @@ -943,18 +958,14 @@ int snd_hdac_dsp_prepare(struct hdac_stream *azx_dev, unsigned int format, azx_dev->substream = NULL; azx_dev->bufsize = byte_size; - azx_dev->period_bytes = byte_size; + /* It is recommended to transfer the firmware in two or more chunks. */ + azx_dev->period_bytes = byte_size / 2; azx_dev->format_val = format; + azx_dev->no_period_wakeup = 1; snd_hdac_stream_reset(azx_dev); - /* reset BDL address */ - snd_hdac_stream_writel(azx_dev, SD_BDLPL, 0); - snd_hdac_stream_writel(azx_dev, SD_BDLPU, 0); - - azx_dev->frags = 0; - bdl = (__le32 *)azx_dev->bdl.area; - err = setup_bdle(bus, bufp, azx_dev, &bdl, 0, byte_size, 0); + err = snd_hdac_stream_setup_bdle(azx_dev, bufp, NULL); if (err < 0) goto error; diff --git a/sound/pci/ctxfi/ctdaio.c b/sound/pci/ctxfi/ctdaio.c index 83aaf9441ef30f..9993b02d2968c6 100644 --- a/sound/pci/ctxfi/ctdaio.c +++ b/sound/pci/ctxfi/ctdaio.c @@ -211,52 +211,30 @@ static int dao_set_right_input(struct dao *dao, struct rsc *input) return 0; } -static int dao_clear_left_input(struct dao *dao) +static int dao_clear_input(struct dao *dao, unsigned int start, unsigned int end) { - struct imapper *entry; - struct daio *daio = &dao->daio; - int i; + unsigned int i; - if (!dao->imappers[0]) + if (!dao->imappers[start]) return 0; - - entry = dao->imappers[0]; - dao->mgr->imap_delete(dao->mgr, entry); - /* Program conjugate resources */ - for (i = 1; i < daio->rscl.msr; i++) { - entry = dao->imappers[i]; - dao->mgr->imap_delete(dao->mgr, entry); + for (i = start; i < end; i++) { + dao->mgr->imap_delete(dao->mgr, dao->imappers[i]); dao->imappers[i] = NULL; } - kfree(dao->imappers[0]); - dao->imappers[0] = NULL; - return 0; } -static int dao_clear_right_input(struct dao *dao) -{ - struct imapper *entry; - struct daio *daio = &dao->daio; - int i; - if (!dao->imappers[daio->rscl.msr]) - return 0; - - entry = dao->imappers[daio->rscl.msr]; - dao->mgr->imap_delete(dao->mgr, entry); - /* Program conjugate resources */ - for (i = 1; i < daio->rscr.msr; i++) { - entry = dao->imappers[daio->rscl.msr + i]; - dao->mgr->imap_delete(dao->mgr, entry); - dao->imappers[daio->rscl.msr + i] = NULL; - } - - kfree(dao->imappers[daio->rscl.msr]); - dao->imappers[daio->rscl.msr] = NULL; +static int dao_clear_left_input(struct dao *dao) +{ + return dao_clear_input(dao, 0, dao->daio.rscl.msr); +} - return 0; +static int dao_clear_right_input(struct dao *dao) +{ + return dao_clear_input(dao, dao->daio.rscl.msr, + dao->daio.rscl.msr + dao->daio.rscr.msr); } static const struct dao_rsc_ops dao_ops = { diff --git a/sound/pci/hda/Kconfig b/sound/pci/hda/Kconfig index 68f1eee9e5c938..e393578cbe6840 100644 --- a/sound/pci/hda/Kconfig +++ b/sound/pci/hda/Kconfig @@ -206,6 +206,20 @@ config SND_HDA_SCODEC_TAS2781_I2C comment "Set to Y if you want auto-loading the side codec driver" depends on SND_HDA=y && SND_HDA_SCODEC_TAS2781_I2C=m +config SND_HDA_SCODEC_TAS2781_SPI + tristate "Build TAS2781 HD-audio side codec support for SPI Bus" + depends on SPI_MASTER + depends on ACPI + depends on EFI + depends on SND_SOC + select CRC32 + help + Say Y or M here to include TAS2781 SPI HD-audio side codec support + in snd-hda-intel driver, such as ALC287. + +comment "Set to Y if you want auto-loading the side codec driver" + depends on SND_HDA=y && SND_HDA_SCODEC_TAS2781_SPI=m + config SND_HDA_CODEC_REALTEK tristate "Build Realtek HD-audio codec support" select SND_HDA_GENERIC diff --git a/sound/pci/hda/Makefile b/sound/pci/hda/Makefile index 80210f845df212..210c406dfbc504 100644 --- a/sound/pci/hda/Makefile +++ b/sound/pci/hda/Makefile @@ -40,6 +40,7 @@ snd-hda-scodec-cs35l56-spi-y := cs35l56_hda_spi.o snd-hda-cs-dsp-ctls-y := hda_cs_dsp_ctl.o snd-hda-scodec-component-y := hda_component.o snd-hda-scodec-tas2781-i2c-y := tas2781_hda_i2c.o +snd-hda-scodec-tas2781-spi-y := tas2781_hda_spi.o tas2781_spi_fwlib.o # common driver obj-$(CONFIG_SND_HDA) := snd-hda-codec.o @@ -72,6 +73,7 @@ obj-$(CONFIG_SND_HDA_SCODEC_CS35L56_SPI) += snd-hda-scodec-cs35l56-spi.o obj-$(CONFIG_SND_HDA_CS_DSP_CONTROLS) += snd-hda-cs-dsp-ctls.o obj-$(CONFIG_SND_HDA_SCODEC_COMPONENT) += snd-hda-scodec-component.o obj-$(CONFIG_SND_HDA_SCODEC_TAS2781_I2C) += snd-hda-scodec-tas2781-i2c.o +obj-$(CONFIG_SND_HDA_SCODEC_TAS2781_SPI) += snd-hda-scodec-tas2781-spi.o # this must be the last entry after codec drivers; # otherwise the codec patches won't be hooked before the PCI probe diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index 4a62440adfafdb..7d7f9aac50a91f 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -2738,9 +2738,9 @@ static const struct pci_device_id azx_ids[] = { { PCI_VDEVICE(ZHAOXIN, 0x3288), .driver_data = AZX_DRIVER_ZHAOXIN }, /* Loongson HDAudio*/ { PCI_VDEVICE(LOONGSON, PCI_DEVICE_ID_LOONGSON_HDA), - .driver_data = AZX_DRIVER_LOONGSON }, + .driver_data = AZX_DRIVER_LOONGSON | AZX_DCAPS_NO_TCSEL }, { PCI_VDEVICE(LOONGSON, PCI_DEVICE_ID_LOONGSON_HDMI), - .driver_data = AZX_DRIVER_LOONGSON }, + .driver_data = AZX_DRIVER_LOONGSON | AZX_DCAPS_NO_TCSEL }, { 0, } }; MODULE_DEVICE_TABLE(pci, azx_ids); diff --git a/sound/pci/hda/ideapad_hotkey_led_helper.c b/sound/pci/hda/ideapad_hotkey_led_helper.c new file mode 100644 index 00000000000000..c10d97964d49b6 --- /dev/null +++ b/sound/pci/hda/ideapad_hotkey_led_helper.c @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Ideapad helper functions for Lenovo Ideapad LED control, + * It should be included from codec driver. + */ + +#if IS_ENABLED(CONFIG_IDEAPAD_LAPTOP) + +#include +#include + +static bool is_ideapad(struct hda_codec *codec) +{ + return (codec->core.subsystem_id >> 16 == 0x17aa) && + (acpi_dev_found("LHK2019") || acpi_dev_found("VPC2004")); +} + +static void hda_fixup_ideapad_acpi(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + if (!is_ideapad(codec)) + return; + snd_hda_gen_add_mute_led_cdev(codec, NULL); + snd_hda_gen_add_micmute_led_cdev(codec, NULL); + } +} + +#else /* CONFIG_IDEAPAD_LAPTOP */ + +static void hda_fixup_ideapad_acpi(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ +} + +#endif /* CONFIG_IDEAPAD_LAPTOP */ diff --git a/sound/pci/hda/patch_conexant.c b/sound/pci/hda/patch_conexant.c index 538c37a78a56f7..4985e72b909477 100644 --- a/sound/pci/hda/patch_conexant.c +++ b/sound/pci/hda/patch_conexant.c @@ -291,6 +291,7 @@ enum { CXT_FIXUP_GPIO1, CXT_FIXUP_ASPIRE_DMIC, CXT_FIXUP_THINKPAD_ACPI, + CXT_FIXUP_LENOVO_XPAD_ACPI, CXT_FIXUP_OLPC_XO, CXT_FIXUP_CAP_MIX_AMP, CXT_FIXUP_TOSHIBA_P105, @@ -313,6 +314,9 @@ enum { /* for hda_fixup_thinkpad_acpi() */ #include "thinkpad_helper.c" +/* for hda_fixup_ideapad_acpi() */ +#include "ideapad_hotkey_led_helper.c" + static void cxt_fixup_stereo_dmic(struct hda_codec *codec, const struct hda_fixup *fix, int action) { @@ -928,6 +932,12 @@ static const struct hda_fixup cxt_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = hda_fixup_thinkpad_acpi, }, + [CXT_FIXUP_LENOVO_XPAD_ACPI] = { + .type = HDA_FIXUP_FUNC, + .v.func = hda_fixup_ideapad_acpi, + .chained = true, + .chain_id = CXT_FIXUP_THINKPAD_ACPI, + }, [CXT_FIXUP_OLPC_XO] = { .type = HDA_FIXUP_FUNC, .v.func = cxt_fixup_olpc_xo, @@ -1119,7 +1129,7 @@ static const struct hda_quirk cxt5066_fixups[] = { SND_PCI_QUIRK(0x17aa, 0x3977, "Lenovo IdeaPad U310", CXT_FIXUP_STEREO_DMIC), SND_PCI_QUIRK(0x17aa, 0x3978, "Lenovo G50-70", CXT_FIXUP_STEREO_DMIC), SND_PCI_QUIRK(0x17aa, 0x397b, "Lenovo S205", CXT_FIXUP_STEREO_DMIC), - SND_PCI_QUIRK_VENDOR(0x17aa, "Thinkpad", CXT_FIXUP_THINKPAD_ACPI), + SND_PCI_QUIRK_VENDOR(0x17aa, "Thinkpad/Ideapad", CXT_FIXUP_LENOVO_XPAD_ACPI), SND_PCI_QUIRK(0x1c06, 0x2011, "Lemote A1004", CXT_PINCFG_LEMOTE_A1004), SND_PCI_QUIRK(0x1c06, 0x2012, "Lemote A1205", CXT_PINCFG_LEMOTE_A1205), HDA_CODEC_QUIRK(0x2782, 0x12c3, "Sirius Gen1", CXT_PINCFG_TOP_SPEAKER), @@ -1133,6 +1143,7 @@ static const struct hda_model_fixup cxt5066_fixup_models[] = { { .id = CXT_FIXUP_HEADPHONE_MIC_PIN, .name = "headphone-mic-pin" }, { .id = CXT_PINCFG_LENOVO_TP410, .name = "tp410" }, { .id = CXT_FIXUP_THINKPAD_ACPI, .name = "thinkpad" }, + { .id = CXT_FIXUP_LENOVO_XPAD_ACPI, .name = "thinkpad-ideapad" }, { .id = CXT_PINCFG_LEMOTE_A1004, .name = "lemote-a1004" }, { .id = CXT_PINCFG_LEMOTE_A1205, .name = "lemote-a1205" }, { .id = CXT_FIXUP_OLPC_XO, .name = "olpc-xo" }, diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index f578b9893a8f67..d3c9ed9635888b 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -6932,6 +6932,15 @@ static void alc_fixup_thinkpad_acpi(struct hda_codec *codec, hda_fixup_thinkpad_acpi(codec, fix, action); } +/* for hda_fixup_ideapad_acpi() */ +#include "ideapad_hotkey_led_helper.c" + +static void alc_fixup_ideapad_acpi(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + hda_fixup_ideapad_acpi(codec, fix, action); +} + /* Fixup for Lenovo Legion 15IMHg05 speaker output on headset removal. */ static void alc287_fixup_legion_15imhg05_speakers(struct hda_codec *codec, const struct hda_fixup *fix, @@ -7126,6 +7135,11 @@ static void tas2781_fixup_i2c(struct hda_codec *cdc, comp_generic_fixup(cdc, action, "i2c", "TIAS2781", "-%s:00", 1); } +static void tas2781_fixup_spi(struct hda_codec *cdc, const struct hda_fixup *fix, int action) +{ + comp_generic_fixup(cdc, action, "spi", "TXNW2781", "-%s:00-tas2781-hda.%d", 2); +} + static void yoga7_14arb7_fixup_i2c(struct hda_codec *cdc, const struct hda_fixup *fix, int action) { @@ -7554,6 +7568,7 @@ enum { ALC290_FIXUP_SUBWOOFER, ALC290_FIXUP_SUBWOOFER_HSJACK, ALC269_FIXUP_THINKPAD_ACPI, + ALC269_FIXUP_LENOVO_XPAD_ACPI, ALC269_FIXUP_DMIC_THINKPAD_ACPI, ALC269VB_FIXUP_INFINIX_ZERO_BOOK_13, ALC269VC_FIXUP_INFINIX_Y4_MAX, @@ -7760,6 +7775,7 @@ enum { ALC236_FIXUP_DELL_DUAL_CODECS, ALC287_FIXUP_CS35L41_I2C_2_THINKPAD_ACPI, ALC287_FIXUP_TAS2781_I2C, + ALC245_FIXUP_TAS2781_SPI_2, ALC287_FIXUP_YOGA7_14ARB7_I2C, ALC245_FIXUP_HP_MUTE_LED_COEFBIT, ALC245_FIXUP_HP_X360_MUTE_LEDS, @@ -8325,6 +8341,12 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC269_FIXUP_SKU_IGNORE, }, + [ALC269_FIXUP_LENOVO_XPAD_ACPI] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_ideapad_acpi, + .chained = true, + .chain_id = ALC269_FIXUP_THINKPAD_ACPI, + }, [ALC269_FIXUP_DMIC_THINKPAD_ACPI] = { .type = HDA_FIXUP_FUNC, .v.func = alc_fixup_inv_dmic, @@ -9984,6 +10006,12 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC285_FIXUP_THINKPAD_HEADSET_JACK, }, + [ALC245_FIXUP_TAS2781_SPI_2] = { + .type = HDA_FIXUP_FUNC, + .v.func = tas2781_fixup_spi, + .chained = true, + .chain_id = ALC285_FIXUP_HP_GPIO_LED, + }, [ALC287_FIXUP_YOGA7_14ARB7_I2C] = { .type = HDA_FIXUP_FUNC, .v.func = yoga7_14arb7_fixup_i2c, @@ -10156,6 +10184,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1025, 0x1308, "Acer Aspire Z24-890", ALC286_FIXUP_ACER_AIO_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x132a, "Acer TravelMate B114-21", ALC233_FIXUP_ACER_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x1330, "Acer TravelMate X514-51T", ALC255_FIXUP_ACER_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x1360, "Acer Aspire A115", ALC255_FIXUP_ACER_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1025, 0x141f, "Acer Spin SP513-54N", ALC255_FIXUP_ACER_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1025, 0x142b, "Acer Swift SF314-42", ALC255_FIXUP_ACER_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1025, 0x1430, "Acer TravelMate B311R-31", ALC256_FIXUP_ACER_MIC_NO_PRESENCE), @@ -10386,6 +10415,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8870, "HP ZBook Fury 15.6 Inch G8 Mobile Workstation PC", ALC285_FIXUP_HP_GPIO_AMP_INIT), SND_PCI_QUIRK(0x103c, 0x8873, "HP ZBook Studio 15.6 Inch G8 Mobile Workstation PC", ALC285_FIXUP_HP_GPIO_AMP_INIT), SND_PCI_QUIRK(0x103c, 0x887a, "HP Laptop 15s-eq2xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x887c, "HP Laptop 14s-fq1xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), SND_PCI_QUIRK(0x103c, 0x888a, "HP ENVY x360 Convertible 15-eu0xxx", ALC245_FIXUP_HP_X360_MUTE_LEDS), SND_PCI_QUIRK(0x103c, 0x888d, "HP ZBook Power 15.6 inch G8 Mobile Workstation PC", ALC236_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8895, "HP EliteBook 855 G8 Notebook PC", ALC285_FIXUP_HP_SPEAKERS_MICMUTE_LED), @@ -10546,6 +10576,8 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8d84, "HP EliteBook X G1i", ALC285_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8d91, "HP ZBook Firefly 14 G12", ALC285_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8d92, "HP ZBook Firefly 16 G12", ALC285_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8de8, "HP Gemtree", ALC245_FIXUP_TAS2781_SPI_2), + SND_PCI_QUIRK(0x103c, 0x8de9, "HP Gemtree", ALC245_FIXUP_TAS2781_SPI_2), SND_PCI_QUIRK(0x103c, 0x8e18, "HP ZBook Firefly 14 G12A", ALC285_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8e19, "HP ZBook Firefly 14 G12A", ALC285_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8e1a, "HP ZBook Firefly 14 G12A", ALC285_FIXUP_HP_GPIO_LED), @@ -10639,6 +10671,8 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1043, 0x1e1f, "ASUS Vivobook 15 X1504VAP", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1043, 0x1e51, "ASUS Zephyrus M15", ALC294_FIXUP_ASUS_GU502_PINS), SND_PCI_QUIRK(0x1043, 0x1e5e, "ASUS ROG Strix G513", ALC294_FIXUP_ASUS_G513_PINS), + SND_PCI_QUIRK(0x1043, 0x1e63, "ASUS H7606W", ALC285_FIXUP_ASUS_GU605_SPI_SPEAKER2_TO_DAC1), + SND_PCI_QUIRK(0x1043, 0x1e83, "ASUS GA605W", ALC285_FIXUP_ASUS_GU605_SPI_SPEAKER2_TO_DAC1), SND_PCI_QUIRK(0x1043, 0x1e8e, "ASUS Zephyrus G15", ALC289_FIXUP_ASUS_GA401), SND_PCI_QUIRK(0x1043, 0x1eb3, "ASUS Ally RCLA72", ALC287_FIXUP_TAS2781_I2C), SND_PCI_QUIRK(0x1043, 0x1ed3, "ASUS HN7306W", ALC287_FIXUP_CS35L41_I2C_2), @@ -10928,8 +10962,8 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x38e0, "Yoga Y990 Intel VECO Dual", ALC287_FIXUP_TAS2781_I2C), SND_PCI_QUIRK(0x17aa, 0x38f8, "Yoga Book 9i", ALC287_FIXUP_TAS2781_I2C), SND_PCI_QUIRK(0x17aa, 0x38df, "Y990 YG DUAL", ALC287_FIXUP_TAS2781_I2C), - SND_PCI_QUIRK(0x17aa, 0x38f9, "Thinkbook 16P Gen5", ALC287_FIXUP_CS35L41_I2C_2), - SND_PCI_QUIRK(0x17aa, 0x38fa, "Thinkbook 16P Gen5", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x17aa, 0x38f9, "Thinkbook 16P Gen5", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD), + SND_PCI_QUIRK(0x17aa, 0x38fa, "Thinkbook 16P Gen5", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD), SND_PCI_QUIRK(0x17aa, 0x38fd, "ThinkBook plus Gen5 Hybrid", ALC287_FIXUP_TAS2781_I2C), SND_PCI_QUIRK(0x17aa, 0x3902, "Lenovo E50-80", ALC269_FIXUP_DMIC_THINKPAD_ACPI), SND_PCI_QUIRK(0x17aa, 0x3913, "Lenovo 145", ALC236_FIXUP_LENOVO_INV_DMIC), @@ -10959,6 +10993,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x511f, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), SND_PCI_QUIRK(0x17aa, 0x9e54, "LENOVO NB", ALC269_FIXUP_LENOVO_EAPD), SND_PCI_QUIRK(0x17aa, 0x9e56, "Lenovo ZhaoYang CF4620Z", ALC286_FIXUP_SONY_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1849, 0x0269, "Positivo Master C6400", ALC269VB_FIXUP_ASUS_ZENBOOK), SND_PCI_QUIRK(0x1849, 0x1233, "ASRock NUC Box 1100", ALC233_FIXUP_NO_AUDIO_JACK), SND_PCI_QUIRK(0x1849, 0xa233, "Positivo Master C6300", ALC269_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1854, 0x0440, "LG CQ6", ALC256_FIXUP_HEADPHONE_AMP_VOL), @@ -10993,6 +11028,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1d72, 0x1901, "RedmiBook 14", ALC256_FIXUP_ASUS_HEADSET_MIC), SND_PCI_QUIRK(0x1d72, 0x1945, "Redmi G", ALC256_FIXUP_ASUS_HEADSET_MIC), SND_PCI_QUIRK(0x1d72, 0x1947, "RedmiBook Air", ALC255_FIXUP_XIAOMI_HEADSET_MIC), + SND_PCI_QUIRK(0x1f66, 0x0105, "Ayaneo Portable Game Player", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x2782, 0x0214, "VAIO VJFE-CL", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), SND_PCI_QUIRK(0x2782, 0x0228, "Infinix ZERO BOOK 13", ALC269VB_FIXUP_INFINIX_ZERO_BOOK_13), SND_PCI_QUIRK(0x2782, 0x0232, "CHUWI CoreBook XPro", ALC269VB_FIXUP_CHUWI_COREBOOK_XPRO), @@ -11064,7 +11100,7 @@ static const struct hda_quirk alc269_fixup_vendor_tbl[] = { SND_PCI_QUIRK_VENDOR(0x1025, "Acer Aspire", ALC271_FIXUP_DMIC), SND_PCI_QUIRK_VENDOR(0x103c, "HP", ALC269_FIXUP_HP_MUTE_LED), SND_PCI_QUIRK_VENDOR(0x104d, "Sony VAIO", ALC269_FIXUP_SONY_VAIO), - SND_PCI_QUIRK_VENDOR(0x17aa, "Thinkpad", ALC269_FIXUP_THINKPAD_ACPI), + SND_PCI_QUIRK_VENDOR(0x17aa, "Lenovo XPAD", ALC269_FIXUP_LENOVO_XPAD_ACPI), SND_PCI_QUIRK_VENDOR(0x19e5, "Huawei Matebook", ALC255_FIXUP_MIC_MUTE_LED), {} }; @@ -11129,6 +11165,7 @@ static const struct hda_model_fixup alc269_fixup_models[] = { {.id = ALC290_FIXUP_MONO_SPEAKERS_HSJACK, .name = "mono-speakers"}, {.id = ALC290_FIXUP_SUBWOOFER_HSJACK, .name = "alc290-subwoofer"}, {.id = ALC269_FIXUP_THINKPAD_ACPI, .name = "thinkpad"}, + {.id = ALC269_FIXUP_LENOVO_XPAD_ACPI, .name = "lenovo-xpad-led"}, {.id = ALC269_FIXUP_DMIC_THINKPAD_ACPI, .name = "dmic-thinkpad"}, {.id = ALC255_FIXUP_ACER_MIC_NO_PRESENCE, .name = "alc255-acer"}, {.id = ALC255_FIXUP_ASUS_MIC_NO_PRESENCE, .name = "alc255-asus"}, diff --git a/sound/pci/hda/tas2781-spi.h b/sound/pci/hda/tas2781-spi.h new file mode 100644 index 00000000000000..ecfc3c8bb82147 --- /dev/null +++ b/sound/pci/hda/tas2781-spi.h @@ -0,0 +1,158 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +// +// ALSA SoC Texas Instruments TAS2781 Audio Smart Amplifier +// +// Copyright (C) 2024 Texas Instruments Incorporated +// https://www.ti.com +// +// The TAS2781 driver implements a flexible and configurable +// algo coefficient setting for TAS2781 chips. +// +// Author: Baojun Xu +// + +#ifndef __TAS2781_SPI_H__ +#define __TAS2781_SPI_H__ + +#define TASDEVICE_RATES \ + (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | \ + SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_88200) + +#define TASDEVICE_FORMATS \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +#define TASDEVICE_MAX_BOOK_NUM 256 +#define TASDEVICE_MAX_PAGE 256 + +#define TASDEVICE_MAX_SIZE (TASDEVICE_MAX_BOOK_NUM * TASDEVICE_MAX_PAGE) + +/* PAGE Control Register (available in page0 of each book) */ +#define TASDEVICE_PAGE_SELECT 0x00 +#define TASDEVICE_BOOKCTL_PAGE 0x00 +#define TASDEVICE_BOOKCTL_REG GENMASK(7, 1) +#define TASDEVICE_BOOK_ID(reg) (((reg) & GENMASK(24, 16)) >> 16) +#define TASDEVICE_PAGE_ID(reg) (((reg) & GENMASK(15, 8)) >> 8) +#define TASDEVICE_REG_ID(reg) (((reg) & GENMASK(7, 1)) >> 1) +#define TASDEVICE_PAGE_REG(reg) ((reg) & GENMASK(15, 1)) +#define TASDEVICE_REG(book, page, reg) \ + (((book) << 16) | ((page) << 8) | ((reg) << 1)) + +/* Software Reset */ +#define TAS2781_REG_SWRESET TASDEVICE_REG(0x0, 0x0, 0x01) +#define TAS2781_REG_SWRESET_RESET BIT(0) + +/* System Reset Check Register */ +#define TAS2781_REG_CLK_CONFIG TASDEVICE_REG(0x0, 0x0, 0x5c) +#define TAS2781_REG_CLK_CONFIG_RESET (0x19) +#define TAS2781_PRE_POST_RESET_CFG 3 + +/* Block Checksum */ +#define TASDEVICE_CHECKSUM TASDEVICE_REG(0x0, 0x0, 0x7e) + +/* Volume control */ +#define TAS2781_DVC_LVL TASDEVICE_REG(0x0, 0x0, 0x1a) +#define TAS2781_AMP_LEVEL TASDEVICE_REG(0x0, 0x0, 0x03) +#define TAS2781_AMP_LEVEL_MASK GENMASK(5, 1) + +#define TASDEVICE_CMD_SING_W 0x1 +#define TASDEVICE_CMD_BURST 0x2 +#define TASDEVICE_CMD_DELAY 0x3 +#define TASDEVICE_CMD_FIELD_W 0x4 + +#define TAS2781_SPI_MAX_FREQ (4 * HZ_PER_MHZ) + +#define TASDEVICE_CRC8_POLYNOMIAL 0x4d +#define TASDEVICE_SPEAKER_CALIBRATION_SIZE 20 + +/* Flag of calibration registers address. */ +#define TASDEVICE_CALIBRATION_REG_ADDRESS BIT(7) + +#define TASDEVICE_CALIBRATION_DATA_NAME L"CALI_DATA" +#define TASDEVICE_CALIBRATION_DATA_SIZE 256 + +enum calib_data { + R0_VAL = 0, + INV_R0, + R0LOW, + POWER, + TLIM, + CALIB_MAX +}; + +struct tasdevice_priv { + struct tasdevice_fw *cali_data_fmw; + struct tasdevice_rca rcabin; + struct tasdevice_fw *fmw; + struct gpio_desc *reset; + struct mutex codec_lock; + struct regmap *regmap; + struct device *dev; + struct tm tm; + + unsigned char crc8_lkp_tbl[CRC8_TABLE_SIZE]; + unsigned char coef_binaryname[64]; + unsigned char rca_binaryname[64]; + unsigned char dev_name[32]; + + bool force_fwload_status; + bool playback_started; + bool is_loading; + bool is_loaderr; + unsigned int cali_reg_array[CALIB_MAX]; + unsigned int cali_data[CALIB_MAX]; + unsigned int err_code; + void *codec; + int cur_book; + int cur_prog; + int cur_conf; + int fw_state; + int index; + int irq; + + int (*fw_parse_variable_header)(struct tasdevice_priv *tas_priv, + const struct firmware *fmw, + int offset); + int (*fw_parse_program_data)(struct tasdevice_priv *tas_priv, + struct tasdevice_fw *tas_fmw, + const struct firmware *fmw, int offset); + int (*fw_parse_configuration_data)(struct tasdevice_priv *tas_priv, + struct tasdevice_fw *tas_fmw, + const struct firmware *fmw, + int offset); + int (*tasdevice_load_block)(struct tasdevice_priv *tas_priv, + struct tasdev_blk *block); + + int (*save_calibration)(struct tasdevice_priv *tas_priv); + void (*apply_calibration)(struct tasdevice_priv *tas_priv); +}; + +int tasdevice_spi_dev_read(struct tasdevice_priv *tas_priv, + unsigned int reg, unsigned int *value); +int tasdevice_spi_dev_write(struct tasdevice_priv *tas_priv, + unsigned int reg, unsigned int value); +int tasdevice_spi_dev_bulk_write(struct tasdevice_priv *tas_priv, + unsigned int reg, unsigned char *p_data, + unsigned int n_length); +int tasdevice_spi_dev_bulk_read(struct tasdevice_priv *tas_priv, + unsigned int reg, unsigned char *p_data, + unsigned int n_length); +int tasdevice_spi_dev_update_bits(struct tasdevice_priv *tasdevice, + unsigned int reg, unsigned int mask, + unsigned int value); + +void tasdevice_spi_select_cfg_blk(void *context, int conf_no, + unsigned char block_type); +void tasdevice_spi_config_info_remove(void *context); +int tasdevice_spi_dsp_parser(void *context); +int tasdevice_spi_rca_parser(void *context, const struct firmware *fmw); +void tasdevice_spi_dsp_remove(void *context); +void tasdevice_spi_calbin_remove(void *context); +int tasdevice_spi_select_tuningprm_cfg(void *context, int prm, int cfg_no, + int rca_conf_no); +int tasdevice_spi_prmg_load(void *context, int prm_no); +int tasdevice_spi_prmg_calibdata_load(void *context, int prm_no); +void tasdevice_spi_tuning_switch(void *context, int state); +int tas2781_spi_load_calibration(void *context, char *file_name, + unsigned short i); +#endif /* __TAS2781_SPI_H__ */ diff --git a/sound/pci/hda/tas2781_hda_spi.c b/sound/pci/hda/tas2781_hda_spi.c new file mode 100644 index 00000000000000..a42fa990e7b9e7 --- /dev/null +++ b/sound/pci/hda/tas2781_hda_spi.c @@ -0,0 +1,1265 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// TAS2781 HDA SPI driver +// +// Copyright 2024 Texas Instruments, Inc. +// +// Author: Baojun Xu + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "tas2781-spi.h" + +#include "hda_local.h" +#include "hda_auto_parser.h" +#include "hda_component.h" +#include "hda_jack.h" +#include "hda_generic.h" + +/* + * No standard control callbacks for SNDRV_CTL_ELEM_IFACE_CARD + * Define two controls, one is Volume control callbacks, the other is + * flag setting control callbacks. + */ + +/* Volume control callbacks for tas2781 */ +#define ACARD_SINGLE_RANGE_EXT_TLV(xname, xreg, xshift, xmin, xmax, xinvert, \ + xhandler_get, xhandler_put, tlv_array) { \ + .iface = SNDRV_CTL_ELEM_IFACE_CARD, .name = (xname), \ + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \ + SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .tlv.p = (tlv_array), \ + .info = snd_soc_info_volsw_range, \ + .get = xhandler_get, .put = xhandler_put, \ + .private_value = (unsigned long)&(struct soc_mixer_control) { \ + .reg = xreg, .rreg = xreg, \ + .shift = xshift, .rshift = xshift,\ + .min = xmin, .max = xmax, .invert = xinvert, \ + } \ +} + +/* Flag control callbacks for tas2781 */ +#define ACARD_SINGLE_BOOL_EXT(xname, xdata, xhandler_get, xhandler_put) { \ + .iface = SNDRV_CTL_ELEM_IFACE_CARD, \ + .name = xname, \ + .info = snd_ctl_boolean_mono_info, \ + .get = xhandler_get, \ + .put = xhandler_put, \ + .private_value = xdata, \ +} + +struct tas2781_hda { + struct tasdevice_priv *priv; + struct acpi_device *dacpi; + struct snd_kcontrol *dsp_prog_ctl; + struct snd_kcontrol *dsp_conf_ctl; + struct snd_kcontrol *snd_ctls[3]; + struct snd_kcontrol *prof_ctl; +}; + +static const struct regmap_range_cfg tasdevice_ranges[] = { + { + .range_min = 0, + .range_max = TASDEVICE_MAX_SIZE, + .selector_reg = TASDEVICE_PAGE_SELECT, + .selector_mask = GENMASK(7, 0), + .selector_shift = 0, + .window_start = 0, + .window_len = TASDEVICE_MAX_PAGE, + }, +}; + +static const struct regmap_config tasdevice_regmap = { + .reg_bits = 8, + .val_bits = 8, + .zero_flag_mask = true, + .cache_type = REGCACHE_NONE, + .ranges = tasdevice_ranges, + .num_ranges = ARRAY_SIZE(tasdevice_ranges), + .max_register = TASDEVICE_MAX_SIZE, +}; + +static int tasdevice_spi_switch_book(struct tasdevice_priv *tas_priv, int reg) +{ + struct regmap *map = tas_priv->regmap; + + if (tas_priv->cur_book != TASDEVICE_BOOK_ID(reg)) { + int ret = regmap_write(map, TASDEVICE_BOOKCTL_REG, + TASDEVICE_BOOK_ID(reg)); + if (ret < 0) { + dev_err(tas_priv->dev, "Switch Book E=%d\n", ret); + return ret; + } + tas_priv->cur_book = TASDEVICE_BOOK_ID(reg); + } + return 0; +} + +int tasdevice_spi_dev_read(struct tasdevice_priv *tas_priv, + unsigned int reg, + unsigned int *val) +{ + struct regmap *map = tas_priv->regmap; + int ret; + + ret = tasdevice_spi_switch_book(tas_priv, reg); + if (ret < 0) + return ret; + + /* + * In our TAS2781 SPI mode, if read from other book (not book 0), + * or read from page number larger than 1 in book 0, one more byte + * read is needed, and first byte is a dummy byte, need to be ignored. + */ + if ((TASDEVICE_BOOK_ID(reg) > 0) || (TASDEVICE_PAGE_ID(reg) > 1)) { + unsigned char data[2]; + + ret = regmap_bulk_read(map, TASDEVICE_PAGE_REG(reg) | 1, + data, sizeof(data)); + *val = data[1]; + } else { + ret = regmap_read(map, TASDEVICE_PAGE_REG(reg) | 1, val); + } + if (ret < 0) + dev_err(tas_priv->dev, "%s, E=%d\n", __func__, ret); + + return ret; +} + +int tasdevice_spi_dev_write(struct tasdevice_priv *tas_priv, + unsigned int reg, + unsigned int value) +{ + struct regmap *map = tas_priv->regmap; + int ret; + + ret = tasdevice_spi_switch_book(tas_priv, reg); + if (ret < 0) + return ret; + + ret = regmap_write(map, TASDEVICE_PAGE_REG(reg), value); + if (ret < 0) + dev_err(tas_priv->dev, "%s, E=%d\n", __func__, ret); + + return ret; +} + +int tasdevice_spi_dev_bulk_write(struct tasdevice_priv *tas_priv, + unsigned int reg, + unsigned char *data, + unsigned int len) +{ + struct regmap *map = tas_priv->regmap; + int ret; + + ret = tasdevice_spi_switch_book(tas_priv, reg); + if (ret < 0) + return ret; + + ret = regmap_bulk_write(map, TASDEVICE_PAGE_REG(reg), data, len); + if (ret < 0) + dev_err(tas_priv->dev, "%s, E=%d\n", __func__, ret); + + return ret; +} + +int tasdevice_spi_dev_bulk_read(struct tasdevice_priv *tas_priv, + unsigned int reg, + unsigned char *data, + unsigned int len) +{ + struct regmap *map = tas_priv->regmap; + int ret; + + ret = tasdevice_spi_switch_book(tas_priv, reg); + if (ret < 0) + return ret; + + if (len > TASDEVICE_MAX_PAGE) + len = TASDEVICE_MAX_PAGE; + /* + * In our TAS2781 SPI mode, if read from other book (not book 0), + * or read from page number larger than 1 in book 0, one more byte + * read is needed, and first byte is a dummy byte, need to be ignored. + */ + if ((TASDEVICE_BOOK_ID(reg) > 0) || (TASDEVICE_PAGE_ID(reg) > 1)) { + unsigned char buf[TASDEVICE_MAX_PAGE+1]; + + ret = regmap_bulk_read(map, TASDEVICE_PAGE_REG(reg) | 1, buf, + len + 1); + memcpy(data, buf + 1, len); + } else { + ret = regmap_bulk_read(map, TASDEVICE_PAGE_REG(reg) | 1, data, + len); + } + if (ret < 0) + dev_err(tas_priv->dev, "%s, E=%d\n", __func__, ret); + + return ret; +} + +int tasdevice_spi_dev_update_bits(struct tasdevice_priv *tas_priv, + unsigned int reg, + unsigned int mask, + unsigned int value) +{ + struct regmap *map = tas_priv->regmap; + int ret, val; + + /* + * In our TAS2781 SPI mode, read/write was masked in last bit of + * address, it cause regmap_update_bits() not work as expected. + */ + ret = tasdevice_spi_dev_read(tas_priv, reg, &val); + if (ret < 0) { + dev_err(tas_priv->dev, "%s, E=%d\n", __func__, ret); + return ret; + } + ret = regmap_write(map, TASDEVICE_PAGE_REG(reg), + (val & ~mask) | (mask & value)); + if (ret < 0) + dev_err(tas_priv->dev, "%s, E=%d\n", __func__, ret); + + return ret; +} + +static void tas2781_spi_reset(struct tasdevice_priv *tas_dev) +{ + int ret; + + if (tas_dev->reset) { + gpiod_set_value_cansleep(tas_dev->reset, 0); + fsleep(800); + gpiod_set_value_cansleep(tas_dev->reset, 1); + } + ret = tasdevice_spi_dev_write(tas_dev, TAS2781_REG_SWRESET, + TAS2781_REG_SWRESET_RESET); + if (ret < 0) + dev_err(tas_dev->dev, "dev sw-reset fail, %d\n", ret); + fsleep(1000); +} + +static int tascodec_spi_init(struct tasdevice_priv *tas_priv, + void *codec, struct module *module, + void (*cont)(const struct firmware *fw, void *context)) +{ + int ret; + + /* + * Codec Lock Hold to ensure that codec_probe and firmware parsing and + * loading do not simultaneously execute. + */ + guard(mutex)(&tas_priv->codec_lock); + + scnprintf(tas_priv->rca_binaryname, + sizeof(tas_priv->rca_binaryname), "%sRCA%d.bin", + tas_priv->dev_name, tas_priv->index); + crc8_populate_msb(tas_priv->crc8_lkp_tbl, TASDEVICE_CRC8_POLYNOMIAL); + tas_priv->codec = codec; + ret = request_firmware_nowait(module, FW_ACTION_UEVENT, + tas_priv->rca_binaryname, tas_priv->dev, GFP_KERNEL, tas_priv, + cont); + if (ret) + dev_err(tas_priv->dev, "request_firmware_nowait err:0x%08x\n", + ret); + + return ret; +} + +static void tasdevice_spi_init(struct tasdevice_priv *tas_priv) +{ + tas_priv->cur_prog = -1; + tas_priv->cur_conf = -1; + + tas_priv->cur_book = -1; + tas_priv->cur_prog = -1; + tas_priv->cur_conf = -1; + + /* Store default registers address for calibration data. */ + tas_priv->cali_reg_array[0] = TASDEVICE_REG(0, 0x17, 0x74); + tas_priv->cali_reg_array[1] = TASDEVICE_REG(0, 0x18, 0x0c); + tas_priv->cali_reg_array[2] = TASDEVICE_REG(0, 0x18, 0x14); + tas_priv->cali_reg_array[3] = TASDEVICE_REG(0, 0x13, 0x70); + tas_priv->cali_reg_array[4] = TASDEVICE_REG(0, 0x18, 0x7c); + + mutex_init(&tas_priv->codec_lock); +} + +static int tasdevice_spi_amp_putvol(struct tasdevice_priv *tas_priv, + struct snd_ctl_elem_value *ucontrol, + struct soc_mixer_control *mc) +{ + unsigned int invert = mc->invert; + unsigned char mask; + int max = mc->max; + int val, ret; + + mask = rounddown_pow_of_two(max); + mask <<= mc->shift; + val = clamp(invert ? max - ucontrol->value.integer.value[0] : + ucontrol->value.integer.value[0], 0, max); + ret = tasdevice_spi_dev_update_bits(tas_priv, + mc->reg, mask, (unsigned int)(val << mc->shift)); + if (ret) + dev_err(tas_priv->dev, "set AMP vol error in dev %d\n", + tas_priv->index); + + return ret; +} + +static int tasdevice_spi_amp_getvol(struct tasdevice_priv *tas_priv, + struct snd_ctl_elem_value *ucontrol, + struct soc_mixer_control *mc) +{ + unsigned int invert = mc->invert; + unsigned char mask = 0; + int max = mc->max; + int ret, val; + + /* Read the primary device */ + ret = tasdevice_spi_dev_read(tas_priv, mc->reg, &val); + if (ret) { + dev_err(tas_priv->dev, "%s, get AMP vol error\n", __func__); + return ret; + } + + mask = rounddown_pow_of_two(max); + mask <<= mc->shift; + val = (val & mask) >> mc->shift; + val = clamp(invert ? max - val : val, 0, max); + ucontrol->value.integer.value[0] = val; + + return ret; +} + +static int tasdevice_spi_digital_putvol(struct tasdevice_priv *tas_priv, + struct snd_ctl_elem_value *ucontrol, + struct soc_mixer_control *mc) +{ + unsigned int invert = mc->invert; + int max = mc->max; + int val, ret; + + val = clamp(invert ? max - ucontrol->value.integer.value[0] : + ucontrol->value.integer.value[0], 0, max); + ret = tasdevice_spi_dev_write(tas_priv, mc->reg, (unsigned int)val); + if (ret) + dev_err(tas_priv->dev, "set digital vol err in dev %d\n", + tas_priv->index); + + return ret; +} + +static int tasdevice_spi_digital_getvol(struct tasdevice_priv *tas_priv, + struct snd_ctl_elem_value *ucontrol, + struct soc_mixer_control *mc) +{ + unsigned int invert = mc->invert; + int max = mc->max; + int ret, val; + + /* Read the primary device as the whole */ + ret = tasdevice_spi_dev_read(tas_priv, mc->reg, &val); + if (ret) { + dev_err(tas_priv->dev, "%s, get digital vol err\n", __func__); + return ret; + } + + val = clamp(invert ? max - val : val, 0, max); + ucontrol->value.integer.value[0] = val; + + return ret; +} + +static int tas2781_read_acpi(struct tas2781_hda *tas_hda, + const char *hid, + int id) +{ + struct tasdevice_priv *p = tas_hda->priv; + struct acpi_device *adev; + struct device *physdev; + u32 values[HDA_MAX_COMPONENTS]; + const char *property; + size_t nval; + int ret, i; + + adev = acpi_dev_get_first_match_dev(hid, NULL, -1); + if (!adev) { + dev_err(p->dev, "Failed to find ACPI device: %s\n", hid); + return -ENODEV; + } + + strscpy(p->dev_name, hid, sizeof(p->dev_name)); + tas_hda->dacpi = adev; + physdev = get_device(acpi_get_first_physical_node(adev)); + acpi_dev_put(adev); + + property = "ti,dev-index"; + ret = device_property_count_u32(physdev, property); + if (ret <= 0 || ret > ARRAY_SIZE(values)) { + ret = -EINVAL; + goto err; + } + nval = ret; + + ret = device_property_read_u32_array(physdev, property, values, nval); + if (ret) + goto err; + + p->index = U8_MAX; + for (i = 0; i < nval; i++) { + if (values[i] == id) { + p->index = i; + break; + } + } + if (p->index == U8_MAX) { + dev_dbg(p->dev, "No index found in %s\n", property); + ret = -ENODEV; + goto err; + } + + if (p->index == 0) { + /* All of amps share same RESET pin. */ + p->reset = devm_gpiod_get_index_optional(physdev, "reset", + p->index, GPIOD_OUT_LOW); + if (IS_ERR(p->reset)) { + ret = PTR_ERR(p->reset); + dev_err_probe(p->dev, ret, "Failed on reset GPIO\n"); + goto err; + } + } + put_device(physdev); + + return 0; +err: + dev_err(p->dev, "read acpi error, ret: %d\n", ret); + put_device(physdev); + acpi_dev_put(adev); + + return ret; +} + +static void tas2781_hda_playback_hook(struct device *dev, int action) +{ + struct tas2781_hda *tas_hda = dev_get_drvdata(dev); + + if (action == HDA_GEN_PCM_ACT_OPEN) { + pm_runtime_get_sync(dev); + guard(mutex)(&tas_hda->priv->codec_lock); + tasdevice_spi_tuning_switch(tas_hda->priv, 0); + } else if (action == HDA_GEN_PCM_ACT_CLOSE) { + guard(mutex)(&tas_hda->priv->codec_lock); + tasdevice_spi_tuning_switch(tas_hda->priv, 1); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + } +} + +static int tasdevice_info_profile(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = tas_priv->rcabin.ncfgs - 1; + + return 0; +} + +static int tasdevice_get_profile_id(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = tas_priv->rcabin.profile_cfg_id; + + return 0; +} + +static int tasdevice_set_profile_id(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol); + int max = tas_priv->rcabin.ncfgs - 1; + int val; + + val = clamp(ucontrol->value.integer.value[0], 0, max); + if (tas_priv->rcabin.profile_cfg_id != val) { + tas_priv->rcabin.profile_cfg_id = val; + return 1; + } + + return 0; +} + +static int tasdevice_info_programs(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = tas_priv->fmw->nr_programs - 1; + + return 0; +} + +static int tasdevice_info_config(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = tas_priv->fmw->nr_configurations - 1; + + return 0; +} + +static int tasdevice_program_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = tas_priv->cur_prog; + + return 0; +} + +static int tasdevice_program_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol); + int nr_program = ucontrol->value.integer.value[0]; + int max = tas_priv->fmw->nr_programs - 1; + int val; + + val = clamp(nr_program, 0, max); + + if (tas_priv->cur_prog != val) { + tas_priv->cur_prog = val; + return 1; + } + + return 0; +} + +static int tasdevice_config_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = tas_priv->cur_conf; + + return 0; +} + +static int tasdevice_config_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol); + int max = tas_priv->fmw->nr_configurations - 1; + int val; + + val = clamp(ucontrol->value.integer.value[0], 0, max); + + if (tas_priv->cur_conf != val) { + tas_priv->cur_conf = val; + return 1; + } + + return 0; +} + +/* + * tas2781_digital_getvol - get the volum control + * @kcontrol: control pointer + * @ucontrol: User data + * + * Customer Kcontrol for tas2781 is primarily for regmap booking, paging + * depends on internal regmap mechanism. + * tas2781 contains book and page two-level register map, especially + * book switching will set the register BXXP00R7F, after switching to the + * correct book, then leverage the mechanism for paging to access the + * register. + * + * Return 0 if succeeded. + */ +static int tas2781_digital_getvol(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + return tasdevice_spi_digital_getvol(tas_priv, ucontrol, mc); +} + +static int tas2781_amp_getvol(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + return tasdevice_spi_amp_getvol(tas_priv, ucontrol, mc); +} + +static int tas2781_digital_putvol(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + /* The check of the given value is in tasdevice_digital_putvol. */ + return tasdevice_spi_digital_putvol(tas_priv, ucontrol, mc); +} + +static int tas2781_amp_putvol(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + /* The check of the given value is in tasdevice_amp_putvol. */ + return tasdevice_spi_amp_putvol(tas_priv, ucontrol, mc); +} + +static int tas2781_force_fwload_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = (int)tas_priv->force_fwload_status; + dev_dbg(tas_priv->dev, "%s : Force FWload %s\n", __func__, + str_on_off(tas_priv->force_fwload_status)); + + return 0; +} + +static int tas2781_force_fwload_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol); + bool change, val = (bool)ucontrol->value.integer.value[0]; + + if (tas_priv->force_fwload_status == val) { + change = false; + } else { + change = true; + tas_priv->force_fwload_status = val; + } + dev_dbg(tas_priv->dev, "%s : Force FWload %s\n", __func__, + str_on_off(tas_priv->force_fwload_status)); + + return change; +} + +static const struct snd_kcontrol_new tas2781_snd_controls[] = { + ACARD_SINGLE_RANGE_EXT_TLV("Speaker Analog Gain 0", TAS2781_AMP_LEVEL, + 1, 0, 20, 0, tas2781_amp_getvol, + tas2781_amp_putvol, amp_vol_tlv), + ACARD_SINGLE_RANGE_EXT_TLV("Speaker Digital Gain 0", TAS2781_DVC_LVL, + 0, 0, 200, 1, tas2781_digital_getvol, + tas2781_digital_putvol, dvc_tlv), + ACARD_SINGLE_BOOL_EXT("Speaker Force Firmware Load 0", 0, + tas2781_force_fwload_get, tas2781_force_fwload_put), + ACARD_SINGLE_RANGE_EXT_TLV("Speaker Analog Gain 1", TAS2781_AMP_LEVEL, + 1, 0, 20, 0, tas2781_amp_getvol, + tas2781_amp_putvol, amp_vol_tlv), + ACARD_SINGLE_RANGE_EXT_TLV("Speaker Digital Gain 1", TAS2781_DVC_LVL, + 0, 0, 200, 1, tas2781_digital_getvol, + tas2781_digital_putvol, dvc_tlv), + ACARD_SINGLE_BOOL_EXT("Speaker Force Firmware Load 1", 0, + tas2781_force_fwload_get, tas2781_force_fwload_put), +}; + +static const struct snd_kcontrol_new tas2781_prof_ctrl[] = { +{ + .name = "Speaker Profile Id - 0", + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .info = tasdevice_info_profile, + .get = tasdevice_get_profile_id, + .put = tasdevice_set_profile_id, +}, +{ + .name = "Speaker Profile Id - 1", + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .info = tasdevice_info_profile, + .get = tasdevice_get_profile_id, + .put = tasdevice_set_profile_id, +}, +}; +static const struct snd_kcontrol_new tas2781_dsp_prog_ctrl[] = { +{ + .name = "Speaker Program Id 0", + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .info = tasdevice_info_programs, + .get = tasdevice_program_get, + .put = tasdevice_program_put, +}, +{ + .name = "Speaker Program Id 1", + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .info = tasdevice_info_programs, + .get = tasdevice_program_get, + .put = tasdevice_program_put, +}, +}; + +static const struct snd_kcontrol_new tas2781_dsp_conf_ctrl[] = { +{ + .name = "Speaker Config Id 0", + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .info = tasdevice_info_config, + .get = tasdevice_config_get, + .put = tasdevice_config_put, +}, +{ + .name = "Speaker Config Id 1", + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .info = tasdevice_info_config, + .get = tasdevice_config_get, + .put = tasdevice_config_put, +}, +}; + +static void tas2781_apply_calib(struct tasdevice_priv *tas_priv) +{ + int i, rc; + + /* + * If no calibration data exist in tasdevice_priv *tas_priv, + * calibration apply will be ignored, and use default values + * in firmware binary, which was loaded during firmware download. + */ + if (tas_priv->cali_data[0] == 0) + return; + /* + * Calibration data was saved in tasdevice_priv *tas_priv as: + * unsigned int cali_data[CALIB_MAX]; + * and every data (in 4 bytes) will be saved in register which in + * book 0, and page number in page_array[], offset was saved in + * rgno_array[]. + */ + for (i = 0; i < CALIB_MAX; i++) { + rc = tasdevice_spi_dev_bulk_write(tas_priv, + tas_priv->cali_reg_array[i], + (unsigned char *)&tas_priv->cali_data[i], 4); + if (rc < 0) + dev_err(tas_priv->dev, + "chn %d calib %d bulk_wr err = %d\n", + tas_priv->index, i, rc); + } +} + +/* + * Update the calibration data, including speaker impedance, f0, etc, + * into algo. Calibrate data is done by manufacturer in the factory. + * These data are used by Algo for calculating the speaker temperature, + * speaker membrane excursion and f0 in real time during playback. + * Calibration data format in EFI is V2, since 2024. + */ +static int tas2781_save_calibration(struct tasdevice_priv *tas_priv) +{ + /* + * GUID was used for data access in BIOS, it was provided by board + * manufactory, like HP: "{02f9af02-7734-4233-b43d-93fe5aa35db3}" + */ + efi_guid_t efi_guid = + EFI_GUID(0x02f9af02, 0x7734, 0x4233, + 0xb4, 0x3d, 0x93, 0xfe, 0x5a, 0xa3, 0x5d, 0xb3); + static efi_char16_t efi_name[] = TASDEVICE_CALIBRATION_DATA_NAME; + unsigned char data[TASDEVICE_CALIBRATION_DATA_SIZE], *buf; + unsigned int attr, crc, offset, *tmp_val; + struct tm *tm = &tas_priv->tm; + unsigned long total_sz = 0; + efi_status_t status; + + tas_priv->cali_data[0] = 0; + status = efi.get_variable(efi_name, &efi_guid, &attr, &total_sz, data); + if (status == EFI_BUFFER_TOO_SMALL) { + if (total_sz > TASDEVICE_CALIBRATION_DATA_SIZE) + return -ENOMEM; + /* Get variable contents into buffer */ + status = efi.get_variable(efi_name, &efi_guid, &attr, + &total_sz, data); + } + if (status != EFI_SUCCESS) + return status; + + tmp_val = (unsigned int *)data; + if (tmp_val[0] == 2781) { + /* + * New features were added in calibrated Data V3: + * 1. Added calibration registers address define in + * a node, marked as Device id == 0x80. + * New features were added in calibrated Data V2: + * 1. Added some the fields to store the link_id and + * uniqie_id for multi-link solutions + * 2. Support flexible number of devices instead of + * fixed one in V1. + * Layout of calibrated data V2 in UEFI(total 256 bytes): + * ChipID (2781, 4 bytes) + * Device-Sum (4 bytes) + * TimeStamp of Calibration (4 bytes) + * for (i = 0; i < Device-Sum; i++) { + * Device #i index_info () { + * SDW link id (2bytes) + * SDW unique_id (2bytes) + * } // if Device number is 0x80, mean it's + * calibration registers address. + * Calibrated Data of Device #i (20 bytes) + * } + * CRC (4 bytes) + * Reserved (the rest) + */ + crc = crc32(~0, data, (3 + tmp_val[1] * 6) * 4) ^ ~0; + + if (crc != tmp_val[3 + tmp_val[1] * 6]) + return 0; + + time64_to_tm(tmp_val[2], 0, tm); + for (int j = 0; j < tmp_val[1]; j++) { + offset = j * 6 + 3; + if (tmp_val[offset] == tas_priv->index) { + for (int i = 0; i < CALIB_MAX; i++) + tas_priv->cali_data[i] = + tmp_val[offset + i + 1]; + } else if (tmp_val[offset] == + TASDEVICE_CALIBRATION_REG_ADDRESS) { + for (int i = 0; i < CALIB_MAX; i++) { + buf = &data[(offset + i + 1) * 4]; + tas_priv->cali_reg_array[i] = + TASDEVICE_REG(buf[1], buf[2], + buf[3]); + } + } + tas_priv->apply_calibration(tas_priv); + } + } else { + /* + * Calibration data is in V1 format. + * struct cali_data { + * char cali_data[20]; + * } + * + * struct { + * struct cali_data cali_data[4]; + * int TimeStamp of Calibration (4 bytes) + * int CRC (4 bytes) + * } ueft; + */ + crc = crc32(~0, data, 84) ^ ~0; + if (crc == tmp_val[21]) { + time64_to_tm(tmp_val[20], 0, tm); + for (int i = 0; i < CALIB_MAX; i++) + tas_priv->cali_data[i] = + tmp_val[tas_priv->index * 5 + i]; + tas_priv->apply_calibration(tas_priv); + } + } + + return 0; +} + +static void tas2781_hda_remove_controls(struct tas2781_hda *tas_hda) +{ + struct hda_codec *codec = tas_hda->priv->codec; + + snd_ctl_remove(codec->card, tas_hda->dsp_prog_ctl); + + snd_ctl_remove(codec->card, tas_hda->dsp_conf_ctl); + + for (int i = ARRAY_SIZE(tas_hda->snd_ctls) - 1; i >= 0; i--) + snd_ctl_remove(codec->card, tas_hda->snd_ctls[i]); + + snd_ctl_remove(codec->card, tas_hda->prof_ctl); +} + +static void tasdev_fw_ready(const struct firmware *fmw, void *context) +{ + struct tasdevice_priv *tas_priv = context; + struct tas2781_hda *tas_hda = dev_get_drvdata(tas_priv->dev); + struct hda_codec *codec = tas_priv->codec; + int i, j, ret; + + pm_runtime_get_sync(tas_priv->dev); + guard(mutex)(&tas_priv->codec_lock); + + ret = tasdevice_spi_rca_parser(tas_priv, fmw); + if (ret) + goto out; + + /* Add control one time only. */ + tas_hda->prof_ctl = snd_ctl_new1(&tas2781_prof_ctrl[tas_priv->index], + tas_priv); + ret = snd_ctl_add(codec->card, tas_hda->prof_ctl); + if (ret) { + dev_err(tas_priv->dev, "Failed to add KControl %s = %d\n", + tas2781_prof_ctrl[tas_priv->index].name, ret); + goto out; + } + j = tas_priv->index * ARRAY_SIZE(tas2781_snd_controls) / 2; + for (i = 0; i < 3; i++) { + tas_hda->snd_ctls[i] = snd_ctl_new1(&tas2781_snd_controls[i+j], + tas_priv); + ret = snd_ctl_add(codec->card, tas_hda->snd_ctls[i]); + if (ret) { + dev_err(tas_priv->dev, + "Failed to add KControl %s = %d\n", + tas2781_snd_controls[i+tas_priv->index*3].name, + ret); + goto out; + } + } + + tasdevice_spi_dsp_remove(tas_priv); + + tas_priv->fw_state = TASDEVICE_DSP_FW_PENDING; + scnprintf(tas_priv->coef_binaryname, 64, "TAS2XXX%08X-%01d.bin", + codec->core.subsystem_id, tas_priv->index); + ret = tasdevice_spi_dsp_parser(tas_priv); + if (ret) { + dev_err(tas_priv->dev, "dspfw load %s error\n", + tas_priv->coef_binaryname); + tas_priv->fw_state = TASDEVICE_DSP_FW_FAIL; + goto out; + } + + /* Add control one time only. */ + tas_hda->dsp_prog_ctl = + snd_ctl_new1(&tas2781_dsp_prog_ctrl[tas_priv->index], + tas_priv); + ret = snd_ctl_add(codec->card, tas_hda->dsp_prog_ctl); + if (ret) { + dev_err(tas_priv->dev, + "Failed to add KControl %s = %d\n", + tas2781_dsp_prog_ctrl[tas_priv->index].name, ret); + goto out; + } + + tas_hda->dsp_conf_ctl = + snd_ctl_new1(&tas2781_dsp_conf_ctrl[tas_priv->index], + tas_priv); + ret = snd_ctl_add(codec->card, tas_hda->dsp_conf_ctl); + if (ret) { + dev_err(tas_priv->dev, "Failed to add KControl %s = %d\n", + tas2781_dsp_conf_ctrl[tas_priv->index].name, ret); + goto out; + } + + /* Perform AMP reset before firmware download. */ + tas_priv->rcabin.profile_cfg_id = TAS2781_PRE_POST_RESET_CFG; + tasdevice_spi_tuning_switch(tas_priv, 0); + tas2781_spi_reset(tas_priv); + tas_priv->rcabin.profile_cfg_id = 0; + tasdevice_spi_tuning_switch(tas_priv, 1); + + tas_priv->fw_state = TASDEVICE_DSP_FW_ALL_OK; + ret = tasdevice_spi_prmg_load(tas_priv, 0); + if (ret < 0) { + dev_err(tas_priv->dev, "FW download failed = %d\n", ret); + goto out; + } + if (tas_priv->fmw->nr_programs > 0) + tas_priv->cur_prog = 0; + if (tas_priv->fmw->nr_configurations > 0) + tas_priv->cur_conf = 0; + + /* + * If calibrated data occurs error, dsp will still works with default + * calibrated data inside algo. + */ + tas_priv->save_calibration(tas_priv); + +out: + if (fmw) + release_firmware(fmw); + pm_runtime_mark_last_busy(tas_hda->priv->dev); + pm_runtime_put_autosuspend(tas_hda->priv->dev); +} + +static int tas2781_hda_bind(struct device *dev, struct device *master, + void *master_data) +{ + struct tas2781_hda *tas_hda = dev_get_drvdata(dev); + struct hda_component_parent *parent = master_data; + struct hda_component *comp; + struct hda_codec *codec; + int ret; + + comp = hda_component_from_index(parent, tas_hda->priv->index); + if (!comp) + return -EINVAL; + + if (comp->dev) + return -EBUSY; + + codec = parent->codec; + + pm_runtime_get_sync(dev); + + comp->dev = dev; + + strscpy(comp->name, dev_name(dev), sizeof(comp->name)); + + ret = tascodec_spi_init(tas_hda->priv, codec, THIS_MODULE, + tasdev_fw_ready); + if (!ret) + comp->playback_hook = tas2781_hda_playback_hook; + + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + return ret; +} + +static void tas2781_hda_unbind(struct device *dev, struct device *master, + void *master_data) +{ + struct tas2781_hda *tas_hda = dev_get_drvdata(dev); + struct hda_component_parent *parent = master_data; + struct hda_component *comp; + + comp = hda_component_from_index(parent, tas_hda->priv->index); + if (comp && (comp->dev == dev)) { + comp->dev = NULL; + memset(comp->name, 0, sizeof(comp->name)); + comp->playback_hook = NULL; + } + + tas2781_hda_remove_controls(tas_hda); + + tasdevice_spi_config_info_remove(tas_hda->priv); + tasdevice_spi_dsp_remove(tas_hda->priv); + + tas_hda->priv->fw_state = TASDEVICE_DSP_FW_PENDING; +} + +static const struct component_ops tas2781_hda_comp_ops = { + .bind = tas2781_hda_bind, + .unbind = tas2781_hda_unbind, +}; + +static void tas2781_hda_remove(struct device *dev) +{ + struct tas2781_hda *tas_hda = dev_get_drvdata(dev); + + component_del(tas_hda->priv->dev, &tas2781_hda_comp_ops); + + pm_runtime_get_sync(tas_hda->priv->dev); + pm_runtime_disable(tas_hda->priv->dev); + + pm_runtime_put_noidle(tas_hda->priv->dev); + + mutex_destroy(&tas_hda->priv->codec_lock); +} + +static int tas2781_hda_spi_probe(struct spi_device *spi) +{ + struct tasdevice_priv *tas_priv; + struct tas2781_hda *tas_hda; + const char *device_name; + int ret = 0; + + tas_hda = devm_kzalloc(&spi->dev, sizeof(*tas_hda), GFP_KERNEL); + if (!tas_hda) + return -ENOMEM; + + spi->max_speed_hz = TAS2781_SPI_MAX_FREQ; + + tas_priv = devm_kzalloc(&spi->dev, sizeof(*tas_priv), GFP_KERNEL); + if (!tas_priv) + return -ENOMEM; + tas_priv->dev = &spi->dev; + tas_hda->priv = tas_priv; + tas_priv->regmap = devm_regmap_init_spi(spi, &tasdevice_regmap); + if (IS_ERR(tas_priv->regmap)) { + ret = PTR_ERR(tas_priv->regmap); + dev_err(tas_priv->dev, "Failed to allocate regmap: %d\n", + ret); + return ret; + } + if (strstr(dev_name(&spi->dev), "TXNW2781")) { + device_name = "TXNW2781"; + tas_priv->save_calibration = tas2781_save_calibration; + tas_priv->apply_calibration = tas2781_apply_calib; + } else { + dev_err(tas_priv->dev, "Unmatched spi dev %s\n", + dev_name(&spi->dev)); + return -ENODEV; + } + + tas_priv->irq = spi->irq; + dev_set_drvdata(&spi->dev, tas_hda); + ret = tas2781_read_acpi(tas_hda, device_name, + spi_get_chipselect(spi, 0)); + if (ret) + return dev_err_probe(tas_priv->dev, ret, + "Platform not supported\n"); + + tasdevice_spi_init(tas_priv); + + ret = component_add(tas_priv->dev, &tas2781_hda_comp_ops); + if (ret) { + dev_err(tas_priv->dev, "Register component fail: %d\n", ret); + return ret; + } + + pm_runtime_set_autosuspend_delay(tas_priv->dev, 3000); + pm_runtime_use_autosuspend(tas_priv->dev); + pm_runtime_mark_last_busy(tas_priv->dev); + pm_runtime_set_active(tas_priv->dev); + pm_runtime_get_noresume(tas_priv->dev); + pm_runtime_enable(tas_priv->dev); + + pm_runtime_put_autosuspend(tas_priv->dev); + + return 0; +} + +static void tas2781_hda_spi_remove(struct spi_device *spi) +{ + tas2781_hda_remove(&spi->dev); +} + +static int tas2781_runtime_suspend(struct device *dev) +{ + struct tas2781_hda *tas_hda = dev_get_drvdata(dev); + + guard(mutex)(&tas_hda->priv->codec_lock); + + tasdevice_spi_tuning_switch(tas_hda->priv, 1); + + tas_hda->priv->cur_book = -1; + tas_hda->priv->cur_conf = -1; + + return 0; +} + +static int tas2781_runtime_resume(struct device *dev) +{ + struct tas2781_hda *tas_hda = dev_get_drvdata(dev); + + guard(mutex)(&tas_hda->priv->codec_lock); + + tasdevice_spi_tuning_switch(tas_hda->priv, 0); + + return 0; +} + +static int tas2781_system_suspend(struct device *dev) +{ + struct tas2781_hda *tas_hda = dev_get_drvdata(dev); + int ret; + + ret = pm_runtime_force_suspend(dev); + if (ret) + return ret; + + /* Shutdown chip before system suspend */ + tasdevice_spi_tuning_switch(tas_hda->priv, 1); + tas2781_spi_reset(tas_hda->priv); + /* + * Reset GPIO may be shared, so cannot reset here. + * However beyond this point, amps may be powered down. + */ + return 0; +} + +static int tas2781_system_resume(struct device *dev) +{ + struct tas2781_hda *tas_hda = dev_get_drvdata(dev); + int ret, val; + + ret = pm_runtime_force_resume(dev); + if (ret) + return ret; + + guard(mutex)(&tas_hda->priv->codec_lock); + ret = tasdevice_spi_dev_read(tas_hda->priv, TAS2781_REG_CLK_CONFIG, + &val); + if (ret < 0) + return ret; + + if (val == TAS2781_REG_CLK_CONFIG_RESET) { + tas_hda->priv->cur_book = -1; + tas_hda->priv->cur_conf = -1; + tas_hda->priv->cur_prog = -1; + + ret = tasdevice_spi_prmg_load(tas_hda->priv, 0); + if (ret < 0) { + dev_err(tas_hda->priv->dev, + "FW download failed = %d\n", ret); + return ret; + } + + if (tas_hda->priv->playback_started) + tasdevice_spi_tuning_switch(tas_hda->priv, 0); + } + + return ret; +} + +static const struct dev_pm_ops tas2781_hda_pm_ops = { + RUNTIME_PM_OPS(tas2781_runtime_suspend, tas2781_runtime_resume, NULL) + SYSTEM_SLEEP_PM_OPS(tas2781_system_suspend, tas2781_system_resume) +}; + +static const struct spi_device_id tas2781_hda_spi_id[] = { + { "tas2781-hda", }, + {} +}; + +static const struct acpi_device_id tas2781_acpi_hda_match[] = { + {"TXNW2781", }, + {} +}; +MODULE_DEVICE_TABLE(acpi, tas2781_acpi_hda_match); + +static struct spi_driver tas2781_hda_spi_driver = { + .driver = { + .name = "tas2781-hda", + .acpi_match_table = tas2781_acpi_hda_match, + .pm = &tas2781_hda_pm_ops, + }, + .id_table = tas2781_hda_spi_id, + .probe = tas2781_hda_spi_probe, + .remove = tas2781_hda_spi_remove, +}; +module_spi_driver(tas2781_hda_spi_driver); + +MODULE_DESCRIPTION("TAS2781 HDA SPI Driver"); +MODULE_AUTHOR("Baojun, Xu, "); +MODULE_LICENSE("GPL"); diff --git a/sound/pci/hda/tas2781_spi_fwlib.c b/sound/pci/hda/tas2781_spi_fwlib.c new file mode 100644 index 00000000000000..0e2acbc3c90017 --- /dev/null +++ b/sound/pci/hda/tas2781_spi_fwlib.c @@ -0,0 +1,2006 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// TAS2781 HDA SPI driver +// +// Copyright 2024 Texas Instruments, Inc. +// +// Author: Baojun Xu + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tas2781-spi.h" + +#define OFFSET_ERROR_BIT BIT(31) + +#define ERROR_PRAM_CRCCHK 0x0000000 +#define ERROR_YRAM_CRCCHK 0x0000001 +#define PPC_DRIVER_CRCCHK 0x00000200 + +#define TAS2781_SA_COEFF_SWAP_REG TASDEVICE_REG(0, 0x35, 0x2c) +#define TAS2781_YRAM_BOOK1 140 +#define TAS2781_YRAM1_PAGE 42 +#define TAS2781_YRAM1_START_REG 88 + +#define TAS2781_YRAM2_START_PAGE 43 +#define TAS2781_YRAM2_END_PAGE 49 +#define TAS2781_YRAM2_START_REG 8 +#define TAS2781_YRAM2_END_REG 127 + +#define TAS2781_YRAM3_PAGE 50 +#define TAS2781_YRAM3_START_REG 8 +#define TAS2781_YRAM3_END_REG 27 + +/* should not include B0_P53_R44-R47 */ +#define TAS2781_YRAM_BOOK2 0 +#define TAS2781_YRAM4_START_PAGE 50 +#define TAS2781_YRAM4_END_PAGE 60 + +#define TAS2781_YRAM5_PAGE 61 +#define TAS2781_YRAM5_START_REG TAS2781_YRAM3_START_REG +#define TAS2781_YRAM5_END_REG TAS2781_YRAM3_END_REG + +#define TASDEVICE_MAXPROGRAM_NUM_KERNEL 5 +#define TASDEVICE_MAXCONFIG_NUM_KERNEL_MULTIPLE_AMPS 64 +#define TASDEVICE_MAXCONFIG_NUM_KERNEL 10 +#define MAIN_ALL_DEVICES_1X 0x01 +#define MAIN_DEVICE_A_1X 0x02 +#define MAIN_DEVICE_B_1X 0x03 +#define MAIN_DEVICE_C_1X 0x04 +#define MAIN_DEVICE_D_1X 0x05 +#define COEFF_DEVICE_A_1X 0x12 +#define COEFF_DEVICE_B_1X 0x13 +#define COEFF_DEVICE_C_1X 0x14 +#define COEFF_DEVICE_D_1X 0x15 +#define PRE_DEVICE_A_1X 0x22 +#define PRE_DEVICE_B_1X 0x23 +#define PRE_DEVICE_C_1X 0x24 +#define PRE_DEVICE_D_1X 0x25 +#define PRE_SOFTWARE_RESET_DEVICE_A 0x41 +#define PRE_SOFTWARE_RESET_DEVICE_B 0x42 +#define PRE_SOFTWARE_RESET_DEVICE_C 0x43 +#define PRE_SOFTWARE_RESET_DEVICE_D 0x44 +#define POST_SOFTWARE_RESET_DEVICE_A 0x45 +#define POST_SOFTWARE_RESET_DEVICE_B 0x46 +#define POST_SOFTWARE_RESET_DEVICE_C 0x47 +#define POST_SOFTWARE_RESET_DEVICE_D 0x48 + +struct tas_crc { + unsigned char offset; + unsigned char len; +}; + +struct blktyp_devidx_map { + unsigned char blktyp; + unsigned char dev_idx; +}; + +/* fixed m68k compiling issue: mapping table can save code field */ +static const struct blktyp_devidx_map ppc3_tas2781_mapping_table[] = { + { MAIN_ALL_DEVICES_1X, 0x80 }, + { MAIN_DEVICE_A_1X, 0x81 }, + { COEFF_DEVICE_A_1X, 0x81 }, + { PRE_DEVICE_A_1X, 0x81 }, + { PRE_SOFTWARE_RESET_DEVICE_A, 0xC1 }, + { POST_SOFTWARE_RESET_DEVICE_A, 0xC1 }, + { MAIN_DEVICE_B_1X, 0x82 }, + { COEFF_DEVICE_B_1X, 0x82 }, + { PRE_DEVICE_B_1X, 0x82 }, + { PRE_SOFTWARE_RESET_DEVICE_B, 0xC2 }, + { POST_SOFTWARE_RESET_DEVICE_B, 0xC2 }, + { MAIN_DEVICE_C_1X, 0x83 }, + { COEFF_DEVICE_C_1X, 0x83 }, + { PRE_DEVICE_C_1X, 0x83 }, + { PRE_SOFTWARE_RESET_DEVICE_C, 0xC3 }, + { POST_SOFTWARE_RESET_DEVICE_C, 0xC3 }, + { MAIN_DEVICE_D_1X, 0x84 }, + { COEFF_DEVICE_D_1X, 0x84 }, + { PRE_DEVICE_D_1X, 0x84 }, + { PRE_SOFTWARE_RESET_DEVICE_D, 0xC4 }, + { POST_SOFTWARE_RESET_DEVICE_D, 0xC4 }, +}; + +static const struct blktyp_devidx_map ppc3_mapping_table[] = { + { MAIN_ALL_DEVICES_1X, 0x80 }, + { MAIN_DEVICE_A_1X, 0x81 }, + { COEFF_DEVICE_A_1X, 0xC1 }, + { PRE_DEVICE_A_1X, 0xC1 }, + { MAIN_DEVICE_B_1X, 0x82 }, + { COEFF_DEVICE_B_1X, 0xC2 }, + { PRE_DEVICE_B_1X, 0xC2 }, + { MAIN_DEVICE_C_1X, 0x83 }, + { COEFF_DEVICE_C_1X, 0xC3 }, + { PRE_DEVICE_C_1X, 0xC3 }, + { MAIN_DEVICE_D_1X, 0x84 }, + { COEFF_DEVICE_D_1X, 0xC4 }, + { PRE_DEVICE_D_1X, 0xC4 }, +}; + +static const struct blktyp_devidx_map non_ppc3_mapping_table[] = { + { MAIN_ALL_DEVICES, 0x80 }, + { MAIN_DEVICE_A, 0x81 }, + { COEFF_DEVICE_A, 0xC1 }, + { PRE_DEVICE_A, 0xC1 }, + { MAIN_DEVICE_B, 0x82 }, + { COEFF_DEVICE_B, 0xC2 }, + { PRE_DEVICE_B, 0xC2 }, + { MAIN_DEVICE_C, 0x83 }, + { COEFF_DEVICE_C, 0xC3 }, + { PRE_DEVICE_C, 0xC3 }, + { MAIN_DEVICE_D, 0x84 }, + { COEFF_DEVICE_D, 0xC4 }, + { PRE_DEVICE_D, 0xC4 }, +}; + +/* + * Device support different configurations for different scene, + * like voice, music, calibration, was write in regbin file. + * Will be stored into tas_priv after regbin was loaded. + */ +static struct tasdevice_config_info *tasdevice_add_config( + struct tasdevice_priv *tas_priv, unsigned char *config_data, + unsigned int config_size, int *status) +{ + struct tasdevice_config_info *cfg_info; + struct tasdev_blk_data **bk_da; + unsigned int config_offset = 0; + unsigned int i; + + /* + * In most projects are many audio cases, such as music, handfree, + * receiver, games, audio-to-haptics, PMIC record, bypass mode, + * portrait, landscape, etc. Even in multiple audios, one or + * two of the chips will work for the special case, such as + * ultrasonic application. In order to support these variable-numbers + * of audio cases, flexible configs have been introduced in the + * DSP firmware. + */ + cfg_info = kzalloc(sizeof(*cfg_info), GFP_KERNEL); + if (!cfg_info) { + *status = -ENOMEM; + return NULL; + } + + if (tas_priv->rcabin.fw_hdr.binary_version_num >= 0x105) { + if ((config_offset + 64) > config_size) { + *status = -EINVAL; + dev_err(tas_priv->dev, "add conf: Out of boundary\n"); + goto config_err; + } + config_offset += 64; + } + + if ((config_offset + 4) > config_size) { + *status = -EINVAL; + dev_err(tas_priv->dev, "add config: Out of boundary\n"); + goto config_err; + } + + /* + * convert data[offset], data[offset + 1], data[offset + 2] and + * data[offset + 3] into host + */ + cfg_info->nblocks = get_unaligned_be32(&config_data[config_offset]); + config_offset += 4; + + /* + * Several kinds of dsp/algorithm firmwares can run on tas2781, + * the number and size of blk are not fixed and different among + * these firmwares. + */ + bk_da = cfg_info->blk_data = kcalloc(cfg_info->nblocks, + sizeof(*bk_da), GFP_KERNEL); + if (!bk_da) { + *status = -ENOMEM; + goto config_err; + } + cfg_info->real_nblocks = 0; + for (i = 0; i < cfg_info->nblocks; i++) { + if (config_offset + 12 > config_size) { + *status = -EINVAL; + dev_err(tas_priv->dev, + "%s: Out of boundary: i = %d nblocks = %u!\n", + __func__, i, cfg_info->nblocks); + goto block_err; + } + bk_da[i] = kzalloc(sizeof(*bk_da[i]), GFP_KERNEL); + if (!bk_da[i]) { + *status = -ENOMEM; + goto block_err; + } + + bk_da[i]->dev_idx = config_data[config_offset]; + config_offset++; + + bk_da[i]->block_type = config_data[config_offset]; + config_offset++; + + bk_da[i]->yram_checksum = + get_unaligned_be16(&config_data[config_offset]); + config_offset += 2; + bk_da[i]->block_size = + get_unaligned_be32(&config_data[config_offset]); + config_offset += 4; + + bk_da[i]->n_subblks = + get_unaligned_be32(&config_data[config_offset]); + + config_offset += 4; + + if (config_offset + bk_da[i]->block_size > config_size) { + *status = -EINVAL; + dev_err(tas_priv->dev, + "%s: Out of boundary: i = %d blks = %u!\n", + __func__, i, cfg_info->nblocks); + goto block_err; + } + /* instead of kzalloc+memcpy */ + bk_da[i]->regdata = kmemdup(&config_data[config_offset], + bk_da[i]->block_size, GFP_KERNEL); + if (!bk_da[i]->regdata) { + *status = -ENOMEM; + i++; + goto block_err; + } + + config_offset += bk_da[i]->block_size; + cfg_info->real_nblocks += 1; + } + + return cfg_info; +block_err: + for (int j = 0; j < i; j++) + kfree(bk_da[j]); + kfree(bk_da); +config_err: + kfree(cfg_info); + return NULL; +} + +/* Regbin file parser function. */ +int tasdevice_spi_rca_parser(void *context, const struct firmware *fmw) +{ + struct tasdevice_priv *tas_priv = context; + struct tasdevice_config_info **cfg_info; + struct tasdevice_rca_hdr *fw_hdr; + struct tasdevice_rca *rca; + unsigned int total_config_sz = 0; + int offset = 0, ret = 0, i; + unsigned char *buf; + + rca = &tas_priv->rcabin; + fw_hdr = &rca->fw_hdr; + if (!fmw || !fmw->data) { + dev_err(tas_priv->dev, "Failed to read %s\n", + tas_priv->rca_binaryname); + tas_priv->fw_state = TASDEVICE_DSP_FW_FAIL; + return -EINVAL; + } + buf = (unsigned char *)fmw->data; + fw_hdr->img_sz = get_unaligned_be32(&buf[offset]); + offset += 4; + if (fw_hdr->img_sz != fmw->size) { + dev_err(tas_priv->dev, + "File size not match, %d %u", (int)fmw->size, + fw_hdr->img_sz); + tas_priv->fw_state = TASDEVICE_DSP_FW_FAIL; + return -EINVAL; + } + + fw_hdr->checksum = get_unaligned_be32(&buf[offset]); + offset += 4; + fw_hdr->binary_version_num = get_unaligned_be32(&buf[offset]); + if (fw_hdr->binary_version_num < 0x103) { + dev_err(tas_priv->dev, "File version 0x%04x is too low", + fw_hdr->binary_version_num); + tas_priv->fw_state = TASDEVICE_DSP_FW_FAIL; + return -EINVAL; + } + offset += 4; + fw_hdr->drv_fw_version = get_unaligned_be32(&buf[offset]); + offset += 8; + fw_hdr->plat_type = buf[offset++]; + fw_hdr->dev_family = buf[offset++]; + fw_hdr->reserve = buf[offset++]; + fw_hdr->ndev = buf[offset++]; + if (offset + TASDEVICE_DEVICE_SUM > fw_hdr->img_sz) { + dev_err(tas_priv->dev, "rca_ready: Out of boundary!\n"); + tas_priv->fw_state = TASDEVICE_DSP_FW_FAIL; + return -EINVAL; + } + + for (i = 0; i < TASDEVICE_DEVICE_SUM; i++, offset++) + fw_hdr->devs[i] = buf[offset]; + + fw_hdr->nconfig = get_unaligned_be32(&buf[offset]); + offset += 4; + + for (i = 0; i < TASDEVICE_CONFIG_SUM; i++) { + fw_hdr->config_size[i] = get_unaligned_be32(&buf[offset]); + offset += 4; + total_config_sz += fw_hdr->config_size[i]; + } + + if (fw_hdr->img_sz - total_config_sz != (unsigned int)offset) { + dev_err(tas_priv->dev, "Bin file err %d - %d != %d!\n", + fw_hdr->img_sz, total_config_sz, (int)offset); + tas_priv->fw_state = TASDEVICE_DSP_FW_FAIL; + return -EINVAL; + } + + cfg_info = kcalloc(fw_hdr->nconfig, sizeof(*cfg_info), GFP_KERNEL); + if (!cfg_info) { + tas_priv->fw_state = TASDEVICE_DSP_FW_FAIL; + return -ENOMEM; + } + rca->cfg_info = cfg_info; + rca->ncfgs = 0; + for (i = 0; i < (int)fw_hdr->nconfig; i++) { + rca->ncfgs += 1; + cfg_info[i] = tasdevice_add_config(tas_priv, &buf[offset], + fw_hdr->config_size[i], &ret); + if (ret) { + tas_priv->fw_state = TASDEVICE_DSP_FW_FAIL; + return ret; + } + offset += (int)fw_hdr->config_size[i]; + } + + return ret; +} + +/* fixed m68k compiling issue: mapping table can save code field */ +static unsigned char map_dev_idx(struct tasdevice_fw *tas_fmw, + struct tasdev_blk *block) +{ + struct blktyp_devidx_map *p = + (struct blktyp_devidx_map *)non_ppc3_mapping_table; + struct tasdevice_dspfw_hdr *fw_hdr = &tas_fmw->fw_hdr; + struct tasdevice_fw_fixed_hdr *fw_fixed_hdr = &fw_hdr->fixed_hdr; + int i, n = ARRAY_SIZE(non_ppc3_mapping_table); + unsigned char dev_idx = 0; + + if (fw_fixed_hdr->ppcver >= PPC3_VERSION_TAS2781) { + p = (struct blktyp_devidx_map *)ppc3_tas2781_mapping_table; + n = ARRAY_SIZE(ppc3_tas2781_mapping_table); + } else if (fw_fixed_hdr->ppcver >= PPC3_VERSION) { + p = (struct blktyp_devidx_map *)ppc3_mapping_table; + n = ARRAY_SIZE(ppc3_mapping_table); + } + + for (i = 0; i < n; i++) { + if (block->type == p[i].blktyp) { + dev_idx = p[i].dev_idx; + break; + } + } + + return dev_idx; +} + +/* Block parser function. */ +static int fw_parse_block_data_kernel(struct tasdevice_fw *tas_fmw, + struct tasdev_blk *block, const struct firmware *fmw, int offset) +{ + const unsigned char *data = fmw->data; + + if (offset + 16 > fmw->size) { + dev_err(tas_fmw->dev, "%s: File Size error\n", __func__); + return -EINVAL; + } + + /* + * Convert data[offset], data[offset + 1], data[offset + 2] and + * data[offset + 3] into host. + */ + block->type = get_unaligned_be32(&data[offset]); + offset += 4; + + block->is_pchksum_present = data[offset++]; + block->pchksum = data[offset++]; + block->is_ychksum_present = data[offset++]; + block->ychksum = data[offset++]; + block->blk_size = get_unaligned_be32(&data[offset]); + offset += 4; + block->nr_subblocks = get_unaligned_be32(&data[offset]); + offset += 4; + + /* + * Fixed m68k compiling issue: + * 1. mapping table can save code field. + * 2. storing the dev_idx as a member of block can reduce unnecessary + * time and system resource comsumption of dev_idx mapping every + * time the block data writing to the dsp. + */ + block->dev_idx = map_dev_idx(tas_fmw, block); + + if (offset + block->blk_size > fmw->size) { + dev_err(tas_fmw->dev, "%s: nSublocks error\n", __func__); + return -EINVAL; + } + /* instead of kzalloc+memcpy */ + block->data = kmemdup(&data[offset], block->blk_size, GFP_KERNEL); + if (!block->data) + return -ENOMEM; + + offset += block->blk_size; + + return offset; +} + +/* Data of block parser function. */ +static int fw_parse_data_kernel(struct tasdevice_fw *tas_fmw, + struct tasdevice_data *img_data, const struct firmware *fmw, + int offset) +{ + const unsigned char *data = fmw->data; + struct tasdev_blk *blk; + unsigned int i; + + if (offset + 4 > fmw->size) { + dev_err(tas_fmw->dev, "%s: File Size error\n", __func__); + return -EINVAL; + } + img_data->nr_blk = get_unaligned_be32(&data[offset]); + offset += 4; + + img_data->dev_blks = kcalloc(img_data->nr_blk, + sizeof(struct tasdev_blk), GFP_KERNEL); + if (!img_data->dev_blks) + return -ENOMEM; + + for (i = 0; i < img_data->nr_blk; i++) { + blk = &img_data->dev_blks[i]; + offset = fw_parse_block_data_kernel( + tas_fmw, blk, fmw, offset); + if (offset < 0) { + kfree(img_data->dev_blks); + return -EINVAL; + } + } + + return offset; +} + +/* Data of DSP program parser function. */ +static int fw_parse_program_data_kernel( + struct tasdevice_priv *tas_priv, struct tasdevice_fw *tas_fmw, + const struct firmware *fmw, int offset) +{ + struct tasdevice_prog *program; + unsigned int i; + + for (i = 0; i < tas_fmw->nr_programs; i++) { + program = &tas_fmw->programs[i]; + if (offset + 72 > fmw->size) { + dev_err(tas_priv->dev, "%s: mpName error\n", __func__); + return -EINVAL; + } + /* skip 72 unused byts */ + offset += 72; + + offset = fw_parse_data_kernel(tas_fmw, &program->dev_data, + fmw, offset); + if (offset < 0) + break; + } + + return offset; +} + +/* Data of DSP configurations parser function. */ +static int fw_parse_configuration_data_kernel(struct tasdevice_priv *tas_priv, + struct tasdevice_fw *tas_fmw, const struct firmware *fmw, int offset) +{ + const unsigned char *data = fmw->data; + struct tasdevice_config *config; + unsigned int i; + + for (i = 0; i < tas_fmw->nr_configurations; i++) { + config = &tas_fmw->configs[i]; + if (offset + 80 > fmw->size) { + dev_err(tas_priv->dev, "%s: mpName error\n", __func__); + return -EINVAL; + } + memcpy(config->name, &data[offset], 64); + /* skip extra 16 bytes */ + offset += 80; + + offset = fw_parse_data_kernel(tas_fmw, &config->dev_data, + fmw, offset); + if (offset < 0) + break; + } + + return offset; +} + +/* DSP firmware file header parser function for early PPC3 firmware binary. */ +static int fw_parse_variable_header_kernel(struct tasdevice_priv *tas_priv, + const struct firmware *fmw, int offset) +{ + struct tasdevice_fw *tas_fmw = tas_priv->fmw; + struct tasdevice_dspfw_hdr *fw_hdr = &tas_fmw->fw_hdr; + struct tasdevice_config *config; + struct tasdevice_prog *program; + const unsigned char *buf = fmw->data; + unsigned short max_confs; + unsigned int i; + + if (offset + 12 + 4 * TASDEVICE_MAXPROGRAM_NUM_KERNEL > fmw->size) { + dev_err(tas_priv->dev, "%s: File Size error\n", __func__); + return -EINVAL; + } + fw_hdr->device_family = get_unaligned_be16(&buf[offset]); + if (fw_hdr->device_family != 0) { + dev_err(tas_priv->dev, "%s:not TAS device\n", __func__); + return -EINVAL; + } + offset += 2; + fw_hdr->device = get_unaligned_be16(&buf[offset]); + if (fw_hdr->device >= TASDEVICE_DSP_TAS_MAX_DEVICE || + fw_hdr->device == 6) { + dev_err(tas_priv->dev, "Unsupported dev %d\n", fw_hdr->device); + return -EINVAL; + } + offset += 2; + + tas_fmw->nr_programs = get_unaligned_be32(&buf[offset]); + offset += 4; + + if (tas_fmw->nr_programs == 0 || + tas_fmw->nr_programs > TASDEVICE_MAXPROGRAM_NUM_KERNEL) { + dev_err(tas_priv->dev, "mnPrograms is invalid\n"); + return -EINVAL; + } + + tas_fmw->programs = kcalloc(tas_fmw->nr_programs, + sizeof(*tas_fmw->programs), GFP_KERNEL); + if (!tas_fmw->programs) + return -ENOMEM; + + for (i = 0; i < tas_fmw->nr_programs; i++) { + program = &tas_fmw->programs[i]; + program->prog_size = get_unaligned_be32(&buf[offset]); + offset += 4; + } + + /* Skip the unused prog_size */ + offset += 4 * (TASDEVICE_MAXPROGRAM_NUM_KERNEL - tas_fmw->nr_programs); + + tas_fmw->nr_configurations = get_unaligned_be32(&buf[offset]); + offset += 4; + + /* + * The max number of config in firmware greater than 4 pieces of + * tas2781s is different from the one lower than 4 pieces of + * tas2781s. + */ + max_confs = TASDEVICE_MAXCONFIG_NUM_KERNEL; + if (tas_fmw->nr_configurations == 0 || + tas_fmw->nr_configurations > max_confs) { + dev_err(tas_priv->dev, "%s: Conf is invalid\n", __func__); + kfree(tas_fmw->programs); + return -EINVAL; + } + + if (offset + 4 * max_confs > fmw->size) { + dev_err(tas_priv->dev, "%s: mpConfigurations err\n", __func__); + kfree(tas_fmw->programs); + return -EINVAL; + } + + tas_fmw->configs = kcalloc(tas_fmw->nr_configurations, + sizeof(*tas_fmw->configs), GFP_KERNEL); + if (!tas_fmw->configs) { + kfree(tas_fmw->programs); + return -ENOMEM; + } + + for (i = 0; i < tas_fmw->nr_programs; i++) { + config = &tas_fmw->configs[i]; + config->cfg_size = get_unaligned_be32(&buf[offset]); + offset += 4; + } + + /* Skip the unused configs */ + offset += 4 * (max_confs - tas_fmw->nr_programs); + + return offset; +} + +/* + * In sub-block data, have three type sub-block: + * 1. Single byte write. + * 2. Multi-byte write. + * 3. Delay. + * 4. Bits update. + * This function perform single byte write to device. + */ +static int tasdevice_single_byte_wr(void *context, int dev_idx, + unsigned char *data, int sublocksize) +{ + struct tasdevice_priv *tas_priv = context; + unsigned short len = get_unaligned_be16(&data[2]); + int i, subblk_offset, rc; + + subblk_offset = 4; + if (subblk_offset + 4 * len > sublocksize) { + dev_err(tas_priv->dev, "process_block: Out of boundary\n"); + return 0; + } + + for (i = 0; i < len; i++) { + if (dev_idx == (tas_priv->index + 1) || dev_idx == 0) { + rc = tasdevice_spi_dev_write(tas_priv, + TASDEVICE_REG(data[subblk_offset], + data[subblk_offset + 1], + data[subblk_offset + 2]), + data[subblk_offset + 3]); + if (rc < 0) { + dev_err(tas_priv->dev, + "process_block: single write error\n"); + subblk_offset |= OFFSET_ERROR_BIT; + } + } + subblk_offset += 4; + } + + return subblk_offset; +} + +/* + * In sub-block data, have three type sub-block: + * 1. Single byte write. + * 2. Multi-byte write. + * 3. Delay. + * 4. Bits update. + * This function perform multi-write to device. + */ +static int tasdevice_burst_wr(void *context, int dev_idx, unsigned char *data, + int sublocksize) +{ + struct tasdevice_priv *tas_priv = context; + unsigned short len = get_unaligned_be16(&data[2]); + int subblk_offset, rc; + + subblk_offset = 4; + if (subblk_offset + 4 + len > sublocksize) { + dev_err(tas_priv->dev, "%s: BST Out of boundary\n", __func__); + subblk_offset |= OFFSET_ERROR_BIT; + } + if (len % 4) { + dev_err(tas_priv->dev, "%s:Bst-len(%u)not div by 4\n", + __func__, len); + subblk_offset |= OFFSET_ERROR_BIT; + } + + if (dev_idx == (tas_priv->index + 1) || dev_idx == 0) { + rc = tasdevice_spi_dev_bulk_write(tas_priv, + TASDEVICE_REG(data[subblk_offset], + data[subblk_offset + 1], + data[subblk_offset + 2]), + &data[subblk_offset + 4], len); + if (rc < 0) { + dev_err(tas_priv->dev, "%s: bulk_write error = %d\n", + __func__, rc); + subblk_offset |= OFFSET_ERROR_BIT; + } + } + subblk_offset += (len + 4); + + return subblk_offset; +} + +/* Just delay for ms.*/ +static int tasdevice_delay(void *context, int dev_idx, unsigned char *data, + int sublocksize) +{ + struct tasdevice_priv *tas_priv = context; + unsigned int sleep_time, subblk_offset = 2; + + if (subblk_offset + 2 > sublocksize) { + dev_err(tas_priv->dev, "%s: delay Out of boundary\n", + __func__); + subblk_offset |= OFFSET_ERROR_BIT; + } + if (dev_idx == (tas_priv->index + 1) || dev_idx == 0) { + sleep_time = get_unaligned_be16(&data[2]) * 1000; + fsleep(sleep_time); + } + subblk_offset += 2; + + return subblk_offset; +} + +/* + * In sub-block data, have three type sub-block: + * 1. Single byte write. + * 2. Multi-byte write. + * 3. Delay. + * 4. Bits update. + * This function perform bits update. + */ +static int tasdevice_field_wr(void *context, int dev_idx, unsigned char *data, + int sublocksize) +{ + struct tasdevice_priv *tas_priv = context; + int rc, subblk_offset = 2; + + if (subblk_offset + 6 > sublocksize) { + dev_err(tas_priv->dev, "%s: bit write Out of boundary\n", + __func__); + subblk_offset |= OFFSET_ERROR_BIT; + } + if (dev_idx == (tas_priv->index + 1) || dev_idx == 0) { + rc = tasdevice_spi_dev_update_bits(tas_priv, + TASDEVICE_REG(data[subblk_offset + 2], + data[subblk_offset + 3], + data[subblk_offset + 4]), + data[subblk_offset + 1], + data[subblk_offset + 5]); + if (rc < 0) { + dev_err(tas_priv->dev, "%s: update_bits error = %d\n", + __func__, rc); + subblk_offset |= OFFSET_ERROR_BIT; + } + } + subblk_offset += 6; + + return subblk_offset; +} + +/* Data block process function. */ +static int tasdevice_process_block(void *context, unsigned char *data, + unsigned char dev_idx, int sublocksize) +{ + struct tasdevice_priv *tas_priv = context; + int blktyp = dev_idx & 0xC0, subblk_offset; + unsigned char subblk_typ = data[1]; + + switch (subblk_typ) { + case TASDEVICE_CMD_SING_W: + subblk_offset = tasdevice_single_byte_wr(tas_priv, + dev_idx & 0x4f, data, sublocksize); + break; + case TASDEVICE_CMD_BURST: + subblk_offset = tasdevice_burst_wr(tas_priv, + dev_idx & 0x4f, data, sublocksize); + break; + case TASDEVICE_CMD_DELAY: + subblk_offset = tasdevice_delay(tas_priv, + dev_idx & 0x4f, data, sublocksize); + break; + case TASDEVICE_CMD_FIELD_W: + subblk_offset = tasdevice_field_wr(tas_priv, + dev_idx & 0x4f, data, sublocksize); + break; + default: + subblk_offset = 2; + break; + } + if (((subblk_offset & OFFSET_ERROR_BIT) != 0) && blktyp != 0) { + if (blktyp == 0x80) { + tas_priv->cur_prog = -1; + tas_priv->cur_conf = -1; + } else + tas_priv->cur_conf = -1; + } + subblk_offset &= ~OFFSET_ERROR_BIT; + + return subblk_offset; +} + +/* + * Device support different configurations for different scene, + * this function was used for choose different config. + */ +void tasdevice_spi_select_cfg_blk(void *pContext, int conf_no, + unsigned char block_type) +{ + struct tasdevice_priv *tas_priv = pContext; + struct tasdevice_rca *rca = &tas_priv->rcabin; + struct tasdevice_config_info **cfg_info = rca->cfg_info; + struct tasdev_blk_data **blk_data; + unsigned int j, k; + + if (conf_no >= rca->ncfgs || conf_no < 0 || !cfg_info) { + dev_err(tas_priv->dev, "conf_no should be not more than %u\n", + rca->ncfgs); + return; + } + blk_data = cfg_info[conf_no]->blk_data; + + for (j = 0; j < cfg_info[conf_no]->real_nblocks; j++) { + unsigned int length = 0, rc = 0; + + if (block_type > 5 || block_type < 2) { + dev_err(tas_priv->dev, + "block_type should be in range from 2 to 5\n"); + break; + } + if (block_type != blk_data[j]->block_type) + continue; + + for (k = 0; k < blk_data[j]->n_subblks; k++) { + tas_priv->is_loading = true; + + rc = tasdevice_process_block(tas_priv, + blk_data[j]->regdata + length, + blk_data[j]->dev_idx, + blk_data[j]->block_size - length); + length += rc; + if (blk_data[j]->block_size < length) { + dev_err(tas_priv->dev, + "%s: %u %u out of boundary\n", + __func__, length, + blk_data[j]->block_size); + break; + } + } + if (length != blk_data[j]->block_size) + dev_err(tas_priv->dev, "%s: %u %u size is not same\n", + __func__, length, blk_data[j]->block_size); + } +} + +/* Block process function. */ +static int tasdevice_load_block_kernel( + struct tasdevice_priv *tasdevice, struct tasdev_blk *block) +{ + const unsigned int blk_size = block->blk_size; + unsigned char *data = block->data; + unsigned int i, length; + + for (i = 0, length = 0; i < block->nr_subblocks; i++) { + int rc = tasdevice_process_block(tasdevice, data + length, + block->dev_idx, blk_size - length); + if (rc < 0) { + dev_err(tasdevice->dev, + "%s: %u %u sublock write error\n", + __func__, length, blk_size); + return rc; + } + length += rc; + if (blk_size < length) { + dev_err(tasdevice->dev, "%s: %u %u out of boundary\n", + __func__, length, blk_size); + rc = -ENOMEM; + return rc; + } + } + + return 0; +} + +/* DSP firmware file header parser function. */ +static int fw_parse_variable_hdr(struct tasdevice_priv *tas_priv, + struct tasdevice_dspfw_hdr *fw_hdr, + const struct firmware *fmw, int offset) +{ + const unsigned char *buf = fmw->data; + int len = strlen((char *)&buf[offset]); + + len++; + + if (offset + len + 8 > fmw->size) { + dev_err(tas_priv->dev, "%s: File Size error\n", __func__); + return -EINVAL; + } + + offset += len; + + fw_hdr->device_family = get_unaligned_be32(&buf[offset]); + if (fw_hdr->device_family != 0) { + dev_err(tas_priv->dev, "%s: not TAS device\n", __func__); + return -EINVAL; + } + offset += 4; + + fw_hdr->device = get_unaligned_be32(&buf[offset]); + if (fw_hdr->device >= TASDEVICE_DSP_TAS_MAX_DEVICE || + fw_hdr->device == 6) { + dev_err(tas_priv->dev, "Unsupported dev %d\n", fw_hdr->device); + return -EINVAL; + } + offset += 4; + fw_hdr->ndev = 1; + + return offset; +} + +/* DSP firmware file header parser function for size variabled header. */ +static int fw_parse_variable_header_git(struct tasdevice_priv + *tas_priv, const struct firmware *fmw, int offset) +{ + struct tasdevice_fw *tas_fmw = tas_priv->fmw; + struct tasdevice_dspfw_hdr *fw_hdr = &tas_fmw->fw_hdr; + + offset = fw_parse_variable_hdr(tas_priv, fw_hdr, fmw, offset); + + return offset; +} + +/* DSP firmware file block parser function. */ +static int fw_parse_block_data(struct tasdevice_fw *tas_fmw, + struct tasdev_blk *block, const struct firmware *fmw, int offset) +{ + unsigned char *data = (unsigned char *)fmw->data; + int n; + + if (offset + 8 > fmw->size) { + dev_err(tas_fmw->dev, "%s: Type error\n", __func__); + return -EINVAL; + } + block->type = get_unaligned_be32(&data[offset]); + offset += 4; + + if (tas_fmw->fw_hdr.fixed_hdr.drv_ver >= PPC_DRIVER_CRCCHK) { + if (offset + 8 > fmw->size) { + dev_err(tas_fmw->dev, "PChkSumPresent error\n"); + return -EINVAL; + } + block->is_pchksum_present = data[offset]; + offset++; + + block->pchksum = data[offset]; + offset++; + + block->is_ychksum_present = data[offset]; + offset++; + + block->ychksum = data[offset]; + offset++; + } else { + block->is_pchksum_present = 0; + block->is_ychksum_present = 0; + } + + block->nr_cmds = get_unaligned_be32(&data[offset]); + offset += 4; + + n = block->nr_cmds * 4; + if (offset + n > fmw->size) { + dev_err(tas_fmw->dev, + "%s: File Size(%lu) error offset = %d n = %d\n", + __func__, (unsigned long)fmw->size, offset, n); + return -EINVAL; + } + /* instead of kzalloc+memcpy */ + block->data = kmemdup(&data[offset], n, GFP_KERNEL); + if (!block->data) + return -ENOMEM; + + offset += n; + + return offset; +} + +/* + * When parsing error occurs, all the memory resource will be released + * in the end of tasdevice_rca_ready. + */ +static int fw_parse_data(struct tasdevice_fw *tas_fmw, + struct tasdevice_data *img_data, const struct firmware *fmw, + int offset) +{ + const unsigned char *data = (unsigned char *)fmw->data; + struct tasdev_blk *blk; + unsigned int i, n; + + if (offset + 64 > fmw->size) { + dev_err(tas_fmw->dev, "%s: Name error\n", __func__); + return -EINVAL; + } + memcpy(img_data->name, &data[offset], 64); + offset += 64; + + n = strlen((char *)&data[offset]); + n++; + if (offset + n + 2 > fmw->size) { + dev_err(tas_fmw->dev, "%s: Description error\n", __func__); + return -EINVAL; + } + offset += n; + img_data->nr_blk = get_unaligned_be16(&data[offset]); + offset += 2; + + img_data->dev_blks = kcalloc(img_data->nr_blk, + sizeof(*img_data->dev_blks), GFP_KERNEL); + if (!img_data->dev_blks) + return -ENOMEM; + + for (i = 0; i < img_data->nr_blk; i++) { + blk = &img_data->dev_blks[i]; + offset = fw_parse_block_data(tas_fmw, blk, fmw, offset); + if (offset < 0) + return -EINVAL; + } + + return offset; +} + +/* + * When parsing error occurs, all the memory resource will be released + * in the end of tasdevice_rca_ready. + */ +static int fw_parse_program_data(struct tasdevice_priv *tas_priv, + struct tasdevice_fw *tas_fmw, const struct firmware *fmw, int offset) +{ + unsigned char *buf = (unsigned char *)fmw->data; + struct tasdevice_prog *program; + int i; + + if (offset + 2 > fmw->size) { + dev_err(tas_priv->dev, "%s: File Size error\n", __func__); + return -EINVAL; + } + tas_fmw->nr_programs = get_unaligned_be16(&buf[offset]); + offset += 2; + + if (tas_fmw->nr_programs == 0) { + /* Not error in calibration Data file, return directly */ + dev_dbg(tas_priv->dev, "%s: No Programs data, maybe calbin\n", + __func__); + return offset; + } + + tas_fmw->programs = + kcalloc(tas_fmw->nr_programs, sizeof(*tas_fmw->programs), + GFP_KERNEL); + if (!tas_fmw->programs) + return -ENOMEM; + + for (i = 0; i < tas_fmw->nr_programs; i++) { + int n = 0; + + program = &tas_fmw->programs[i]; + if (offset + 64 > fmw->size) { + dev_err(tas_priv->dev, "%s: mpName error\n", __func__); + return -EINVAL; + } + offset += 64; + + n = strlen((char *)&buf[offset]); + /* skip '\0' and 5 unused bytes */ + n += 6; + if (offset + n > fmw->size) { + dev_err(tas_priv->dev, "Description err\n"); + return -EINVAL; + } + + offset += n; + + offset = fw_parse_data(tas_fmw, &program->dev_data, fmw, + offset); + if (offset < 0) + return offset; + } + + return offset; +} + +/* + * When parsing error occurs, all the memory resource will be released + * in the end of tasdevice_rca_ready. + */ +static int fw_parse_configuration_data(struct tasdevice_priv *tas_priv, + struct tasdevice_fw *tas_fmw, const struct firmware *fmw, int offset) +{ + unsigned char *data = (unsigned char *)fmw->data; + struct tasdevice_config *config; + unsigned int i, n; + + if (offset + 2 > fmw->size) { + dev_err(tas_priv->dev, "%s: File Size error\n", __func__); + return -EINVAL; + } + tas_fmw->nr_configurations = get_unaligned_be16(&data[offset]); + offset += 2; + + if (tas_fmw->nr_configurations == 0) { + dev_err(tas_priv->dev, "%s: Conf is zero\n", __func__); + /* Not error for calibration Data file, return directly */ + return offset; + } + tas_fmw->configs = kcalloc(tas_fmw->nr_configurations, + sizeof(*tas_fmw->configs), GFP_KERNEL); + if (!tas_fmw->configs) + return -ENOMEM; + for (i = 0; i < tas_fmw->nr_configurations; i++) { + config = &tas_fmw->configs[i]; + if (offset + 64 > fmw->size) { + dev_err(tas_priv->dev, "File Size err\n"); + return -EINVAL; + } + memcpy(config->name, &data[offset], 64); + offset += 64; + + n = strlen((char *)&data[offset]); + n += 15; + if (offset + n > fmw->size) { + dev_err(tas_priv->dev, "Description err\n"); + return -EINVAL; + } + offset += n; + offset = fw_parse_data(tas_fmw, &config->dev_data, + fmw, offset); + if (offset < 0) + break; + } + + return offset; +} + +/* yram5 page check. */ +static bool check_inpage_yram_rg(struct tas_crc *cd, + unsigned char reg, unsigned char len) +{ + bool in = false; + + if (reg <= TAS2781_YRAM5_END_REG && + reg >= TAS2781_YRAM5_START_REG) { + if (reg + len > TAS2781_YRAM5_END_REG) + cd->len = TAS2781_YRAM5_END_REG - reg + 1; + else + cd->len = len; + cd->offset = reg; + in = true; + } else if (reg < TAS2781_YRAM5_START_REG) { + if (reg + len > TAS2781_YRAM5_START_REG) { + cd->offset = TAS2781_YRAM5_START_REG; + cd->len = len - TAS2781_YRAM5_START_REG + reg; + in = true; + } + } + + return in; +} + +/* DSP firmware yram block check. */ +static bool check_inpage_yram_bk1(struct tas_crc *cd, + unsigned char page, unsigned char reg, unsigned char len) +{ + bool in = false; + + if (page == TAS2781_YRAM1_PAGE) { + if (reg >= TAS2781_YRAM1_START_REG) { + cd->offset = reg; + cd->len = len; + in = true; + } else if (reg + len > TAS2781_YRAM1_START_REG) { + cd->offset = TAS2781_YRAM1_START_REG; + cd->len = len - TAS2781_YRAM1_START_REG + reg; + in = true; + } + } else if (page == TAS2781_YRAM3_PAGE) { + in = check_inpage_yram_rg(cd, reg, len); + } + + return in; +} + +/* + * Return Code: + * true -- the registers are in the inpage yram + * false -- the registers are NOT in the inpage yram + */ +static bool check_inpage_yram(struct tas_crc *cd, unsigned char book, + unsigned char page, unsigned char reg, unsigned char len) +{ + bool in = false; + + if (book == TAS2781_YRAM_BOOK1) + in = check_inpage_yram_bk1(cd, page, reg, len); + else if (book == TAS2781_YRAM_BOOK2 && page == TAS2781_YRAM5_PAGE) + in = check_inpage_yram_rg(cd, reg, len); + + return in; +} + +/* yram4 page check. */ +static bool check_inblock_yram_bk(struct tas_crc *cd, + unsigned char page, unsigned char reg, unsigned char len) +{ + bool in = false; + + if ((page >= TAS2781_YRAM4_START_PAGE && + page <= TAS2781_YRAM4_END_PAGE) || + (page >= TAS2781_YRAM2_START_PAGE && + page <= TAS2781_YRAM2_END_PAGE)) { + if (reg <= TAS2781_YRAM2_END_REG && + reg >= TAS2781_YRAM2_START_REG) { + cd->offset = reg; + cd->len = len; + in = true; + } else if (reg < TAS2781_YRAM2_START_REG) { + if (reg + len - 1 >= TAS2781_YRAM2_START_REG) { + cd->offset = TAS2781_YRAM2_START_REG; + cd->len = reg + len - TAS2781_YRAM2_START_REG; + in = true; + } + } + } + + return in; +} + +/* + * Return Code: + * true -- the registers are in the inblock yram + * false -- the registers are NOT in the inblock yram + */ +static bool check_inblock_yram(struct tas_crc *cd, unsigned char book, + unsigned char page, unsigned char reg, unsigned char len) +{ + bool in = false; + + if (book == TAS2781_YRAM_BOOK1 || book == TAS2781_YRAM_BOOK2) + in = check_inblock_yram_bk(cd, page, reg, len); + + return in; +} + +/* yram page check. */ +static bool check_yram(struct tas_crc *cd, unsigned char book, + unsigned char page, unsigned char reg, unsigned char len) +{ + bool in; + + in = check_inpage_yram(cd, book, page, reg, len); + if (!in) + in = check_inblock_yram(cd, book, page, reg, len); + + return in; +} + +/* Checksum for data block. */ +static int tasdev_multibytes_chksum(struct tasdevice_priv *tasdevice, + unsigned char book, unsigned char page, + unsigned char reg, unsigned int len) +{ + struct tas_crc crc_data; + unsigned char crc_chksum = 0; + unsigned char nBuf1[128]; + int ret = 0, i; + bool in; + + if ((reg + len - 1) > 127) { + ret = -EINVAL; + dev_err(tasdevice->dev, "firmware error\n"); + goto end; + } + + if ((book == TASDEVICE_BOOK_ID(TAS2781_SA_COEFF_SWAP_REG)) && + (page == TASDEVICE_PAGE_ID(TAS2781_SA_COEFF_SWAP_REG)) && + (reg == TASDEVICE_REG_ID(TAS2781_SA_COEFF_SWAP_REG)) && + (len == 4)) { + /* DSP swap command, pass */ + ret = 0; + goto end; + } + + in = check_yram(&crc_data, book, page, reg, len); + if (!in) + goto end; + + if (len == 1) { + dev_err(tasdevice->dev, "firmware error\n"); + ret = -EINVAL; + goto end; + } + + ret = tasdevice_spi_dev_bulk_read(tasdevice, + TASDEVICE_REG(book, page, crc_data.offset), + nBuf1, crc_data.len); + if (ret < 0) + goto end; + + for (i = 0; i < crc_data.len; i++) { + if ((book == TASDEVICE_BOOK_ID(TAS2781_SA_COEFF_SWAP_REG)) && + (page == TASDEVICE_PAGE_ID(TAS2781_SA_COEFF_SWAP_REG)) && + ((i + crc_data.offset) >= + TASDEVICE_REG_ID(TAS2781_SA_COEFF_SWAP_REG)) && + ((i + crc_data.offset) <= + (TASDEVICE_REG_ID(TAS2781_SA_COEFF_SWAP_REG) + 4))) + /* DSP swap command, bypass */ + continue; + else + crc_chksum += crc8(tasdevice->crc8_lkp_tbl, &nBuf1[i], + 1, 0); + } + + ret = crc_chksum; + +end: + return ret; +} + +/* Checksum for single register. */ +static int do_singlereg_checksum(struct tasdevice_priv *tasdevice, + unsigned char book, unsigned char page, + unsigned char reg, unsigned char val) +{ + struct tas_crc crc_data; + unsigned int nData1; + int ret = 0; + bool in; + + /* DSP swap command, pass */ + if ((book == TASDEVICE_BOOK_ID(TAS2781_SA_COEFF_SWAP_REG)) && + (page == TASDEVICE_PAGE_ID(TAS2781_SA_COEFF_SWAP_REG)) && + (reg >= TASDEVICE_REG_ID(TAS2781_SA_COEFF_SWAP_REG)) && + (reg <= (TASDEVICE_REG_ID(TAS2781_SA_COEFF_SWAP_REG) + 4))) + return 0; + + in = check_yram(&crc_data, book, page, reg, 1); + if (!in) + return 0; + ret = tasdevice_spi_dev_read(tasdevice, + TASDEVICE_REG(book, page, reg), &nData1); + if (ret < 0) + return ret; + + if (nData1 != val) { + dev_err(tasdevice->dev, + "B[0x%x]P[0x%x]R[0x%x] W[0x%x], R[0x%x]\n", + book, page, reg, val, nData1); + tasdevice->err_code |= ERROR_YRAM_CRCCHK; + return -EAGAIN; + } + + ret = crc8(tasdevice->crc8_lkp_tbl, &val, 1, 0); + + return ret; +} + +/* Block type check. */ +static void set_err_prg_cfg(unsigned int type, struct tasdevice_priv *p) +{ + if ((type == MAIN_ALL_DEVICES) || (type == MAIN_DEVICE_A) || + (type == MAIN_DEVICE_B) || (type == MAIN_DEVICE_C) || + (type == MAIN_DEVICE_D)) + p->cur_prog = -1; + else + p->cur_conf = -1; +} + +/* Checksum for data bytes. */ +static int tasdev_bytes_chksum(struct tasdevice_priv *tas_priv, + struct tasdev_blk *block, unsigned char book, + unsigned char page, unsigned char reg, unsigned int len, + unsigned char val, unsigned char *crc_chksum) +{ + int ret; + + if (len > 1) + ret = tasdev_multibytes_chksum(tas_priv, book, page, reg, + len); + else + ret = do_singlereg_checksum(tas_priv, book, page, reg, val); + + if (ret > 0) { + *crc_chksum += ret; + goto end; + } + + if (ret != -EAGAIN) + goto end; + + block->nr_retry--; + if (block->nr_retry > 0) + goto end; + + set_err_prg_cfg(block->type, tas_priv); + +end: + return ret; +} + +/* Multi-data byte write. */ +static int tasdev_multibytes_wr(struct tasdevice_priv *tas_priv, + struct tasdev_blk *block, unsigned char book, + unsigned char page, unsigned char reg, unsigned char *data, + unsigned int len, unsigned int *nr_cmds, + unsigned char *crc_chksum) +{ + int ret; + + if (len > 1) { + ret = tasdevice_spi_dev_bulk_write(tas_priv, + TASDEVICE_REG(book, page, reg), data + 3, len); + if (ret < 0) + return ret; + if (block->is_ychksum_present) + ret = tasdev_bytes_chksum(tas_priv, block, + book, page, reg, len, 0, crc_chksum); + } else { + ret = tasdevice_spi_dev_write(tas_priv, + TASDEVICE_REG(book, page, reg), data[3]); + if (ret < 0) + return ret; + if (block->is_ychksum_present) + ret = tasdev_bytes_chksum(tas_priv, block, book, + page, reg, 1, data[3], crc_chksum); + } + + if (!block->is_ychksum_present || ret >= 0) { + *nr_cmds += 1; + if (len >= 2) + *nr_cmds += ((len - 2) / 4) + 1; + } + + return ret; +} + +/* Checksum for block. */ +static int tasdev_block_chksum(struct tasdevice_priv *tas_priv, + struct tasdev_blk *block) +{ + unsigned int nr_value; + int ret; + + ret = tasdevice_spi_dev_read(tas_priv, TASDEVICE_CHECKSUM, &nr_value); + if (ret < 0) { + dev_err(tas_priv->dev, "%s: read error %d.\n", __func__, ret); + set_err_prg_cfg(block->type, tas_priv); + return ret; + } + + if ((nr_value & 0xff) != block->pchksum) { + dev_err(tas_priv->dev, "%s: PChkSum err %d ", __func__, ret); + dev_err(tas_priv->dev, "PChkSum = 0x%x, Reg = 0x%x\n", + block->pchksum, (nr_value & 0xff)); + tas_priv->err_code |= ERROR_PRAM_CRCCHK; + ret = -EAGAIN; + block->nr_retry--; + + if (block->nr_retry <= 0) + set_err_prg_cfg(block->type, tas_priv); + } else { + tas_priv->err_code &= ~ERROR_PRAM_CRCCHK; + } + + return ret; +} + +/* Firmware block load function. */ +static int tasdev_load_blk(struct tasdevice_priv *tas_priv, + struct tasdev_blk *block) +{ + unsigned int sleep_time, len, nr_cmds; + unsigned char offset, book, page, val; + unsigned char *data = block->data; + unsigned char crc_chksum = 0; + int ret = 0; + + while (block->nr_retry > 0) { + if (block->is_pchksum_present) { + ret = tasdevice_spi_dev_write(tas_priv, + TASDEVICE_CHECKSUM, 0); + if (ret < 0) + break; + } + + if (block->is_ychksum_present) + crc_chksum = 0; + + nr_cmds = 0; + + while (nr_cmds < block->nr_cmds) { + data = block->data + nr_cmds * 4; + + book = data[0]; + page = data[1]; + offset = data[2]; + val = data[3]; + + nr_cmds++; + /* Single byte write */ + if (offset <= 0x7F) { + ret = tasdevice_spi_dev_write(tas_priv, + TASDEVICE_REG(book, page, offset), + val); + if (ret < 0) + break; + if (block->is_ychksum_present) { + ret = tasdev_bytes_chksum(tas_priv, + block, book, page, offset, + 1, val, &crc_chksum); + if (ret < 0) + break; + } + continue; + } + /* sleep command */ + if (offset == 0x81) { + /* book -- data[0] page -- data[1] */ + sleep_time = ((book << 8) + page)*1000; + fsleep(sleep_time); + continue; + } + /* Multiple bytes write */ + if (offset == 0x85) { + data += 4; + len = (book << 8) + page; + book = data[0]; + page = data[1]; + offset = data[2]; + ret = tasdev_multibytes_wr(tas_priv, + block, book, page, offset, data, + len, &nr_cmds, &crc_chksum); + if (ret < 0) + break; + } + } + if (ret == -EAGAIN) { + if (block->nr_retry > 0) + continue; + } else if (ret < 0) { + /* err in current device, skip it */ + break; + } + + if (block->is_pchksum_present) { + ret = tasdev_block_chksum(tas_priv, block); + if (ret == -EAGAIN) { + if (block->nr_retry > 0) + continue; + } else if (ret < 0) { + /* err in current device, skip it */ + break; + } + } + + if (block->is_ychksum_present) { + /* TBD, open it when FW ready */ + dev_err(tas_priv->dev, + "Blk YChkSum: FW = 0x%x, YCRC = 0x%x\n", + block->ychksum, crc_chksum); + + tas_priv->err_code &= + ~ERROR_YRAM_CRCCHK; + ret = 0; + } + /* skip current blk */ + break; + } + + return ret; +} + +/* Firmware block load function. */ +static int tasdevice_load_block(struct tasdevice_priv *tas_priv, + struct tasdev_blk *block) +{ + int ret = 0; + + block->nr_retry = 6; + if (tas_priv->is_loading == false) + return 0; + ret = tasdev_load_blk(tas_priv, block); + if (ret < 0) + dev_err(tas_priv->dev, "Blk (%d) load error\n", block->type); + + return ret; +} + +/* + * Select firmware binary parser & load callback functions by ppc3 version + * and firmware binary version. + */ +static int dspfw_default_callback(struct tasdevice_priv *tas_priv, + unsigned int drv_ver, unsigned int ppcver) +{ + int rc = 0; + + if (drv_ver == 0x100) { + if (ppcver >= PPC3_VERSION) { + tas_priv->fw_parse_variable_header = + fw_parse_variable_header_kernel; + tas_priv->fw_parse_program_data = + fw_parse_program_data_kernel; + tas_priv->fw_parse_configuration_data = + fw_parse_configuration_data_kernel; + tas_priv->tasdevice_load_block = + tasdevice_load_block_kernel; + } else if (ppcver == 0x00) { + tas_priv->fw_parse_variable_header = + fw_parse_variable_header_git; + tas_priv->fw_parse_program_data = + fw_parse_program_data; + tas_priv->fw_parse_configuration_data = + fw_parse_configuration_data; + tas_priv->tasdevice_load_block = + tasdevice_load_block; + } else { + dev_err(tas_priv->dev, + "Wrong PPCVer :0x%08x\n", ppcver); + rc = -EINVAL; + } + } else { + dev_err(tas_priv->dev, "Wrong DrvVer : 0x%02x\n", drv_ver); + rc = -EINVAL; + } + + return rc; +} + +/* DSP firmware binary file header parser function. */ +static int fw_parse_header(struct tasdevice_priv *tas_priv, + struct tasdevice_fw *tas_fmw, const struct firmware *fmw, int offset) +{ + struct tasdevice_dspfw_hdr *fw_hdr = &tas_fmw->fw_hdr; + struct tasdevice_fw_fixed_hdr *fw_fixed_hdr = &fw_hdr->fixed_hdr; + static const unsigned char magic_number[] = {0x35, 0x35, 0x35, 0x32, }; + const unsigned char *buf = (unsigned char *)fmw->data; + + if (offset + 92 > fmw->size) { + dev_err(tas_priv->dev, "%s: File Size error\n", __func__); + offset = -EINVAL; + goto out; + } + if (memcmp(&buf[offset], magic_number, 4)) { + dev_err(tas_priv->dev, "%s: Magic num NOT match\n", __func__); + offset = -EINVAL; + goto out; + } + offset += 4; + + /* + * Convert data[offset], data[offset + 1], data[offset + 2] and + * data[offset + 3] into host + */ + fw_fixed_hdr->fwsize = get_unaligned_be32(&buf[offset]); + offset += 4; + if (fw_fixed_hdr->fwsize != fmw->size) { + dev_err(tas_priv->dev, "File size not match, %lu %u", + (unsigned long)fmw->size, fw_fixed_hdr->fwsize); + offset = -EINVAL; + goto out; + } + offset += 4; + fw_fixed_hdr->ppcver = get_unaligned_be32(&buf[offset]); + offset += 8; + fw_fixed_hdr->drv_ver = get_unaligned_be32(&buf[offset]); + offset += 72; + +out: + return offset; +} + +/* DSP firmware binary file parser function. */ +static int tasdevice_dspfw_ready(const struct firmware *fmw, void *context) +{ + struct tasdevice_priv *tas_priv = context; + struct tasdevice_fw_fixed_hdr *fw_fixed_hdr; + struct tasdevice_fw *tas_fmw; + int offset = 0, ret = 0; + + if (!fmw || !fmw->data) { + dev_err(tas_priv->dev, "%s: Failed to read firmware %s\n", + __func__, tas_priv->coef_binaryname); + return -EINVAL; + } + + tas_priv->fmw = kzalloc(sizeof(*tas_priv->fmw), GFP_KERNEL); + if (!tas_priv->fmw) + return -ENOMEM; + tas_fmw = tas_priv->fmw; + tas_fmw->dev = tas_priv->dev; + offset = fw_parse_header(tas_priv, tas_fmw, fmw, offset); + + if (offset == -EINVAL) + return -EINVAL; + + fw_fixed_hdr = &tas_fmw->fw_hdr.fixed_hdr; + /* Support different versions of firmware */ + switch (fw_fixed_hdr->drv_ver) { + case 0x301: + case 0x302: + case 0x502: + case 0x503: + tas_priv->fw_parse_variable_header = + fw_parse_variable_header_kernel; + tas_priv->fw_parse_program_data = + fw_parse_program_data_kernel; + tas_priv->fw_parse_configuration_data = + fw_parse_configuration_data_kernel; + tas_priv->tasdevice_load_block = + tasdevice_load_block_kernel; + break; + case 0x202: + case 0x400: + tas_priv->fw_parse_variable_header = + fw_parse_variable_header_git; + tas_priv->fw_parse_program_data = + fw_parse_program_data; + tas_priv->fw_parse_configuration_data = + fw_parse_configuration_data; + tas_priv->tasdevice_load_block = + tasdevice_load_block; + break; + default: + ret = dspfw_default_callback(tas_priv, + fw_fixed_hdr->drv_ver, fw_fixed_hdr->ppcver); + if (ret) + return ret; + break; + } + + offset = tas_priv->fw_parse_variable_header(tas_priv, fmw, offset); + if (offset < 0) + return offset; + + offset = tas_priv->fw_parse_program_data(tas_priv, tas_fmw, fmw, + offset); + if (offset < 0) + return offset; + + offset = tas_priv->fw_parse_configuration_data(tas_priv, + tas_fmw, fmw, offset); + if (offset < 0) + ret = offset; + + return ret; +} + +/* DSP firmware binary file parser function. */ +int tasdevice_spi_dsp_parser(void *context) +{ + struct tasdevice_priv *tas_priv = context; + const struct firmware *fw_entry; + int ret; + + ret = request_firmware(&fw_entry, tas_priv->coef_binaryname, + tas_priv->dev); + if (ret) { + dev_err(tas_priv->dev, "%s: load %s error\n", __func__, + tas_priv->coef_binaryname); + return ret; + } + + ret = tasdevice_dspfw_ready(fw_entry, tas_priv); + release_firmware(fw_entry); + fw_entry = NULL; + + return ret; +} + +/* DSP firmware program block data remove function. */ +static void tasdev_dsp_prog_blk_remove(struct tasdevice_prog *prog) +{ + struct tasdevice_data *tas_dt; + struct tasdev_blk *blk; + unsigned int i; + + if (!prog) + return; + + tas_dt = &prog->dev_data; + + if (!tas_dt->dev_blks) + return; + + for (i = 0; i < tas_dt->nr_blk; i++) { + blk = &tas_dt->dev_blks[i]; + kfree(blk->data); + } + kfree(tas_dt->dev_blks); +} + +/* DSP firmware program block data remove function. */ +static void tasdev_dsp_prog_remove(struct tasdevice_prog *prog, + unsigned short nr) +{ + int i; + + for (i = 0; i < nr; i++) + tasdev_dsp_prog_blk_remove(&prog[i]); + kfree(prog); +} + +/* DSP firmware config block data remove function. */ +static void tasdev_dsp_cfg_blk_remove(struct tasdevice_config *cfg) +{ + struct tasdevice_data *tas_dt; + struct tasdev_blk *blk; + unsigned int i; + + if (cfg) { + tas_dt = &cfg->dev_data; + + if (!tas_dt->dev_blks) + return; + + for (i = 0; i < tas_dt->nr_blk; i++) { + blk = &tas_dt->dev_blks[i]; + kfree(blk->data); + } + kfree(tas_dt->dev_blks); + } +} + +/* DSP firmware config remove function. */ +static void tasdev_dsp_cfg_remove(struct tasdevice_config *config, + unsigned short nr) +{ + int i; + + for (i = 0; i < nr; i++) + tasdev_dsp_cfg_blk_remove(&config[i]); + kfree(config); +} + +/* DSP firmware remove function. */ +void tasdevice_spi_dsp_remove(void *context) +{ + struct tasdevice_priv *tas_dev = context; + + if (!tas_dev->fmw) + return; + + if (tas_dev->fmw->programs) + tasdev_dsp_prog_remove(tas_dev->fmw->programs, + tas_dev->fmw->nr_programs); + if (tas_dev->fmw->configs) + tasdev_dsp_cfg_remove(tas_dev->fmw->configs, + tas_dev->fmw->nr_configurations); + kfree(tas_dev->fmw); + tas_dev->fmw = NULL; +} + +/* DSP firmware calibration data remove function. */ +static void tas2781_clear_calfirmware(struct tasdevice_fw *tas_fmw) +{ + struct tasdevice_calibration *calibration; + struct tasdev_blk *block; + unsigned int blks; + int i; + + if (!tas_fmw->calibrations) + goto out; + + for (i = 0; i < tas_fmw->nr_calibrations; i++) { + calibration = &tas_fmw->calibrations[i]; + if (!calibration) + continue; + + if (!calibration->dev_data.dev_blks) + continue; + + for (blks = 0; blks < calibration->dev_data.nr_blk; blks++) { + block = &calibration->dev_data.dev_blks[blks]; + if (!block) + continue; + kfree(block->data); + } + kfree(calibration->dev_data.dev_blks); + } + kfree(tas_fmw->calibrations); +out: + kfree(tas_fmw); +} + +/* Calibration data from firmware remove function. */ +void tasdevice_spi_calbin_remove(void *context) +{ + struct tasdevice_priv *tas_priv = context; + + if (tas_priv->cali_data_fmw) { + tas2781_clear_calfirmware(tas_priv->cali_data_fmw); + tas_priv->cali_data_fmw = NULL; + } +} + +/* Configuration remove function. */ +void tasdevice_spi_config_info_remove(void *context) +{ + struct tasdevice_priv *tas_priv = context; + struct tasdevice_rca *rca = &tas_priv->rcabin; + struct tasdevice_config_info **ci = rca->cfg_info; + unsigned int i, j; + + if (!ci) + return; + for (i = 0; i < rca->ncfgs; i++) { + if (!ci[i]) + continue; + if (ci[i]->blk_data) { + for (j = 0; j < ci[i]->real_nblocks; j++) { + if (!ci[i]->blk_data[j]) + continue; + kfree(ci[i]->blk_data[j]->regdata); + kfree(ci[i]->blk_data[j]); + } + kfree(ci[i]->blk_data); + } + kfree(ci[i]); + } + kfree(ci); +} + +/* DSP firmware program block data load function. */ +static int tasdevice_load_data(struct tasdevice_priv *tas_priv, + struct tasdevice_data *dev_data) +{ + struct tasdev_blk *block; + unsigned int i; + int ret = 0; + + for (i = 0; i < dev_data->nr_blk; i++) { + block = &dev_data->dev_blks[i]; + ret = tas_priv->tasdevice_load_block(tas_priv, block); + if (ret < 0) + break; + } + + return ret; +} + +/* DSP firmware program load interface function. */ +int tasdevice_spi_prmg_load(void *context, int prm_no) +{ + struct tasdevice_priv *tas_priv = context; + struct tasdevice_fw *tas_fmw = tas_priv->fmw; + struct tasdevice_prog *program; + struct tasdevice_config *conf; + int ret = 0; + + if (!tas_fmw) { + dev_err(tas_priv->dev, "%s: Firmware is NULL\n", __func__); + return -EINVAL; + } + if (prm_no >= 0 && prm_no <= tas_fmw->nr_programs) { + tas_priv->cur_conf = 0; + tas_priv->is_loading = true; + program = &tas_fmw->programs[prm_no]; + ret = tasdevice_load_data(tas_priv, &program->dev_data); + if (ret < 0) { + dev_err(tas_priv->dev, "Program failed %d.\n", ret); + return ret; + } + tas_priv->cur_prog = prm_no; + + conf = &tas_fmw->configs[tas_priv->cur_conf]; + ret = tasdevice_load_data(tas_priv, &conf->dev_data); + if (ret < 0) + dev_err(tas_priv->dev, "Config failed %d.\n", ret); + } else { + dev_err(tas_priv->dev, + "%s: prm(%d) is not in range of Programs %u\n", + __func__, prm_no, tas_fmw->nr_programs); + return -EINVAL; + } + + return ret; +} + +/* RCABIN configuration switch interface function. */ +void tasdevice_spi_tuning_switch(void *context, int state) +{ + struct tasdevice_priv *tas_priv = context; + int profile_cfg_id = tas_priv->rcabin.profile_cfg_id; + + if (tas_priv->fw_state == TASDEVICE_DSP_FW_FAIL) { + dev_err(tas_priv->dev, "DSP bin file not loaded\n"); + return; + } + + if (state == 0) + tasdevice_spi_select_cfg_blk(tas_priv, profile_cfg_id, + TASDEVICE_BIN_BLK_PRE_POWER_UP); + else + tasdevice_spi_select_cfg_blk(tas_priv, profile_cfg_id, + TASDEVICE_BIN_BLK_PRE_SHUTDOWN); +} diff --git a/sound/soc/codecs/es8326.c b/sound/soc/codecs/es8326.c index b06eead7e0f6f0..066d92b543128d 100644 --- a/sound/soc/codecs/es8326.c +++ b/sound/soc/codecs/es8326.c @@ -911,7 +911,7 @@ static void es8326_jack_detect_handler(struct work_struct *work) regmap_write(es8326->regmap, ES8326_INT_SOURCE, (ES8326_INT_SRC_PIN9 | ES8326_INT_SRC_BUTTON)); regmap_write(es8326->regmap, ES8326_SYS_BIAS, 0x1f); - regmap_update_bits(es8326->regmap, ES8326_HP_DRIVER_REF, 0x0f, 0x08); + regmap_update_bits(es8326->regmap, ES8326_HP_DRIVER_REF, 0x0f, 0x0d); queue_delayed_work(system_wq, &es8326->jack_detect_work, msecs_to_jiffies(400)); es8326->hp = 1; @@ -1023,7 +1023,7 @@ static void es8326_init(struct snd_soc_component *component) struct es8326_priv *es8326 = snd_soc_component_get_drvdata(component); regmap_write(es8326->regmap, ES8326_RESET, 0x1f); - regmap_write(es8326->regmap, ES8326_VMIDSEL, 0x0E); + regmap_write(es8326->regmap, ES8326_VMIDSEL, 0x3E); regmap_write(es8326->regmap, ES8326_ANA_LP, 0xf0); usleep_range(10000, 15000); regmap_write(es8326->regmap, ES8326_HPJACK_TIMER, 0xd9); diff --git a/sound/soc/fsl/fsl_asrc_m2m.c b/sound/soc/fsl/fsl_asrc_m2m.c index 4906843e2a8fd1..f46881f71e4307 100644 --- a/sound/soc/fsl/fsl_asrc_m2m.c +++ b/sound/soc/fsl/fsl_asrc_m2m.c @@ -183,7 +183,7 @@ static int asrc_dmaconfig(struct fsl_asrc_pair *pair, } /* main function of converter */ -static void asrc_m2m_device_run(struct fsl_asrc_pair *pair, struct snd_compr_task_runtime *task) +static int asrc_m2m_device_run(struct fsl_asrc_pair *pair, struct snd_compr_task_runtime *task) { struct fsl_asrc *asrc = pair->asrc; struct device *dev = &asrc->pdev->dev; @@ -193,7 +193,7 @@ static void asrc_m2m_device_run(struct fsl_asrc_pair *pair, struct snd_compr_tas unsigned int out_dma_len; unsigned int width; u32 fifo_addr; - int ret; + int ret = 0; /* set ratio mod */ if (asrc->m2m_set_ratio_mod) { @@ -215,6 +215,7 @@ static void asrc_m2m_device_run(struct fsl_asrc_pair *pair, struct snd_compr_tas in_buf_len > ASRC_M2M_BUFFER_SIZE || in_buf_len % (width * pair->channels / 8)) { dev_err(dev, "out buffer size is error: [%d]\n", in_buf_len); + ret = -EINVAL; goto end; } @@ -245,6 +246,7 @@ static void asrc_m2m_device_run(struct fsl_asrc_pair *pair, struct snd_compr_tas } } else if (out_dma_len > ASRC_M2M_BUFFER_SIZE) { dev_err(dev, "cap buffer size error\n"); + ret = -EINVAL; goto end; } @@ -263,12 +265,14 @@ static void asrc_m2m_device_run(struct fsl_asrc_pair *pair, struct snd_compr_tas if (!wait_for_completion_interruptible_timeout(&pair->complete[IN], 10 * HZ)) { dev_err(dev, "out DMA task timeout\n"); + ret = -ETIMEDOUT; goto end; } if (out_dma_len > 0) { if (!wait_for_completion_interruptible_timeout(&pair->complete[OUT], 10 * HZ)) { dev_err(dev, "cap DMA task timeout\n"); + ret = -ETIMEDOUT; goto end; } } @@ -278,7 +282,7 @@ static void asrc_m2m_device_run(struct fsl_asrc_pair *pair, struct snd_compr_tas /* update payload length for capture */ task->output_size = out_dma_len; end: - return; + return ret; } static int fsl_asrc_m2m_comp_open(struct snd_compr_stream *stream) @@ -525,9 +529,7 @@ static int fsl_asrc_m2m_comp_task_start(struct snd_compr_stream *stream, struct snd_compr_runtime *runtime = stream->runtime; struct fsl_asrc_pair *pair = runtime->private_data; - asrc_m2m_device_run(pair, task); - - return 0; + return asrc_m2m_device_run(pair, task); } static int fsl_asrc_m2m_comp_task_stop(struct snd_compr_stream *stream, @@ -633,7 +635,7 @@ int fsl_asrc_m2m_suspend(struct fsl_asrc *asrc) for (i = 0; i < PAIR_CTX_NUM; i++) { pair = asrc->pair[i]; - if (!pair) + if (!pair || !pair->dma_buffer[IN].area || !pair->dma_buffer[OUT].area) continue; if (!completion_done(&pair->complete[IN])) { if (pair->dma_chan[IN]) diff --git a/sound/soc/fsl/fsl_micfil.c b/sound/soc/fsl/fsl_micfil.c index e908cfb594ab5f..1075598a6647b1 100644 --- a/sound/soc/fsl/fsl_micfil.c +++ b/sound/soc/fsl/fsl_micfil.c @@ -89,6 +89,7 @@ struct fsl_micfil_soc_data { bool use_verid; bool volume_sx; u64 formats; + int fifo_offset; }; static struct fsl_micfil_soc_data fsl_micfil_imx8mm = { @@ -98,6 +99,7 @@ static struct fsl_micfil_soc_data fsl_micfil_imx8mm = { .dataline = 0xf, .formats = SNDRV_PCM_FMTBIT_S16_LE, .volume_sx = true, + .fifo_offset = 0, }; static struct fsl_micfil_soc_data fsl_micfil_imx8mp = { @@ -107,6 +109,7 @@ static struct fsl_micfil_soc_data fsl_micfil_imx8mp = { .dataline = 0xf, .formats = SNDRV_PCM_FMTBIT_S32_LE, .volume_sx = false, + .fifo_offset = 0, }; static struct fsl_micfil_soc_data fsl_micfil_imx93 = { @@ -118,12 +121,26 @@ static struct fsl_micfil_soc_data fsl_micfil_imx93 = { .use_edma = true, .use_verid = true, .volume_sx = false, + .fifo_offset = 0, +}; + +static struct fsl_micfil_soc_data fsl_micfil_imx943 = { + .imx = true, + .fifos = 8, + .fifo_depth = 32, + .dataline = 0xf, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .use_edma = true, + .use_verid = true, + .volume_sx = false, + .fifo_offset = -4, }; static const struct of_device_id fsl_micfil_dt_ids[] = { { .compatible = "fsl,imx8mm-micfil", .data = &fsl_micfil_imx8mm }, { .compatible = "fsl,imx8mp-micfil", .data = &fsl_micfil_imx8mp }, { .compatible = "fsl,imx93-micfil", .data = &fsl_micfil_imx93 }, + { .compatible = "fsl,imx943-micfil", .data = &fsl_micfil_imx943 }, {} }; MODULE_DEVICE_TABLE(of, fsl_micfil_dt_ids); @@ -793,7 +810,7 @@ static int fsl_micfil_hw_params(struct snd_pcm_substream *substream, ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL2, MICFIL_CTRL2_CLKDIV | MICFIL_CTRL2_CICOSR, FIELD_PREP(MICFIL_CTRL2_CLKDIV, clk_div) | - FIELD_PREP(MICFIL_CTRL2_CICOSR, 16 - osr)); + FIELD_PREP(MICFIL_CTRL2_CICOSR, 32 - osr)); /* Configure CIC OSR in VADCICOSR */ regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1, @@ -932,9 +949,39 @@ static const struct reg_default fsl_micfil_reg_defaults[] = { {REG_MICFIL_VAD0_ZCD, 0x00000004}, }; +static const struct reg_default fsl_micfil_reg_defaults_v2[] = { + {REG_MICFIL_CTRL1, 0x00000000}, + {REG_MICFIL_CTRL2, 0x00000000}, + {REG_MICFIL_STAT, 0x00000000}, + {REG_MICFIL_FIFO_CTRL, 0x0000001F}, + {REG_MICFIL_FIFO_STAT, 0x00000000}, + {REG_MICFIL_DATACH0 - 0x4, 0x00000000}, + {REG_MICFIL_DATACH1 - 0x4, 0x00000000}, + {REG_MICFIL_DATACH2 - 0x4, 0x00000000}, + {REG_MICFIL_DATACH3 - 0x4, 0x00000000}, + {REG_MICFIL_DATACH4 - 0x4, 0x00000000}, + {REG_MICFIL_DATACH5 - 0x4, 0x00000000}, + {REG_MICFIL_DATACH6 - 0x4, 0x00000000}, + {REG_MICFIL_DATACH7 - 0x4, 0x00000000}, + {REG_MICFIL_DC_CTRL, 0x00000000}, + {REG_MICFIL_OUT_CTRL, 0x00000000}, + {REG_MICFIL_OUT_STAT, 0x00000000}, + {REG_MICFIL_VAD0_CTRL1, 0x00000000}, + {REG_MICFIL_VAD0_CTRL2, 0x000A0000}, + {REG_MICFIL_VAD0_STAT, 0x00000000}, + {REG_MICFIL_VAD0_SCONFIG, 0x00000000}, + {REG_MICFIL_VAD0_NCONFIG, 0x80000000}, + {REG_MICFIL_VAD0_NDATA, 0x00000000}, + {REG_MICFIL_VAD0_ZCD, 0x00000004}, +}; + static bool fsl_micfil_readable_reg(struct device *dev, unsigned int reg) { struct fsl_micfil *micfil = dev_get_drvdata(dev); + int ofs = micfil->soc->fifo_offset; + + if (reg >= (REG_MICFIL_DATACH0 + ofs) && reg <= (REG_MICFIL_DATACH7 + ofs)) + return true; switch (reg) { case REG_MICFIL_CTRL1: @@ -942,14 +989,6 @@ static bool fsl_micfil_readable_reg(struct device *dev, unsigned int reg) case REG_MICFIL_STAT: case REG_MICFIL_FIFO_CTRL: case REG_MICFIL_FIFO_STAT: - case REG_MICFIL_DATACH0: - case REG_MICFIL_DATACH1: - case REG_MICFIL_DATACH2: - case REG_MICFIL_DATACH3: - case REG_MICFIL_DATACH4: - case REG_MICFIL_DATACH5: - case REG_MICFIL_DATACH6: - case REG_MICFIL_DATACH7: case REG_MICFIL_DC_CTRL: case REG_MICFIL_OUT_CTRL: case REG_MICFIL_OUT_STAT: @@ -1003,17 +1042,15 @@ static bool fsl_micfil_writeable_reg(struct device *dev, unsigned int reg) static bool fsl_micfil_volatile_reg(struct device *dev, unsigned int reg) { + struct fsl_micfil *micfil = dev_get_drvdata(dev); + int ofs = micfil->soc->fifo_offset; + + if (reg >= (REG_MICFIL_DATACH0 + ofs) && reg <= (REG_MICFIL_DATACH7 + ofs)) + return true; + switch (reg) { case REG_MICFIL_STAT: case REG_MICFIL_FIFO_STAT: - case REG_MICFIL_DATACH0: - case REG_MICFIL_DATACH1: - case REG_MICFIL_DATACH2: - case REG_MICFIL_DATACH3: - case REG_MICFIL_DATACH4: - case REG_MICFIL_DATACH5: - case REG_MICFIL_DATACH6: - case REG_MICFIL_DATACH7: case REG_MICFIL_OUT_STAT: case REG_MICFIL_VERID: case REG_MICFIL_PARAM: @@ -1039,6 +1076,20 @@ static const struct regmap_config fsl_micfil_regmap_config = { .cache_type = REGCACHE_MAPLE, }; +static const struct regmap_config fsl_micfil_regmap_config_v2 = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + + .max_register = REG_MICFIL_VAD0_ZCD, + .reg_defaults = fsl_micfil_reg_defaults_v2, + .num_reg_defaults = ARRAY_SIZE(fsl_micfil_reg_defaults_v2), + .readable_reg = fsl_micfil_readable_reg, + .volatile_reg = fsl_micfil_volatile_reg, + .writeable_reg = fsl_micfil_writeable_reg, + .cache_type = REGCACHE_MAPLE, +}; + /* END OF REGMAP */ static irqreturn_t micfil_isr(int irq, void *devid) @@ -1243,9 +1294,14 @@ static int fsl_micfil_probe(struct platform_device *pdev) if (IS_ERR(regs)) return PTR_ERR(regs); - micfil->regmap = devm_regmap_init_mmio(&pdev->dev, - regs, - &fsl_micfil_regmap_config); + if (of_device_is_compatible(np, "fsl,imx943-micfil")) + micfil->regmap = devm_regmap_init_mmio(&pdev->dev, + regs, + &fsl_micfil_regmap_config_v2); + else + micfil->regmap = devm_regmap_init_mmio(&pdev->dev, + regs, + &fsl_micfil_regmap_config); if (IS_ERR(micfil->regmap)) { dev_err(&pdev->dev, "failed to init MICFIL regmap: %ld\n", PTR_ERR(micfil->regmap)); @@ -1314,7 +1370,7 @@ static int fsl_micfil_probe(struct platform_device *pdev) } micfil->dma_params_rx.chan_name = "rx"; - micfil->dma_params_rx.addr = res->start + REG_MICFIL_DATACH0; + micfil->dma_params_rx.addr = res->start + REG_MICFIL_DATACH0 + micfil->soc->fifo_offset; micfil->dma_params_rx.maxburst = MICFIL_DMA_MAXBURST_RX; platform_set_drvdata(pdev, micfil); diff --git a/sound/soc/fsl/fsl_micfil.h b/sound/soc/fsl/fsl_micfil.h index b7798a7cbf2af8..aa3661ea4ffc11 100644 --- a/sound/soc/fsl/fsl_micfil.h +++ b/sound/soc/fsl/fsl_micfil.h @@ -62,7 +62,7 @@ #define MICFIL_QSEL_VLOW1_QUALITY 5 #define MICFIL_QSEL_VLOW2_QUALITY 4 -#define MICFIL_CTRL2_CICOSR GENMASK(19, 16) +#define MICFIL_CTRL2_CICOSR GENMASK(20, 16) #define MICFIL_CTRL2_CLKDIV GENMASK(7, 0) /* MICFIL Status Register -- REG_MICFIL_STAT 0x08 */ diff --git a/sound/soc/generic/simple-card-utils.c b/sound/soc/generic/simple-card-utils.c index e25b387a977637..dd414634b4ac75 100644 --- a/sound/soc/generic/simple-card-utils.c +++ b/sound/soc/generic/simple-card-utils.c @@ -295,7 +295,7 @@ int simple_util_startup(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct simple_util_priv *priv = snd_soc_card_get_drvdata(rtd->card); - struct simple_dai_props *props = simple_priv_to_props(priv, rtd->id); + struct simple_dai_props *props = runtime_simple_priv_to_props(priv, rtd); struct simple_util_dai *dai; unsigned int fixed_sysclk = 0; int i1, i2, i; @@ -356,7 +356,7 @@ void simple_util_shutdown(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct simple_util_priv *priv = snd_soc_card_get_drvdata(rtd->card); - struct simple_dai_props *props = simple_priv_to_props(priv, rtd->id); + struct simple_dai_props *props = runtime_simple_priv_to_props(priv, rtd); struct simple_util_dai *dai; int i; @@ -445,7 +445,7 @@ int simple_util_hw_params(struct snd_pcm_substream *substream, struct simple_util_dai *pdai; struct snd_soc_dai *sdai; struct simple_util_priv *priv = snd_soc_card_get_drvdata(rtd->card); - struct simple_dai_props *props = simple_priv_to_props(priv, rtd->id); + struct simple_dai_props *props = runtime_simple_priv_to_props(priv, rtd); unsigned int mclk, mclk_fs = 0; int i, ret; @@ -516,7 +516,7 @@ int simple_util_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_params *params) { struct simple_util_priv *priv = snd_soc_card_get_drvdata(rtd->card); - struct simple_dai_props *dai_props = simple_priv_to_props(priv, rtd->id); + struct simple_dai_props *dai_props = runtime_simple_priv_to_props(priv, rtd); struct simple_util_data *data = &dai_props->adata; struct snd_interval *rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); struct snd_interval *channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); @@ -627,7 +627,7 @@ static int simple_init_for_codec2codec(struct snd_soc_pcm_runtime *rtd, int simple_util_dai_init(struct snd_soc_pcm_runtime *rtd) { struct simple_util_priv *priv = snd_soc_card_get_drvdata(rtd->card); - struct simple_dai_props *props = simple_priv_to_props(priv, rtd->id); + struct simple_dai_props *props = runtime_simple_priv_to_props(priv, rtd); struct simple_util_dai *dai; int i, ret; diff --git a/sound/soc/rockchip/rockchip_i2s_tdm.c b/sound/soc/rockchip/rockchip_i2s_tdm.c index bd0dc586e24a3c..7f5fcaecee4b62 100644 --- a/sound/soc/rockchip/rockchip_i2s_tdm.c +++ b/sound/soc/rockchip/rockchip_i2s_tdm.c @@ -22,7 +22,6 @@ #define DRV_NAME "rockchip-i2s-tdm" -#define DEFAULT_MCLK_FS 256 #define CH_GRP_MAX 4 /* The max channel 8 / 2 */ #define MULTIPLEX_CH_MAX 10 @@ -70,6 +69,8 @@ struct rk_i2s_tdm_dev { bool has_playback; bool has_capture; struct snd_soc_dai_driver *dai; + unsigned int mclk_rx_freq; + unsigned int mclk_tx_freq; }; static int to_ch_num(unsigned int val) @@ -617,6 +618,27 @@ static int rockchip_i2s_trcm_mode(struct snd_pcm_substream *substream, return 0; } +static int rockchip_i2s_tdm_set_sysclk(struct snd_soc_dai *cpu_dai, int stream, + unsigned int freq, int dir) +{ + struct rk_i2s_tdm_dev *i2s_tdm = to_info(cpu_dai); + + if (i2s_tdm->clk_trcm) { + i2s_tdm->mclk_tx_freq = freq; + i2s_tdm->mclk_rx_freq = freq; + } else { + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + i2s_tdm->mclk_tx_freq = freq; + else + i2s_tdm->mclk_rx_freq = freq; + } + + dev_dbg(i2s_tdm->dev, "The target mclk_%s freq is: %d\n", + stream ? "rx" : "tx", freq); + + return 0; +} + static int rockchip_i2s_tdm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) @@ -631,15 +653,19 @@ static int rockchip_i2s_tdm_hw_params(struct snd_pcm_substream *substream, if (i2s_tdm->clk_trcm == TRCM_TX) { mclk = i2s_tdm->mclk_tx; + mclk_rate = i2s_tdm->mclk_tx_freq; } else if (i2s_tdm->clk_trcm == TRCM_RX) { mclk = i2s_tdm->mclk_rx; + mclk_rate = i2s_tdm->mclk_rx_freq; } else if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { mclk = i2s_tdm->mclk_tx; + mclk_rate = i2s_tdm->mclk_tx_freq; } else { mclk = i2s_tdm->mclk_rx; + mclk_rate = i2s_tdm->mclk_rx_freq; } - err = clk_set_rate(mclk, DEFAULT_MCLK_FS * params_rate(params)); + err = clk_set_rate(mclk, mclk_rate); if (err) return err; @@ -799,6 +825,7 @@ static const struct snd_soc_dai_ops rockchip_i2s_tdm_dai_ops = { .hw_params = rockchip_i2s_tdm_hw_params, .set_bclk_ratio = rockchip_i2s_tdm_set_bclk_ratio, .set_fmt = rockchip_i2s_tdm_set_fmt, + .set_sysclk = rockchip_i2s_tdm_set_sysclk, .set_tdm_slot = rockchip_dai_tdm_slot, .trigger = rockchip_i2s_tdm_trigger, }; diff --git a/sound/soc/soc-dai.c b/sound/soc/soc-dai.c index 34ba1a93a4c95a..ca0308f6d41c17 100644 --- a/sound/soc/soc-dai.c +++ b/sound/soc/soc-dai.c @@ -360,6 +360,22 @@ int snd_soc_dai_set_tristate(struct snd_soc_dai *dai, int tristate) } EXPORT_SYMBOL_GPL(snd_soc_dai_set_tristate); +int snd_soc_dai_prepare(struct snd_soc_dai *dai, + struct snd_pcm_substream *substream) +{ + int ret = 0; + + if (!snd_soc_dai_stream_valid(dai, substream->stream)) + return 0; + + if (dai->driver->ops && + dai->driver->ops->prepare) + ret = dai->driver->ops->prepare(substream, dai); + + return soc_dai_ret(dai, ret); +} +EXPORT_SYMBOL_GPL(snd_soc_dai_prepare); + /** * snd_soc_dai_digital_mute - configure DAI system or master clock. * @dai: DAI @@ -577,14 +593,9 @@ int snd_soc_pcm_dai_prepare(struct snd_pcm_substream *substream) int i, ret; for_each_rtd_dais(rtd, i, dai) { - if (!snd_soc_dai_stream_valid(dai, substream->stream)) - continue; - if (dai->driver->ops && - dai->driver->ops->prepare) { - ret = dai->driver->ops->prepare(substream, dai); - if (ret < 0) - return soc_dai_ret(dai, ret); - } + ret = snd_soc_dai_prepare(dai, substream); + if (ret < 0) + return ret; } return 0; diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 99521c784a9b16..b5116b700d7310 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -730,7 +730,7 @@ static int snd_soc_dapm_set_bias_level(struct snd_soc_dapm_context *dapm, if (ret != 0) goto out; - if (!card || dapm != &card->dapm) + if (dapm != &card->dapm) ret = snd_soc_dapm_force_bias_level(dapm, level); if (ret != 0) @@ -4013,6 +4013,18 @@ static int snd_soc_dai_link_event(struct snd_soc_dapm_widget *w, break; case SND_SOC_DAPM_POST_PMU: + snd_soc_dapm_widget_for_each_source_path(w, path) { + source = path->source->priv; + + snd_soc_dai_prepare(source, substream); + } + + snd_soc_dapm_widget_for_each_sink_path(w, path) { + sink = path->sink->priv; + + snd_soc_dai_prepare(sink, substream); + } + snd_soc_dapm_widget_for_each_sink_path(w, path) { sink = path->sink->priv; diff --git a/sound/soc/sunxi/sun4i-codec.c b/sound/soc/sunxi/sun4i-codec.c index 3d6156fefe7554..886b3fa537d262 100644 --- a/sound/soc/sunxi/sun4i-codec.c +++ b/sound/soc/sunxi/sun4i-codec.c @@ -2273,7 +2273,7 @@ static int sun4i_codec_probe(struct platform_device *pdev) } /* Get the clocks from the DT */ - scodec->clk_apb = devm_clk_get(&pdev->dev, "apb"); + scodec->clk_apb = devm_clk_get_enabled(&pdev->dev, "apb"); if (IS_ERR(scodec->clk_apb)) { dev_err(&pdev->dev, "Failed to get the APB clock\n"); return PTR_ERR(scodec->clk_apb); @@ -2286,8 +2286,7 @@ static int sun4i_codec_probe(struct platform_device *pdev) } if (quirks->has_reset) { - scodec->rst = devm_reset_control_get_exclusive(&pdev->dev, - NULL); + scodec->rst = devm_reset_control_get_exclusive_deasserted(&pdev->dev, NULL); if (IS_ERR(scodec->rst)) { dev_err(&pdev->dev, "Failed to get reset control\n"); return PTR_ERR(scodec->rst); @@ -2323,22 +2322,6 @@ static int sun4i_codec_probe(struct platform_device *pdev) return ret; } - /* Enable the bus clock */ - if (clk_prepare_enable(scodec->clk_apb)) { - dev_err(&pdev->dev, "Failed to enable the APB clock\n"); - return -EINVAL; - } - - /* Deassert the reset control */ - if (scodec->rst) { - ret = reset_control_deassert(scodec->rst); - if (ret) { - dev_err(&pdev->dev, - "Failed to deassert the reset control\n"); - goto err_clk_disable; - } - } - /* DMA configuration for TX FIFO */ scodec->playback_dma_data.addr = res->start + quirks->reg_dac_txdata; scodec->playback_dma_data.maxburst = quirks->dma_max_burst; @@ -2356,7 +2339,7 @@ static int sun4i_codec_probe(struct platform_device *pdev) &sun4i_codec_dai, 1); if (ret) { dev_err(&pdev->dev, "Failed to register our codec\n"); - goto err_assert_reset; + return ret; } ret = devm_snd_soc_register_component(&pdev->dev, @@ -2364,20 +2347,20 @@ static int sun4i_codec_probe(struct platform_device *pdev) &dummy_cpu_dai, 1); if (ret) { dev_err(&pdev->dev, "Failed to register our DAI\n"); - goto err_assert_reset; + return ret; } ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); if (ret) { dev_err(&pdev->dev, "Failed to register against DMAEngine\n"); - goto err_assert_reset; + return ret; } card = quirks->create_card(&pdev->dev); if (IS_ERR(card)) { ret = PTR_ERR(card); dev_err(&pdev->dev, "Failed to create our card\n"); - goto err_assert_reset; + return ret; } snd_soc_card_set_drvdata(card, scodec); @@ -2385,28 +2368,17 @@ static int sun4i_codec_probe(struct platform_device *pdev) ret = snd_soc_register_card(card); if (ret) { dev_err_probe(&pdev->dev, ret, "Failed to register our card\n"); - goto err_assert_reset; + return ret; } return 0; - -err_assert_reset: - if (scodec->rst) - reset_control_assert(scodec->rst); -err_clk_disable: - clk_disable_unprepare(scodec->clk_apb); - return ret; } static void sun4i_codec_remove(struct platform_device *pdev) { struct snd_soc_card *card = platform_get_drvdata(pdev); - struct sun4i_codec *scodec = snd_soc_card_get_drvdata(card); snd_soc_unregister_card(card); - if (scodec->rst) - reset_control_assert(scodec->rst); - clk_disable_unprepare(scodec->clk_apb); } static struct platform_driver sun4i_codec_driver = { diff --git a/sound/soc/xilinx/xlnx_spdif.c b/sound/soc/xilinx/xlnx_spdif.c index 7febb3830dc252..017a64ab9f1eb7 100644 --- a/sound/soc/xilinx/xlnx_spdif.c +++ b/sound/soc/xilinx/xlnx_spdif.c @@ -248,41 +248,35 @@ static int xlnx_spdif_probe(struct platform_device *pdev) if (!ctx) return -ENOMEM; - ctx->axi_clk = devm_clk_get(dev, "s_axi_aclk"); + ctx->axi_clk = devm_clk_get_enabled(dev, "s_axi_aclk"); if (IS_ERR(ctx->axi_clk)) { ret = PTR_ERR(ctx->axi_clk); dev_err(dev, "failed to get s_axi_aclk(%d)\n", ret); return ret; } - ret = clk_prepare_enable(ctx->axi_clk); - if (ret) { - dev_err(dev, "failed to enable s_axi_aclk(%d)\n", ret); - return ret; - } ctx->base = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(ctx->base)) { - ret = PTR_ERR(ctx->base); - goto clk_err; - } + if (IS_ERR(ctx->base)) + return PTR_ERR(ctx->base); + ret = of_property_read_u32(node, "xlnx,spdif-mode", &ctx->mode); if (ret < 0) { dev_err(dev, "cannot get SPDIF mode\n"); - goto clk_err; + return ret; } if (ctx->mode) { dai_drv = &xlnx_spdif_tx_dai; } else { ret = platform_get_irq(pdev, 0); if (ret < 0) - goto clk_err; + return ret; + ret = devm_request_irq(dev, ret, xlnx_spdifrx_irq_handler, 0, "XLNX_SPDIF_RX", ctx); if (ret) { dev_err(dev, "spdif rx irq request failed\n"); - ret = -ENODEV; - goto clk_err; + return -ENODEV; } init_waitqueue_head(&ctx->chsts_q); @@ -292,7 +286,7 @@ static int xlnx_spdif_probe(struct platform_device *pdev) ret = of_property_read_u32(node, "xlnx,aud_clk_i", &ctx->aclk); if (ret < 0) { dev_err(dev, "cannot get aud_clk_i value\n"); - goto clk_err; + return ret; } dev_set_drvdata(dev, ctx); @@ -301,22 +295,13 @@ static int xlnx_spdif_probe(struct platform_device *pdev) dai_drv, 1); if (ret) { dev_err(dev, "SPDIF component registration failed\n"); - goto clk_err; + return ret; } writel(XSPDIF_SOFT_RESET_VALUE, ctx->base + XSPDIF_SOFT_RESET_REG); dev_info(dev, "%s DAI registered\n", dai_drv->name); -clk_err: - clk_disable_unprepare(ctx->axi_clk); - return ret; -} - -static void xlnx_spdif_remove(struct platform_device *pdev) -{ - struct spdif_dev_data *ctx = dev_get_drvdata(&pdev->dev); - - clk_disable_unprepare(ctx->axi_clk); + return 0; } static struct platform_driver xlnx_spdif_driver = { @@ -325,7 +310,6 @@ static struct platform_driver xlnx_spdif_driver = { .of_match_table = xlnx_spdif_of_match, }, .probe = xlnx_spdif_probe, - .remove = xlnx_spdif_remove, }; module_platform_driver(xlnx_spdif_driver); diff --git a/sound/usb/Makefile b/sound/usb/Makefile index 0532499dbc6d6a..30bd5348477b17 100644 --- a/sound/usb/Makefile +++ b/sound/usb/Makefile @@ -6,6 +6,7 @@ snd-usb-audio-y := card.o \ clock.o \ endpoint.o \ + fcp.o \ format.o \ helper.o \ implicit.o \ diff --git a/sound/usb/fcp.c b/sound/usb/fcp.c new file mode 100644 index 00000000000000..7df65041ace556 --- /dev/null +++ b/sound/usb/fcp.c @@ -0,0 +1,1134 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Focusrite Control Protocol Driver for ALSA + * + * Copyright (c) 2024-2025 by Geoffrey D. Bennett + */ +/* + * DOC: Theory of Operation + * + * The Focusrite Control Protocol (FCP) driver provides a minimal + * kernel interface that allows a user-space driver (primarily + * fcp-server) to communicate with Focusrite USB audio interfaces + * using their vendor-specific protocol. This protocol is used by + * Scarlett 2nd Gen, 3rd Gen, 4th Gen, Clarett USB, Clarett+, and + * Vocaster series devices. + * + * Unlike the existing scarlett2 driver which implements all controls + * in kernel space, this driver takes a lighter-weight approach by + * moving most functionality to user space. The only control + * implemented in kernel space is the Level Meter, since it requires + * frequent polling of volatile data. + * + * The driver provides an hwdep interface that allows the user-space + * driver to: + * - Initialise the protocol + * - Send arbitrary FCP commands to the device + * - Receive notifications from the device + * - Configure the Level Meter control + * + * Usage Flow + * ---------- + * 1. Open the hwdep device (requires CAP_SYS_RAWIO) + * 2. Get protocol version using FCP_IOCTL_PVERSION + * 3. Initialise protocol using FCP_IOCTL_INIT + * 4. Send commands using FCP_IOCTL_CMD + * 5. Receive notifications using read() + * 6. Optionally set up the Level Meter control using + * FCP_IOCTL_SET_METER_MAP + * 7. Optionally add labels to the Level Meter control using + * FCP_IOCTL_SET_METER_LABELS + * + * Level Meter + * ----------- + * The Level Meter is implemented as an ALSA control that provides + * real-time level monitoring. When the control is read, the driver + * requests the current meter levels from the device, translates the + * levels using the configured mapping, and returns the result to the + * user. The mapping between device meters and the ALSA control's + * channels is configured with FCP_IOCTL_SET_METER_MAP. + * + * Labels for the Level Meter channels can be set using + * FCP_IOCTL_SET_METER_LABELS and read by applications through the + * control's TLV data. The labels are transferred as a sequence of + * null-terminated strings. + */ + +#include +#include + +#include +#include +#include + +#include + +#include "usbaudio.h" +#include "mixer.h" +#include "helper.h" + +#include "fcp.h" + +/* notify waiting to send to *file */ +struct fcp_notify { + wait_queue_head_t queue; + u32 event; + spinlock_t lock; +}; + +struct fcp_data { + struct usb_mixer_interface *mixer; + + struct mutex mutex; /* serialise access to the device */ + struct completion cmd_done; /* wait for command completion */ + struct file *file; /* hwdep file */ + + struct fcp_notify notify; + + u8 bInterfaceNumber; + u8 bEndpointAddress; + u16 wMaxPacketSize; + u8 bInterval; + + uint16_t step0_resp_size; + uint16_t step2_resp_size; + uint32_t init1_opcode; + uint32_t init2_opcode; + + u8 init; + u16 seq; + + u8 num_meter_slots; + s16 *meter_level_map; + __le32 *meter_levels; + struct snd_kcontrol *meter_ctl; + + unsigned int *meter_labels_tlv; + int meter_labels_tlv_size; +}; + +/*** USB Interactions ***/ + +/* FCP Command ACK notification bit */ +#define FCP_NOTIFY_ACK 1 + +/* Vendor-specific USB control requests */ +#define FCP_USB_REQ_STEP0 0 +#define FCP_USB_REQ_CMD_TX 2 +#define FCP_USB_REQ_CMD_RX 3 + +/* Focusrite Control Protocol opcodes that the kernel side needs to + * know about + */ +#define FCP_USB_REBOOT 0x00000003 +#define FCP_USB_GET_METER 0x00001001 +#define FCP_USB_FLASH_ERASE 0x00004002 +#define FCP_USB_FLASH_WRITE 0x00004004 + +#define FCP_USB_METER_LEVELS_GET_MAGIC 1 + +#define FCP_SEGMENT_APP_GOLD 0 + +/* Forward declarations */ +static int fcp_init(struct usb_mixer_interface *mixer, + void *step0_resp, void *step2_resp); + +/* FCP command request/response format */ +struct fcp_usb_packet { + __le32 opcode; + __le16 size; + __le16 seq; + __le32 error; + __le32 pad; + u8 data[]; +}; + +static void fcp_fill_request_header(struct fcp_data *private, + struct fcp_usb_packet *req, + u32 opcode, u16 req_size) +{ + /* sequence must go up by 1 for each request */ + u16 seq = private->seq++; + + req->opcode = cpu_to_le32(opcode); + req->size = cpu_to_le16(req_size); + req->seq = cpu_to_le16(seq); + req->error = 0; + req->pad = 0; +} + +static int fcp_usb_tx(struct usb_device *dev, int interface, + void *buf, u16 size) +{ + return snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + FCP_USB_REQ_CMD_TX, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT, + 0, interface, buf, size); +} + +static int fcp_usb_rx(struct usb_device *dev, int interface, + void *buf, u16 size) +{ + return snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), + FCP_USB_REQ_CMD_RX, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + 0, interface, buf, size); +} + +/* Send an FCP command and get the response */ +static int fcp_usb(struct usb_mixer_interface *mixer, u32 opcode, + const void *req_data, u16 req_size, + void *resp_data, u16 resp_size) +{ + struct fcp_data *private = mixer->private_data; + struct usb_device *dev = mixer->chip->dev; + struct fcp_usb_packet *req __free(kfree) = NULL; + struct fcp_usb_packet *resp __free(kfree) = NULL; + size_t req_buf_size = struct_size(req, data, req_size); + size_t resp_buf_size = struct_size(resp, data, resp_size); + int retries = 0; + const int max_retries = 5; + int err; + + if (!mixer->urb) + return -ENODEV; + + req = kmalloc(req_buf_size, GFP_KERNEL); + if (!req) + return -ENOMEM; + + resp = kmalloc(resp_buf_size, GFP_KERNEL); + if (!resp) + return -ENOMEM; + + /* build request message */ + fcp_fill_request_header(private, req, opcode, req_size); + if (req_size) + memcpy(req->data, req_data, req_size); + + /* send the request and retry on EPROTO */ +retry: + err = fcp_usb_tx(dev, private->bInterfaceNumber, req, req_buf_size); + if (err == -EPROTO && ++retries <= max_retries) { + msleep(1 << (retries - 1)); + goto retry; + } + + if (err != req_buf_size) { + usb_audio_err(mixer->chip, + "FCP request %08x failed: %d\n", opcode, err); + return -EINVAL; + } + + if (!wait_for_completion_timeout(&private->cmd_done, + msecs_to_jiffies(1000))) { + usb_audio_err(mixer->chip, + "FCP request %08x timed out\n", opcode); + + return -ETIMEDOUT; + } + + /* send a second message to get the response */ + err = fcp_usb_rx(dev, private->bInterfaceNumber, resp, resp_buf_size); + + /* validate the response */ + + if (err < 0) { + + /* ESHUTDOWN and EPROTO are valid responses to a + * reboot request + */ + if (opcode == FCP_USB_REBOOT && + (err == -ESHUTDOWN || err == -EPROTO)) + return 0; + + usb_audio_err(mixer->chip, + "FCP read response %08x failed: %d\n", + opcode, err); + return -EINVAL; + } + + if (err < sizeof(*resp)) { + usb_audio_err(mixer->chip, + "FCP response %08x too short: %d\n", + opcode, err); + return -EINVAL; + } + + if (req->seq != resp->seq) { + usb_audio_err(mixer->chip, + "FCP response %08x seq mismatch %d/%d\n", + opcode, + le16_to_cpu(req->seq), le16_to_cpu(resp->seq)); + return -EINVAL; + } + + if (req->opcode != resp->opcode) { + usb_audio_err(mixer->chip, + "FCP response %08x opcode mismatch %08x\n", + opcode, le32_to_cpu(resp->opcode)); + return -EINVAL; + } + + if (resp->error) { + usb_audio_err(mixer->chip, + "FCP response %08x error %d\n", + opcode, le32_to_cpu(resp->error)); + return -EINVAL; + } + + if (err != resp_buf_size) { + usb_audio_err(mixer->chip, + "FCP response %08x buffer size mismatch %d/%zu\n", + opcode, err, resp_buf_size); + return -EINVAL; + } + + if (resp_size != le16_to_cpu(resp->size)) { + usb_audio_err(mixer->chip, + "FCP response %08x size mismatch %d/%d\n", + opcode, resp_size, le16_to_cpu(resp->size)); + return -EINVAL; + } + + if (resp_data && resp_size > 0) + memcpy(resp_data, resp->data, resp_size); + + return 0; +} + +static int fcp_reinit(struct usb_mixer_interface *mixer) +{ + struct fcp_data *private = mixer->private_data; + void *step0_resp __free(kfree) = NULL; + void *step2_resp __free(kfree) = NULL; + + if (mixer->urb) + return 0; + + step0_resp = kmalloc(private->step0_resp_size, GFP_KERNEL); + if (!step0_resp) + return -ENOMEM; + step2_resp = kmalloc(private->step2_resp_size, GFP_KERNEL); + if (!step2_resp) + return -ENOMEM; + + return fcp_init(mixer, step0_resp, step2_resp); +} + +/*** Control Functions ***/ + +/* helper function to create a new control */ +static int fcp_add_new_ctl(struct usb_mixer_interface *mixer, + const struct snd_kcontrol_new *ncontrol, + int index, int channels, const char *name, + struct snd_kcontrol **kctl_return) +{ + struct snd_kcontrol *kctl; + struct usb_mixer_elem_info *elem; + int err; + + elem = kzalloc(sizeof(*elem), GFP_KERNEL); + if (!elem) + return -ENOMEM; + + /* We set USB_MIXER_BESPOKEN type, so that the core USB mixer code + * ignores them for resume and other operations. + * Also, the head.id field is set to 0, as we don't use this field. + */ + elem->head.mixer = mixer; + elem->control = index; + elem->head.id = 0; + elem->channels = channels; + elem->val_type = USB_MIXER_BESPOKEN; + + kctl = snd_ctl_new1(ncontrol, elem); + if (!kctl) { + kfree(elem); + return -ENOMEM; + } + kctl->private_free = snd_usb_mixer_elem_free; + + strscpy(kctl->id.name, name, sizeof(kctl->id.name)); + + err = snd_usb_mixer_add_control(&elem->head, kctl); + if (err < 0) + return err; + + if (kctl_return) + *kctl_return = kctl; + + return 0; +} + +/*** Level Meter Control ***/ + +static int fcp_meter_ctl_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = elem->channels; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 4095; + uinfo->value.integer.step = 1; + return 0; +} + +static int fcp_meter_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct fcp_data *private = mixer->private_data; + int num_meter_slots, resp_size; + __le32 *resp = private->meter_levels; + int i, err = 0; + + struct { + __le16 pad; + __le16 num_meters; + __le32 magic; + } __packed req; + + guard(mutex)(&private->mutex); + + err = fcp_reinit(mixer); + if (err < 0) + return err; + + num_meter_slots = private->num_meter_slots; + resp_size = num_meter_slots * sizeof(u32); + + req.pad = 0; + req.num_meters = cpu_to_le16(num_meter_slots); + req.magic = cpu_to_le32(FCP_USB_METER_LEVELS_GET_MAGIC); + err = fcp_usb(mixer, FCP_USB_GET_METER, + &req, sizeof(req), resp, resp_size); + if (err < 0) + return err; + + /* copy & translate from resp[] using meter_level_map[] */ + for (i = 0; i < elem->channels; i++) { + int idx = private->meter_level_map[i]; + int value = idx < 0 ? 0 : le32_to_cpu(resp[idx]); + + ucontrol->value.integer.value[i] = value; + } + + return 0; +} + +static int fcp_meter_tlv_callback(struct snd_kcontrol *kctl, + int op_flag, unsigned int size, + unsigned int __user *tlv) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct fcp_data *private = mixer->private_data; + + guard(mutex)(&private->mutex); + + if (op_flag == SNDRV_CTL_TLV_OP_READ) { + if (private->meter_labels_tlv_size == 0) + return 0; + + if (size > private->meter_labels_tlv_size) + size = private->meter_labels_tlv_size; + + if (copy_to_user(tlv, private->meter_labels_tlv, size)) + return -EFAULT; + + return size; + } + + return -EINVAL; +} + +static const struct snd_kcontrol_new fcp_meter_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = fcp_meter_ctl_info, + .get = fcp_meter_ctl_get, + .tlv = { .c = fcp_meter_tlv_callback }, +}; + +/*** hwdep interface ***/ + +/* FCP initialisation */ +static int fcp_ioctl_init(struct usb_mixer_interface *mixer, + struct fcp_init __user *arg) +{ + struct fcp_init init; + struct usb_device *dev = mixer->chip->dev; + struct fcp_data *private = mixer->private_data; + void *resp __free(kfree) = NULL; + void *step2_resp; + int err, buf_size; + + if (usb_pipe_type_check(dev, usb_sndctrlpipe(dev, 0))) + return -EINVAL; + + /* Get initialisation parameters */ + if (copy_from_user(&init, arg, sizeof(init))) + return -EFAULT; + + /* Validate the response sizes */ + if (init.step0_resp_size < 1 || + init.step0_resp_size > 255 || + init.step2_resp_size < 1 || + init.step2_resp_size > 255) + return -EINVAL; + + /* Allocate response buffer */ + buf_size = init.step0_resp_size + init.step2_resp_size; + + resp = kmalloc(buf_size, GFP_KERNEL); + if (!resp) + return -ENOMEM; + + private->step0_resp_size = init.step0_resp_size; + private->step2_resp_size = init.step2_resp_size; + private->init1_opcode = init.init1_opcode; + private->init2_opcode = init.init2_opcode; + + step2_resp = resp + private->step0_resp_size; + + err = fcp_init(mixer, resp, step2_resp); + if (err < 0) + return err; + + if (copy_to_user(arg->resp, resp, buf_size)) + return -EFAULT; + + return 0; +} + +/* Check that the command is allowed + * Don't permit erasing/writing segment 0 (App_Gold) + */ +static int fcp_validate_cmd(u32 opcode, void *data, u16 size) +{ + if (opcode == FCP_USB_FLASH_ERASE) { + struct { + __le32 segment_num; + __le32 pad; + } __packed *req = data; + + if (size != sizeof(*req)) + return -EINVAL; + + if (le32_to_cpu(req->segment_num) == FCP_SEGMENT_APP_GOLD) + return -EPERM; + + if (req->pad != 0) + return -EINVAL; + + } else if (opcode == FCP_USB_FLASH_WRITE) { + struct { + __le32 segment_num; + __le32 offset; + __le32 pad; + u8 data[]; + } __packed *req = data; + + if (size < sizeof(*req)) + return -EINVAL; + + if (le32_to_cpu(req->segment_num) == FCP_SEGMENT_APP_GOLD) + return -EPERM; + + if (req->pad != 0) + return -EINVAL; + } + + return 0; +} + +/* Execute an FCP command specified by the user */ +static int fcp_ioctl_cmd(struct usb_mixer_interface *mixer, + struct fcp_cmd __user *arg) +{ + struct fcp_cmd cmd; + int err, buf_size; + void *data __free(kfree) = NULL; + + /* get opcode and request/response size */ + if (copy_from_user(&cmd, arg, sizeof(cmd))) + return -EFAULT; + + /* validate request and response sizes */ + if (cmd.req_size > 4096 || cmd.resp_size > 4096) + return -EINVAL; + + /* reinit if needed */ + err = fcp_reinit(mixer); + if (err < 0) + return err; + + /* allocate request/response buffer */ + buf_size = max(cmd.req_size, cmd.resp_size); + + if (buf_size > 0) { + data = kmalloc(buf_size, GFP_KERNEL); + if (!data) + return -ENOMEM; + } + + /* copy request from user */ + if (cmd.req_size > 0) + if (copy_from_user(data, arg->data, cmd.req_size)) + return -EFAULT; + + /* check that the command is allowed */ + err = fcp_validate_cmd(cmd.opcode, data, cmd.req_size); + if (err < 0) + return err; + + /* send request, get response */ + err = fcp_usb(mixer, cmd.opcode, + data, cmd.req_size, data, cmd.resp_size); + if (err < 0) + return err; + + /* copy response to user */ + if (cmd.resp_size > 0) + if (copy_to_user(arg->data, data, cmd.resp_size)) + return -EFAULT; + + return 0; +} + +/* Validate the Level Meter map passed by the user */ +static int validate_meter_map(const s16 *map, int map_size, int meter_slots) +{ + int i; + + for (i = 0; i < map_size; i++) + if (map[i] < -1 || map[i] >= meter_slots) + return -EINVAL; + + return 0; +} + +/* Set the Level Meter map and add the control */ +static int fcp_ioctl_set_meter_map(struct usb_mixer_interface *mixer, + struct fcp_meter_map __user *arg) +{ + struct fcp_meter_map map; + struct fcp_data *private = mixer->private_data; + s16 *tmp_map __free(kfree) = NULL; + int err; + + if (copy_from_user(&map, arg, sizeof(map))) + return -EFAULT; + + /* Don't allow changing the map size or meter slots once set */ + if (private->meter_ctl) { + struct usb_mixer_elem_info *elem = + private->meter_ctl->private_data; + + if (map.map_size != elem->channels || + map.meter_slots != private->num_meter_slots) + return -EINVAL; + } + + /* Validate the map size */ + if (map.map_size < 1 || map.map_size > 255 || + map.meter_slots < 1 || map.meter_slots > 255) + return -EINVAL; + + /* Allocate and copy the map data */ + tmp_map = kmalloc_array(map.map_size, sizeof(s16), GFP_KERNEL); + if (!tmp_map) + return -ENOMEM; + + if (copy_from_user(tmp_map, arg->map, map.map_size * sizeof(s16))) + return -EFAULT; + + err = validate_meter_map(tmp_map, map.map_size, map.meter_slots); + if (err < 0) + return err; + + /* If the control doesn't exist, create it */ + if (!private->meter_ctl) { + s16 *new_map __free(kfree) = NULL; + __le32 *meter_levels __free(kfree) = NULL; + + /* Allocate buffer for the map */ + new_map = kmalloc_array(map.map_size, sizeof(s16), GFP_KERNEL); + if (!new_map) + return -ENOMEM; + + /* Allocate buffer for reading meter levels */ + meter_levels = kmalloc_array(map.meter_slots, sizeof(__le32), + GFP_KERNEL); + if (!meter_levels) + return -ENOMEM; + + /* Create the Level Meter control */ + err = fcp_add_new_ctl(mixer, &fcp_meter_ctl, 0, map.map_size, + "Level Meter", &private->meter_ctl); + if (err < 0) + return err; + + /* Success; save the pointers in private and don't free them */ + private->meter_level_map = new_map; + private->meter_levels = meter_levels; + private->num_meter_slots = map.meter_slots; + new_map = NULL; + meter_levels = NULL; + } + + /* Install the new map */ + memcpy(private->meter_level_map, tmp_map, map.map_size * sizeof(s16)); + + return 0; +} + +/* Set the Level Meter labels */ +static int fcp_ioctl_set_meter_labels(struct usb_mixer_interface *mixer, + struct fcp_meter_labels __user *arg) +{ + struct fcp_meter_labels labels; + struct fcp_data *private = mixer->private_data; + unsigned int *tlv_data; + unsigned int tlv_size, data_size; + + if (copy_from_user(&labels, arg, sizeof(labels))) + return -EFAULT; + + /* Remove existing labels if size is zero */ + if (!labels.labels_size) { + + /* Clear TLV read/callback bits if labels were present */ + if (private->meter_labels_tlv) { + private->meter_ctl->vd[0].access &= + ~(SNDRV_CTL_ELEM_ACCESS_TLV_READ | + SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK); + snd_ctl_notify(mixer->chip->card, + SNDRV_CTL_EVENT_MASK_INFO, + &private->meter_ctl->id); + } + + kfree(private->meter_labels_tlv); + private->meter_labels_tlv = NULL; + private->meter_labels_tlv_size = 0; + + return 0; + } + + /* Validate size */ + if (labels.labels_size > 4096) + return -EINVAL; + + /* Calculate padded data size */ + data_size = ALIGN(labels.labels_size, sizeof(unsigned int)); + + /* Calculate total TLV size including header */ + tlv_size = sizeof(unsigned int) * 2 + data_size; + + /* Allocate, set up TLV header, and copy the labels data */ + tlv_data = kzalloc(tlv_size, GFP_KERNEL); + if (!tlv_data) + return -ENOMEM; + tlv_data[0] = SNDRV_CTL_TLVT_FCP_CHANNEL_LABELS; + tlv_data[1] = data_size; + if (copy_from_user(&tlv_data[2], arg->labels, labels.labels_size)) { + kfree(tlv_data); + return -EFAULT; + } + + /* Set TLV read/callback bits if labels weren't present */ + if (!private->meter_labels_tlv) { + private->meter_ctl->vd[0].access |= + SNDRV_CTL_ELEM_ACCESS_TLV_READ | + SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK; + snd_ctl_notify(mixer->chip->card, + SNDRV_CTL_EVENT_MASK_INFO, + &private->meter_ctl->id); + } + + /* Swap in the new labels */ + kfree(private->meter_labels_tlv); + private->meter_labels_tlv = tlv_data; + private->meter_labels_tlv_size = tlv_size; + + return 0; +} + +static int fcp_hwdep_open(struct snd_hwdep *hw, struct file *file) +{ + struct usb_mixer_interface *mixer = hw->private_data; + struct fcp_data *private = mixer->private_data; + + if (!capable(CAP_SYS_RAWIO)) + return -EPERM; + + private->file = file; + + return 0; +} + +static int fcp_hwdep_ioctl(struct snd_hwdep *hw, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct usb_mixer_interface *mixer = hw->private_data; + struct fcp_data *private = mixer->private_data; + void __user *argp = (void __user *)arg; + + guard(mutex)(&private->mutex); + + switch (cmd) { + + case FCP_IOCTL_PVERSION: + return put_user(FCP_HWDEP_VERSION, + (int __user *)argp) ? -EFAULT : 0; + break; + + case FCP_IOCTL_INIT: + return fcp_ioctl_init(mixer, argp); + + case FCP_IOCTL_CMD: + if (!private->init) + return -EINVAL; + return fcp_ioctl_cmd(mixer, argp); + + case FCP_IOCTL_SET_METER_MAP: + if (!private->init) + return -EINVAL; + return fcp_ioctl_set_meter_map(mixer, argp); + + case FCP_IOCTL_SET_METER_LABELS: + if (!private->init) + return -EINVAL; + if (!private->meter_ctl) + return -EINVAL; + return fcp_ioctl_set_meter_labels(mixer, argp); + + default: + return -ENOIOCTLCMD; + } + + /* not reached */ +} + +static long fcp_hwdep_read(struct snd_hwdep *hw, char __user *buf, + long count, loff_t *offset) +{ + struct usb_mixer_interface *mixer = hw->private_data; + struct fcp_data *private = mixer->private_data; + unsigned long flags; + long ret = 0; + u32 event; + + if (count < sizeof(event)) + return -EINVAL; + + ret = wait_event_interruptible(private->notify.queue, + private->notify.event); + if (ret) + return ret; + + spin_lock_irqsave(&private->notify.lock, flags); + event = private->notify.event; + private->notify.event = 0; + spin_unlock_irqrestore(&private->notify.lock, flags); + + if (copy_to_user(buf, &event, sizeof(event))) + return -EFAULT; + + return sizeof(event); +} + +static __poll_t fcp_hwdep_poll(struct snd_hwdep *hw, + struct file *file, + poll_table *wait) +{ + struct usb_mixer_interface *mixer = hw->private_data; + struct fcp_data *private = mixer->private_data; + __poll_t mask = 0; + + poll_wait(file, &private->notify.queue, wait); + + if (private->notify.event) + mask |= EPOLLIN | EPOLLRDNORM; + + return mask; +} + +static int fcp_hwdep_release(struct snd_hwdep *hw, struct file *file) +{ + struct usb_mixer_interface *mixer = hw->private_data; + struct fcp_data *private = mixer->private_data; + + if (!private) + return 0; + + private->file = NULL; + + return 0; +} + +static int fcp_hwdep_init(struct usb_mixer_interface *mixer) +{ + struct snd_hwdep *hw; + int err; + + err = snd_hwdep_new(mixer->chip->card, "Focusrite Control", 0, &hw); + if (err < 0) + return err; + + hw->private_data = mixer; + hw->exclusive = 1; + hw->ops.open = fcp_hwdep_open; + hw->ops.ioctl = fcp_hwdep_ioctl; + hw->ops.ioctl_compat = fcp_hwdep_ioctl; + hw->ops.read = fcp_hwdep_read; + hw->ops.poll = fcp_hwdep_poll; + hw->ops.release = fcp_hwdep_release; + + return 0; +} + +/*** Cleanup ***/ + +static void fcp_cleanup_urb(struct usb_mixer_interface *mixer) +{ + if (!mixer->urb) + return; + + usb_kill_urb(mixer->urb); + kfree(mixer->urb->transfer_buffer); + usb_free_urb(mixer->urb); + mixer->urb = NULL; +} + +static void fcp_private_free(struct usb_mixer_interface *mixer) +{ + struct fcp_data *private = mixer->private_data; + + fcp_cleanup_urb(mixer); + + kfree(private->meter_level_map); + kfree(private->meter_levels); + kfree(private->meter_labels_tlv); + kfree(private); + mixer->private_data = NULL; +} + +static void fcp_private_suspend(struct usb_mixer_interface *mixer) +{ + fcp_cleanup_urb(mixer); +} + +/*** Callbacks ***/ + +static void fcp_notify(struct urb *urb) +{ + struct usb_mixer_interface *mixer = urb->context; + struct fcp_data *private = mixer->private_data; + int len = urb->actual_length; + int ustatus = urb->status; + u32 data; + + if (ustatus != 0 || len != 8) + goto requeue; + + data = le32_to_cpu(*(__le32 *)urb->transfer_buffer); + + /* Handle command acknowledgement */ + if (data & FCP_NOTIFY_ACK) { + complete(&private->cmd_done); + data &= ~FCP_NOTIFY_ACK; + } + + if (data) { + unsigned long flags; + + spin_lock_irqsave(&private->notify.lock, flags); + private->notify.event |= data; + spin_unlock_irqrestore(&private->notify.lock, flags); + + wake_up_interruptible(&private->notify.queue); + } + +requeue: + if (ustatus != -ENOENT && + ustatus != -ECONNRESET && + ustatus != -ESHUTDOWN) { + urb->dev = mixer->chip->dev; + usb_submit_urb(urb, GFP_ATOMIC); + } else { + complete(&private->cmd_done); + } +} + +/* Submit a URB to receive notifications from the device */ +static int fcp_init_notify(struct usb_mixer_interface *mixer) +{ + struct usb_device *dev = mixer->chip->dev; + struct fcp_data *private = mixer->private_data; + unsigned int pipe = usb_rcvintpipe(dev, private->bEndpointAddress); + void *transfer_buffer; + int err; + + /* Already set up */ + if (mixer->urb) + return 0; + + if (usb_pipe_type_check(dev, pipe)) + return -EINVAL; + + mixer->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!mixer->urb) + return -ENOMEM; + + transfer_buffer = kmalloc(private->wMaxPacketSize, GFP_KERNEL); + if (!transfer_buffer) { + usb_free_urb(mixer->urb); + mixer->urb = NULL; + return -ENOMEM; + } + + usb_fill_int_urb(mixer->urb, dev, pipe, + transfer_buffer, private->wMaxPacketSize, + fcp_notify, mixer, private->bInterval); + + init_completion(&private->cmd_done); + + err = usb_submit_urb(mixer->urb, GFP_KERNEL); + if (err) { + usb_audio_err(mixer->chip, + "%s: usb_submit_urb failed: %d\n", + __func__, err); + kfree(transfer_buffer); + usb_free_urb(mixer->urb); + mixer->urb = NULL; + } + + return err; +} + +/*** Initialisation ***/ + +static int fcp_init(struct usb_mixer_interface *mixer, + void *step0_resp, void *step2_resp) +{ + struct fcp_data *private = mixer->private_data; + struct usb_device *dev = mixer->chip->dev; + int err; + + err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), + FCP_USB_REQ_STEP0, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + 0, private->bInterfaceNumber, + step0_resp, private->step0_resp_size); + if (err < 0) + return err; + + err = fcp_init_notify(mixer); + if (err < 0) + return err; + + private->seq = 0; + private->init = 1; + + err = fcp_usb(mixer, private->init1_opcode, NULL, 0, NULL, 0); + if (err < 0) + return err; + + err = fcp_usb(mixer, private->init2_opcode, + NULL, 0, step2_resp, private->step2_resp_size); + if (err < 0) + return err; + + return 0; +} + +static int fcp_init_private(struct usb_mixer_interface *mixer) +{ + struct fcp_data *private = + kzalloc(sizeof(struct fcp_data), GFP_KERNEL); + + if (!private) + return -ENOMEM; + + mutex_init(&private->mutex); + init_waitqueue_head(&private->notify.queue); + spin_lock_init(&private->notify.lock); + + mixer->private_data = private; + mixer->private_free = fcp_private_free; + mixer->private_suspend = fcp_private_suspend; + + private->mixer = mixer; + + return 0; +} + +/* Look through the interface descriptors for the Focusrite Control + * interface (bInterfaceClass = 255 Vendor Specific Class) and set + * bInterfaceNumber, bEndpointAddress, wMaxPacketSize, and bInterval + * in private + */ +static int fcp_find_fc_interface(struct usb_mixer_interface *mixer) +{ + struct snd_usb_audio *chip = mixer->chip; + struct fcp_data *private = mixer->private_data; + struct usb_host_config *config = chip->dev->actconfig; + int i; + + for (i = 0; i < config->desc.bNumInterfaces; i++) { + struct usb_interface *intf = config->interface[i]; + struct usb_interface_descriptor *desc = + &intf->altsetting[0].desc; + struct usb_endpoint_descriptor *epd; + + if (desc->bInterfaceClass != 255) + continue; + + epd = get_endpoint(intf->altsetting, 0); + private->bInterfaceNumber = desc->bInterfaceNumber; + private->bEndpointAddress = epd->bEndpointAddress & + USB_ENDPOINT_NUMBER_MASK; + private->wMaxPacketSize = le16_to_cpu(epd->wMaxPacketSize); + private->bInterval = epd->bInterval; + return 0; + } + + usb_audio_err(chip, "Focusrite vendor-specific interface not found\n"); + return -EINVAL; +} + +int snd_fcp_init(struct usb_mixer_interface *mixer) +{ + struct snd_usb_audio *chip = mixer->chip; + int err; + + /* only use UAC_VERSION_2 */ + if (!mixer->protocol) + return 0; + + err = fcp_init_private(mixer); + if (err < 0) + return err; + + err = fcp_find_fc_interface(mixer); + if (err < 0) + return err; + + err = fcp_hwdep_init(mixer); + if (err < 0) + return err; + + usb_audio_info(chip, + "Focusrite Control Protocol Driver ready (pid=0x%04x); " + "report any issues to " + "https://github.com/geoffreybennett/fcp-support/issues", + USB_ID_PRODUCT(chip->usb_id)); + + return err; +} diff --git a/sound/usb/fcp.h b/sound/usb/fcp.h new file mode 100644 index 00000000000000..d58cc7db75b1cc --- /dev/null +++ b/sound/usb/fcp.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __USBAUDIO_FCP_H +#define __USBAUDIO_FCP_H + +int snd_fcp_init(struct usb_mixer_interface *mixer); + +#endif /* __USBAUDIO_FCP_H */ diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index 23fcd680167d02..ed6127b0389fff 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -38,6 +38,7 @@ #include "mixer_us16x08.h" #include "mixer_s1810c.h" #include "helper.h" +#include "fcp.h" struct std_mono_table { unsigned int unitid, control, cmask; @@ -4090,6 +4091,12 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) err = snd_scarlett2_init(mixer); break; + case USB_ID(0x1235, 0x821b): /* Focusrite Scarlett 16i16 4th Gen */ + case USB_ID(0x1235, 0x821c): /* Focusrite Scarlett 18i16 4th Gen */ + case USB_ID(0x1235, 0x821d): /* Focusrite Scarlett 18i20 4th Gen */ + err = snd_fcp_init(mixer); + break; + case USB_ID(0x041e, 0x323b): /* Creative Sound Blaster E1 */ err = snd_soundblaster_e1_switch_create(mixer); break; diff --git a/sound/usb/mixer_scarlett2.c b/sound/usb/mixer_scarlett2.c index 7f595c1752a5a1..288d22e6a0b253 100644 --- a/sound/usb/mixer_scarlett2.c +++ b/sound/usb/mixer_scarlett2.c @@ -166,6 +166,7 @@ #include "helper.h" #include "mixer_scarlett2.h" +#include "fcp.h" /* device_setup value to allow turning MSD mode back on */ #define SCARLETT2_MSD_ENABLE 0x02 @@ -173,6 +174,9 @@ /* device_setup value to disable this mixer driver */ #define SCARLETT2_DISABLE 0x04 +/* device_setup value to use the FCP driver instead */ +#define SCARLETT2_USE_FCP_DRIVER 0x08 + /* some gui mixers can't handle negative ctl values */ #define SCARLETT2_VOLUME_BIAS 127 @@ -9702,6 +9706,10 @@ int snd_scarlett2_init(struct usb_mixer_interface *mixer) if (!mixer->protocol) return 0; + /* check if the user wants to use the FCP driver instead */ + if (chip->setup & SCARLETT2_USE_FCP_DRIVER) + return snd_fcp_init(mixer); + /* find entry in scarlett2_devices */ entry = get_scarlett2_device_entry(mixer); if (!entry) { diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 8ba0aff8be2ec2..7968d6a2f592ac 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2239,6 +2239,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_CTL_MSG_DELAY_1M), DEVICE_FLG(0x0c45, 0x6340, /* Sonix HD USB Camera */ QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x0d8c, 0x0014, /* USB Audio Device */ + QUIRK_FLAG_CTL_MSG_DELAY_1M), DEVICE_FLG(0x0ecb, 0x205c, /* JBL Quantum610 Wireless */ QUIRK_FLAG_FIXED_RATE), DEVICE_FLG(0x0ecb, 0x2069, /* JBL Quantum810 Wireless */