diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt new file mode 100644 index 000000000000..7bc73e57add3 --- /dev/null +++ b/Documentation/devicetree/bindings/vendor-prefixes.txt @@ -0,0 +1,464 @@ +Device tree binding vendor prefix registry. Keep list in alphabetical order. + +This isn't an exhaustive list, but you should add new prefixes to it before +using them to avoid name-space collisions. + +abilis Abilis Systems +abracon Abracon Corporation +actions Actions Semiconductor Co., Ltd. +active-semi Active-Semi International Inc +ad Avionic Design GmbH +adafruit Adafruit Industries, LLC +adapteva Adapteva, Inc. +adaptrum Adaptrum, Inc. +adh AD Holdings Plc. +adi Analog Devices, Inc. +advantech Advantech Corporation +aeroflexgaisler Aeroflex Gaisler AB +al Annapurna Labs +allo Allo.com +allwinner Allwinner Technology Co., Ltd. +alphascale AlphaScale Integrated Circuits Systems, Inc. +altr Altera Corp. +amarula Amarula Solutions +amazon Amazon.com, Inc. +amcc Applied Micro Circuits Corporation (APM, formally AMCC) +amd Advanced Micro Devices (AMD), Inc. +amediatech Shenzhen Amediatech Technology Co., Ltd +amlogic Amlogic, Inc. +ampire Ampire Co., Ltd. +ams AMS AG +amstaos AMS-Taos Inc. +analogix Analogix Semiconductor, Inc. +andestech Andes Technology Corporation +apm Applied Micro Circuits Corporation (APM) +aptina Aptina Imaging +arasan Arasan Chip Systems +archermind ArcherMind Technology (Nanjing) Co., Ltd. +arctic Arctic Sand +aries Aries Embedded GmbH +arm ARM Ltd. +armadeus ARMadeus Systems SARL +arrow Arrow Electronics +artesyn Artesyn Embedded Technologies Inc. +asahi-kasei Asahi Kasei Corp. +aspeed ASPEED Technology Inc. +asus AsusTek Computer Inc. +atlas Atlas Scientific LLC +atmel Atmel Corporation +auo AU Optronics Corporation +auvidea Auvidea GmbH +avago Avago Technologies +avia avia semiconductor +avic Shanghai AVIC Optoelectronics Co., Ltd. +avnet Avnet, Inc. +axentia Axentia Technologies AB +axis Axis Communications AB +bananapi BIPAI KEJI LIMITED +bhf Beckhoff Automation GmbH & Co. KG +bitmain Bitmain Technologies +blokas Vilniaus Blokas UAB +blokaslabs Vilniaus Blokas UAB +boe BOE Technology Group Co., Ltd. +bosch Bosch Sensortec GmbH +boundary Boundary Devices Inc. +brcm Broadcom Corporation +buffalo Buffalo, Inc. +bticino Bticino International +calxeda Calxeda +capella Capella Microsystems, Inc +cascoda Cascoda, Ltd. +catalyst Catalyst Semiconductor, Inc. +cavium Cavium, Inc. +cdns Cadence Design Systems Inc. +cdtech CDTech(H.K.) Electronics Limited +ceva Ceva, Inc. +chipidea Chipidea, Inc +chipone ChipOne +chipspark ChipSPARK +chrp Common Hardware Reference Platform +chunghwa Chunghwa Picture Tubes Ltd. +ciaa Computadora Industrial Abierta Argentina +cirrus Cirrus Logic, Inc. +cloudengines Cloud Engines, Inc. +cnm Chips&Media, Inc. +cnxt Conexant Systems, Inc. +compulab CompuLab Ltd. +cortina Cortina Systems, Inc. +cosmic Cosmic Circuits +crane Crane Connectivity Solutions +creative Creative Technology Ltd +crystalfontz Crystalfontz America, Inc. +csky Hangzhou C-SKY Microsystems Co., Ltd +cubietech Cubietech, Ltd. +cypress Cypress Semiconductor Corporation +cznic CZ.NIC, z.s.p.o. +dallas Maxim Integrated Products (formerly Dallas Semiconductor) +dataimage DataImage, Inc. +davicom DAVICOM Semiconductor, Inc. +delta Delta Electronics, Inc. +denx Denx Software Engineering +devantech Devantech, Ltd. +dh DH electronics GmbH +digi Digi International Inc. +digilent Diglent, Inc. +dioo Dioo Microcircuit Co., Ltd +dlc DLC Display Co., Ltd. +dlg Dialog Semiconductor +dlink D-Link Corporation +dmo Data Modul AG +domintech Domintech Co., Ltd. +dongwoon Dongwoon Anatech +dptechnics DPTechnics +dragino Dragino Technology Co., Limited +ea Embedded Artists AB +ebs-systart EBS-SYSTART GmbH +ebv EBV Elektronik +eckelmann Eckelmann AG +edt Emerging Display Technologies +eeti eGalax_eMPIA Technology Inc +elan Elan Microelectronic Corp. +elgin Elgin S/A. +embest Shenzhen Embest Technology Co., Ltd. +emlid Emlid, Ltd. +emmicro EM Microelectronic +emtrion emtrion GmbH +endless Endless Mobile, Inc. +energymicro Silicon Laboratories (formerly Energy Micro AS) +engicam Engicam S.r.l. +epcos EPCOS AG +epfl Ecole Polytechnique Fédérale de Lausanne +epson Seiko Epson Corp. +est ESTeem Wireless Modems +ettus NI Ettus Research +eukrea Eukréa Electromatique +everest Everest Semiconductor Co. Ltd. +everspin Everspin Technologies, Inc. +exar Exar Corporation +excito Excito +ezchip EZchip Semiconductor +facebook Facebook +fairphone Fairphone B.V. +faraday Faraday Technology Corporation +fastrax Fastrax Oy +fcs Fairchild Semiconductor +feiyang Shenzhen Fly Young Technology Co.,LTD. +firefly Firefly +focaltech FocalTech Systems Co.,Ltd +friendlyarm Guangzhou FriendlyARM Computer Tech Co., Ltd +fsl Freescale Semiconductor +fujitsu Fujitsu Ltd. +gateworks Gateworks Corporation +gcw Game Consoles Worldwide +ge General Electric Company +geekbuying GeekBuying +gef GE Fanuc Intelligent Platforms Embedded Systems, Inc. +GEFanuc GE Fanuc Intelligent Platforms Embedded Systems, Inc. +geniatech Geniatech, Inc. +giantec Giantec Semiconductor, Inc. +giantplus Giantplus Technology Co., Ltd. +globalscale Globalscale Technologies, Inc. +globaltop GlobalTop Technology, Inc. +gmt Global Mixed-mode Technology, Inc. +goodix Shenzhen Huiding Technology Co., Ltd. +google Google, Inc. +grinn Grinn +grmn Garmin Limited +gumstix Gumstix, Inc. +gw Gateworks Corporation +hannstar HannStar Display Corporation +haoyu Haoyu Microelectronic Co. Ltd. +hardkernel Hardkernel Co., Ltd +hideep HiDeep Inc. +himax Himax Technologies, Inc. +hisilicon Hisilicon Limited. +hit Hitachi Ltd. +hitex Hitex Development Tools +holt Holt Integrated Circuits, Inc. +honeywell Honeywell +hp Hewlett Packard +holtek Holtek Semiconductor, Inc. +hwacom HwaCom Systems Inc. +i2se I2SE GmbH +ibm International Business Machines (IBM) +icplus IC Plus Corp. +idt Integrated Device Technologies, Inc. +ifi Ingenieurburo Fur Ic-Technologie (I/F/I) +ilitek ILI Technology Corporation (ILITEK) +img Imagination Technologies Ltd. +infineon Infineon Technologies +inforce Inforce Computing +ingenic Ingenic Semiconductor +innolux Innolux Corporation +inside-secure INSIDE Secure +intel Intel Corporation +intercontrol Inter Control Group +invensense InvenSense Inc. +inversepath Inverse Path +iom Iomega Corporation +isee ISEE 2007 S.L. +isil Intersil +issi Integrated Silicon Solutions Inc. +itead ITEAD Intelligent Systems Co.Ltd +iwave iWave Systems Technologies Pvt. Ltd. +jdi Japan Display Inc. +jedec JEDEC Solid State Technology Association +jianda Jiandangjing Technology Co., Ltd. +karo Ka-Ro electronics GmbH +keithkoep Keith & Koep GmbH +keymile Keymile GmbH +khadas Khadas +kiebackpeter Kieback & Peter GmbH +kinetic Kinetic Technologies +kingdisplay King & Display Technology Co., Ltd. +kingnovel Kingnovel Technology Co., Ltd. +koe Kaohsiung Opto-Electronics Inc. +kosagi Sutajio Ko-Usagi PTE Ltd. +kyo Kyocera Corporation +lacie LaCie +laird Laird PLC +lantiq Lantiq Semiconductor +lattice Lattice Semiconductor +lego LEGO Systems A/S +lemaker Shenzhen LeMaker Technology Co., Ltd. +lenovo Lenovo Group Ltd. +lg LG Corporation +libretech Shenzhen Libre Technology Co., Ltd +licheepi Lichee Pi +linaro Linaro Limited +linksys Belkin International, Inc. (Linksys) +linux Linux-specific binding +linx Linx Technologies +lltc Linear Technology Corporation +logicpd Logic PD, Inc. +lsi LSI Corp. (LSI Logic) +lwn Liebherr-Werk Nenzing GmbH +macnica Macnica Americas +marvell Marvell Technology Group Ltd. +maxim Maxim Integrated Products +mbvl Mobiveil Inc. +mcube mCube +meas Measurement Specialties +mediatek MediaTek Inc. +megachips MegaChips +mele Shenzhen MeLE Digital Technology Ltd. +melexis Melexis N.V. +melfas MELFAS Inc. +mellanox Mellanox Technologies +memsic MEMSIC Inc. +merrii Merrii Technology Co., Ltd. +micrel Micrel Inc. +microchip Microchip Technology Inc. +microcrystal Micro Crystal AG +micron Micron Technology Inc. +mikroe MikroElektronika d.o.o. +minix MINIX Technology Ltd. +miramems MiraMEMS Sensing Technology Co., Ltd. +mitsubishi Mitsubishi Electric Corporation +mosaixtech Mosaix Technologies, Inc. +motorola Motorola, Inc. +moxa Moxa Inc. +mpl MPL AG +mqmaker mqmaker Inc. +mscc Microsemi Corporation +msi Micro-Star International Co. Ltd. +mti Imagination Technologies Ltd. (formerly MIPS Technologies Inc.) +multi-inno Multi-Inno Technology Co.,Ltd +mundoreader Mundo Reader S.L. +murata Murata Manufacturing Co., Ltd. +mxicy Macronix International Co., Ltd. +myir MYIR Tech Limited +national National Semiconductor +nec NEC LCD Technologies, Ltd. +neonode Neonode Inc. +netgear NETGEAR +netlogic Broadcom Corporation (formerly NetLogic Microsystems) +netron-dy Netron DY +netxeon Shenzhen Netxeon Technology CO., LTD +nexbox Nexbox +nextthing Next Thing Co. +newhaven Newhaven Display International +ni National Instruments +nintendo Nintendo +nlt NLT Technologies, Ltd. +nokia Nokia +nordic Nordic Semiconductor +novtech NovTech, Inc. +nutsboard NutsBoard +nuvoton Nuvoton Technology Corporation +nvd New Vision Display +nvidia NVIDIA +nxp NXP Semiconductors +okaya Okaya Electric America, Inc. +oki Oki Electric Industry Co., Ltd. +olimex OLIMEX Ltd. +olpc One Laptop Per Child +onion Onion Corporation +onnn ON Semiconductor Corp. +ontat On Tat Industrial Company +opalkelly Opal Kelly Incorporated +opencores OpenCores.org +openrisc OpenRISC.io +option Option NV +oranth Shenzhen Oranth Technology Co., Ltd. +ORCL Oracle Corporation +orisetech Orise Technology +ortustech Ortus Technology Co., Ltd. +ovti OmniVision Technologies +oxsemi Oxford Semiconductor, Ltd. +panasonic Panasonic Corporation +parade Parade Technologies Inc. +pda Precision Design Associates, Inc. +pericom Pericom Technology Inc. +pervasive Pervasive Displays, Inc. +phicomm PHICOMM Co., Ltd. +phytec PHYTEC Messtechnik GmbH +picochip Picochip Ltd +pine64 Pine64 +pixcir PIXCIR MICROELECTRONICS Co., Ltd +plantower Plantower Co., Ltd +plathome Plat'Home Co., Ltd. +plda PLDA +plx Broadcom Corporation (formerly PLX Technology) +pni PNI Sensor Corporation +portwell Portwell Inc. +poslab Poslab Technology Co., Ltd. +powervr PowerVR (deprecated, use img) +probox2 PROBOX2 (by W2COMP Co., Ltd.) +pulsedlight PulsedLight, Inc +qca Qualcomm Atheros, Inc. +qcom Qualcomm Technologies, Inc +qemu QEMU, a generic and open source machine emulator and virtualizer +qi Qi Hardware +qiaodian QiaoDian XianShi Corporation +qnap QNAP Systems, Inc. +radxa Radxa +raidsonic RaidSonic Technology GmbH +ralink Mediatek/Ralink Technology Corp. +ramtron Ramtron International +raspberrypi Raspberry Pi Foundation +raydium Raydium Semiconductor Corp. +rda Unisoc Communications, Inc. +realtek Realtek Semiconductor Corp. +renesas Renesas Electronics Corporation +richtek Richtek Technology Corporation +ricoh Ricoh Co. Ltd. +rikomagic Rikomagic Tech Corp. Ltd +riscv RISC-V Foundation +rockchip Fuzhou Rockchip Electronics Co., Ltd +rohm ROHM Semiconductor Co., Ltd +roofull Shenzhen Roofull Technology Co, Ltd +samsung Samsung Semiconductor +samtec Samtec/Softing company +sancloud Sancloud Ltd +sandisk Sandisk Corporation +sbs Smart Battery System +schindler Schindler +seagate Seagate Technology PLC +semtech Semtech Corporation +sensirion Sensirion AG +sff Small Form Factor Committee +sgd Solomon Goldentek Display Corporation +sgx SGX Sensortech +sharp Sharp Corporation +shimafuji Shimafuji Electric, Inc. +si-en Si-En Technology Ltd. +sifive SiFive, Inc. +sigma Sigma Designs, Inc. +sii Seiko Instruments, Inc. +sil Silicon Image +silabs Silicon Laboratories +silead Silead Inc. +silergy Silergy Corp. +siliconmitus Silicon Mitus, Inc. +simtek +sirf SiRF Technology, Inc. +sis Silicon Integrated Systems Corp. +sitronix Sitronix Technology Corporation +skyworks Skyworks Solutions, Inc. +smsc Standard Microsystems Corporation +snps Synopsys, Inc. +socionext Socionext Inc. +solidrun SolidRun +solomon Solomon Systech Limited +sony Sony Corporation +spansion Spansion Inc. +sprd Spreadtrum Communications Inc. +sst Silicon Storage Technology, Inc. +st STMicroelectronics +starry Starry Electronic Technology (ShenZhen) Co., LTD +startek Startek +ste ST-Ericsson +stericsson ST-Ericsson +summit Summit microelectronics +sunchip Shenzhen Sunchip Technology Co., Ltd +SUNW Sun Microsystems, Inc +swir Sierra Wireless +syna Synaptics Inc. +synology Synology, Inc. +tbs TBS Technologies +tbs-biometrics Touchless Biometric Systems AG +tcg Trusted Computing Group +tcl Toby Churchill Ltd. +technexion TechNexion +technologic Technologic Systems +tempo Tempo Semiconductor +techstar Shenzhen Techstar Electronics Co., Ltd. +terasic Terasic Inc. +thine THine Electronics, Inc. +ti Texas Instruments +tianma Tianma Micro-electronics Co., Ltd. +tlm Trusted Logic Mobility +tmt Tecon Microprocessor Technologies, LLC. +topeet Topeet +toradex Toradex AG +toshiba Toshiba Corporation +toumaz Toumaz +tpk TPK U.S.A. LLC +tplink TP-LINK Technologies Co., Ltd. +tpo TPO +tronfy Tronfy +tronsmart Tronsmart +truly Truly Semiconductors Limited +tsd Theobroma Systems Design und Consulting GmbH +tyan Tyan Computer Corporation +u-blox u-blox +ucrobotics uCRobotics +ubnt Ubiquiti Networks +udoo Udoo +uniwest United Western Technologies Corp (UniWest) +upisemi uPI Semiconductor Corp. +urt United Radiant Technology Corporation +usi Universal Scientific Industrial Co., Ltd. +v3 V3 Semiconductor +vamrs Vamrs Ltd. +variscite Variscite Ltd. +via VIA Technologies, Inc. +virtio Virtual I/O Device Specification, developed by the OASIS consortium +vishay Vishay Intertechnology, Inc +vitesse Vitesse Semiconductor Corporation +vivante Vivante Corporation +vocore VoCore Studio +voipac Voipac Technologies s.r.o. +vot Vision Optical Technology Co., Ltd. +wd Western Digital Corp. +wetek WeTek Electronics, limited. +wexler Wexler +whwave Shenzhen whwave Electronics, Inc. +wi2wi Wi2Wi, Inc. +winbond Winbond Electronics corp. +winstar Winstar Display Corp. +wlf Wolfson Microelectronics +wm Wondermedia Technologies, Inc. +x-powers X-Powers +xes Extreme Engineering Solutions (X-ES) +xillybus Xillybus Ltd. +xlnx Xilinx +xunlong Shenzhen Xunlong Software CO.,Limited +ysoft Y Soft Corporation a.s. +zarlink Zarlink Semiconductor +zeitec ZEITEC Semiconductor Co., LTD. +zidoo Shenzhen Zidoo Technology Co., Ltd. +zii Zodiac Inflight Innovations +zte ZTE Corp. +zyxel ZyXEL Communications Corp. diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml index ed7fec614473..0545e382fcbf 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -231,6 +231,8 @@ patternProperties: description: Bitmain Technologies "^blaize,.*": description: Blaize, Inc. + "^blokas(labs)?,.*": + description: Vilniaus Blokas UAB "^bluegiga,.*": description: Bluegiga Technologies Ltd. "^blutek,.*": diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index b1425aed6593..5df03f1dbc9b 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -106,6 +106,12 @@ config COMMON_CLK_HI655X multi-function device has one fixed-rate oscillator, clocked at 32KHz. +config COMMON_CLK_HIFIBERRY_DACPLUSHD + tristate + +config COMMON_CLK_HIFIBERRY_DACPRO + tristate + config COMMON_CLK_SCMI tristate "Clock driver controlled via SCMI interface" depends on ARM_SCMI_PROTOCOL || COMPILE_TEST diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 18ed29cfdc11..2f1b2f0327a4 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -72,6 +72,8 @@ obj-$(CONFIG_COMMON_CLK_LAN966X) += clk-lan966x.o obj-$(CONFIG_COMMON_CLK_LOCHNAGAR) += clk-lochnagar.o obj-$(CONFIG_MACH_LOONGSON32) += clk-loongson1.o obj-$(CONFIG_COMMON_CLK_LOONGSON2) += clk-loongson2.o +obj-$(CONFIG_COMMON_CLK_HIFIBERRY_DACPRO) += clk-hifiberry-dacpro.o +obj-$(CONFIG_COMMON_CLK_HIFIBERRY_DACPLUSHD) += clk-hifiberry-dachd.o obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o obj-$(CONFIG_COMMON_CLK_MAX9485) += clk-max9485.o obj-$(CONFIG_ARCH_MILBEAUT_M10V) += clk-milbeaut.o diff --git a/drivers/clk/clk-hifiberry-dachd.c b/drivers/clk/clk-hifiberry-dachd.c new file mode 100644 index 000000000000..5280b5100559 --- /dev/null +++ b/drivers/clk/clk-hifiberry-dachd.c @@ -0,0 +1,331 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Clock Driver for HiFiBerry DAC+ HD + * + * Author: Joerg Schambacher, i2Audio GmbH for HiFiBerry + * Copyright 2020 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define NO_PLL_RESET 0 +#define PLL_RESET 1 +#define HIFIBERRY_PLL_MAX_REGISTER 256 +#define DEFAULT_RATE 44100 + +static struct reg_default hifiberry_pll_reg_defaults[] = { + {0x02, 0x53}, {0x03, 0x00}, {0x07, 0x20}, {0x0F, 0x00}, + {0x10, 0x0D}, {0x11, 0x1D}, {0x12, 0x0D}, {0x13, 0x8C}, + {0x14, 0x8C}, {0x15, 0x8C}, {0x16, 0x8C}, {0x17, 0x8C}, + {0x18, 0x2A}, {0x1C, 0x00}, {0x1D, 0x0F}, {0x1F, 0x00}, + {0x2A, 0x00}, {0x2C, 0x00}, {0x2F, 0x00}, {0x30, 0x00}, + {0x31, 0x00}, {0x32, 0x00}, {0x34, 0x00}, {0x37, 0x00}, + {0x38, 0x00}, {0x39, 0x00}, {0x3A, 0x00}, {0x3B, 0x01}, + {0x3E, 0x00}, {0x3F, 0x00}, {0x40, 0x00}, {0x41, 0x00}, + {0x5A, 0x00}, {0x5B, 0x00}, {0x95, 0x00}, {0x96, 0x00}, + {0x97, 0x00}, {0x98, 0x00}, {0x99, 0x00}, {0x9A, 0x00}, + {0x9B, 0x00}, {0xA2, 0x00}, {0xA3, 0x00}, {0xA4, 0x00}, + {0xB7, 0x92}, + {0x1A, 0x3D}, {0x1B, 0x09}, {0x1E, 0xF3}, {0x20, 0x13}, + {0x21, 0x75}, {0x2B, 0x04}, {0x2D, 0x11}, {0x2E, 0xE0}, + {0x3D, 0x7A}, + {0x35, 0x9D}, {0x36, 0x00}, {0x3C, 0x42}, + { 177, 0xAC}, +}; +static struct reg_default common_pll_regs[HIFIBERRY_PLL_MAX_REGISTER]; +static int num_common_pll_regs; +static struct reg_default dedicated_192k_pll_regs[HIFIBERRY_PLL_MAX_REGISTER]; +static int num_dedicated_192k_pll_regs; +static struct reg_default dedicated_96k_pll_regs[HIFIBERRY_PLL_MAX_REGISTER]; +static int num_dedicated_96k_pll_regs; +static struct reg_default dedicated_48k_pll_regs[HIFIBERRY_PLL_MAX_REGISTER]; +static int num_dedicated_48k_pll_regs; +static struct reg_default dedicated_176k4_pll_regs[HIFIBERRY_PLL_MAX_REGISTER]; +static int num_dedicated_176k4_pll_regs; +static struct reg_default dedicated_88k2_pll_regs[HIFIBERRY_PLL_MAX_REGISTER]; +static int num_dedicated_88k2_pll_regs; +static struct reg_default dedicated_44k1_pll_regs[HIFIBERRY_PLL_MAX_REGISTER]; +static int num_dedicated_44k1_pll_regs; + +/** + * struct clk_hifiberry_drvdata - Common struct to the HiFiBerry DAC HD Clk + * @hw: clk_hw for the common clk framework + */ +struct clk_hifiberry_drvdata { + struct regmap *regmap; + struct clk *clk; + struct clk_hw hw; + unsigned long rate; +}; + +#define to_hifiberry_clk(_hw) \ + container_of(_hw, struct clk_hifiberry_drvdata, hw) + +static int clk_hifiberry_dachd_write_pll_regs(struct regmap *regmap, + struct reg_default *regs, + int num, int do_pll_reset) +{ + int i; + int ret = 0; + char pll_soft_reset[] = { 177, 0xAC, }; + + for (i = 0; i < num; i++) { + ret |= regmap_write(regmap, regs[i].reg, regs[i].def); + if (ret) + return ret; + } + if (do_pll_reset) { + ret |= regmap_write(regmap, pll_soft_reset[0], + pll_soft_reset[1]); + mdelay(10); + } + return ret; +} + +static unsigned long clk_hifiberry_dachd_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + return to_hifiberry_clk(hw)->rate; +} + +static long clk_hifiberry_dachd_round_rate(struct clk_hw *hw, + unsigned long rate, unsigned long *parent_rate) +{ + return rate; +} + +static int clk_hifiberry_dachd_set_rate(struct clk_hw *hw, + unsigned long rate, unsigned long parent_rate) +{ + int ret; + struct clk_hifiberry_drvdata *drvdata = to_hifiberry_clk(hw); + + switch (rate) { + case 44100: + ret = clk_hifiberry_dachd_write_pll_regs(drvdata->regmap, + dedicated_44k1_pll_regs, num_dedicated_44k1_pll_regs, + PLL_RESET); + break; + case 88200: + ret = clk_hifiberry_dachd_write_pll_regs(drvdata->regmap, + dedicated_88k2_pll_regs, num_dedicated_88k2_pll_regs, + PLL_RESET); + break; + case 176400: + ret = clk_hifiberry_dachd_write_pll_regs(drvdata->regmap, + dedicated_176k4_pll_regs, num_dedicated_176k4_pll_regs, + PLL_RESET); + break; + case 48000: + ret = clk_hifiberry_dachd_write_pll_regs(drvdata->regmap, + dedicated_48k_pll_regs, num_dedicated_48k_pll_regs, + PLL_RESET); + break; + case 96000: + ret = clk_hifiberry_dachd_write_pll_regs(drvdata->regmap, + dedicated_96k_pll_regs, num_dedicated_96k_pll_regs, + PLL_RESET); + break; + case 192000: + ret = clk_hifiberry_dachd_write_pll_regs(drvdata->regmap, + dedicated_192k_pll_regs, num_dedicated_192k_pll_regs, + PLL_RESET); + break; + default: + ret = -EINVAL; + break; + } + to_hifiberry_clk(hw)->rate = rate; + + return ret; +} + +const struct clk_ops clk_hifiberry_dachd_rate_ops = { + .recalc_rate = clk_hifiberry_dachd_recalc_rate, + .round_rate = clk_hifiberry_dachd_round_rate, + .set_rate = clk_hifiberry_dachd_set_rate, +}; + +static int clk_hifiberry_get_prop_values(struct device *dev, + char *prop_name, + struct reg_default *regs) +{ + int ret; + int i; + u8 tmp[2 * HIFIBERRY_PLL_MAX_REGISTER]; + + ret = of_property_read_variable_u8_array(dev->of_node, prop_name, + tmp, 0, 2 * HIFIBERRY_PLL_MAX_REGISTER); + if (ret < 0) + return ret; + if (ret & 1) { + dev_err(dev, + "%s <%s> -> #%i odd number of bytes for reg/val pairs!", + __func__, + prop_name, + ret); + return -EINVAL; + } + ret /= 2; + for (i = 0; i < ret; i++) { + regs[i].reg = (u32)tmp[2 * i]; + regs[i].def = (u32)tmp[2 * i + 1]; + } + return ret; +} + + +static int clk_hifiberry_dachd_dt_parse(struct device *dev) +{ + num_common_pll_regs = clk_hifiberry_get_prop_values(dev, + "common_pll_regs", common_pll_regs); + num_dedicated_44k1_pll_regs = clk_hifiberry_get_prop_values(dev, + "44k1_pll_regs", dedicated_44k1_pll_regs); + num_dedicated_88k2_pll_regs = clk_hifiberry_get_prop_values(dev, + "88k2_pll_regs", dedicated_88k2_pll_regs); + num_dedicated_176k4_pll_regs = clk_hifiberry_get_prop_values(dev, + "176k4_pll_regs", dedicated_176k4_pll_regs); + num_dedicated_48k_pll_regs = clk_hifiberry_get_prop_values(dev, + "48k_pll_regs", dedicated_48k_pll_regs); + num_dedicated_96k_pll_regs = clk_hifiberry_get_prop_values(dev, + "96k_pll_regs", dedicated_96k_pll_regs); + num_dedicated_192k_pll_regs = clk_hifiberry_get_prop_values(dev, + "192k_pll_regs", dedicated_192k_pll_regs); + return 0; +} + + +static int clk_hifiberry_dachd_remove(struct device *dev) +{ + of_clk_del_provider(dev->of_node); + return 0; +} + +const struct regmap_config hifiberry_pll_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = HIFIBERRY_PLL_MAX_REGISTER, + .reg_defaults = hifiberry_pll_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(hifiberry_pll_reg_defaults), + .cache_type = REGCACHE_RBTREE, +}; +EXPORT_SYMBOL_GPL(hifiberry_pll_regmap); + + +static int clk_hifiberry_dachd_i2c_probe(struct i2c_client *i2c) +{ + struct clk_hifiberry_drvdata *hdclk; + int ret = 0; + struct clk_init_data init; + struct device *dev = &i2c->dev; + struct device_node *dev_node = dev->of_node; + struct regmap_config config = hifiberry_pll_regmap; + + hdclk = devm_kzalloc(&i2c->dev, + sizeof(struct clk_hifiberry_drvdata), GFP_KERNEL); + if (!hdclk) + return -ENOMEM; + + i2c_set_clientdata(i2c, hdclk); + + hdclk->regmap = devm_regmap_init_i2c(i2c, &config); + + if (IS_ERR(hdclk->regmap)) + return PTR_ERR(hdclk->regmap); + + /* start PLL to allow detection of DAC */ + ret = clk_hifiberry_dachd_write_pll_regs(hdclk->regmap, + hifiberry_pll_reg_defaults, + ARRAY_SIZE(hifiberry_pll_reg_defaults), + PLL_RESET); + if (ret) + return ret; + + clk_hifiberry_dachd_dt_parse(dev); + + /* restart PLL with configs from DTB */ + ret = clk_hifiberry_dachd_write_pll_regs(hdclk->regmap, common_pll_regs, + num_common_pll_regs, PLL_RESET); + if (ret) + return ret; + + init.name = "clk-hifiberry-dachd"; + init.ops = &clk_hifiberry_dachd_rate_ops; + init.flags = 0; + init.parent_names = NULL; + init.num_parents = 0; + + hdclk->hw.init = &init; + + hdclk->clk = devm_clk_register(dev, &hdclk->hw); + if (IS_ERR(hdclk->clk)) { + dev_err(dev, "unable to register %s\n", init.name); + return PTR_ERR(hdclk->clk); + } + + ret = of_clk_add_provider(dev_node, of_clk_src_simple_get, hdclk->clk); + if (ret != 0) { + dev_err(dev, "Cannot of_clk_add_provider"); + return ret; + } + + ret = clk_set_rate(hdclk->hw.clk, DEFAULT_RATE); + if (ret != 0) { + dev_err(dev, "Cannot set rate : %d\n", ret); + return -EINVAL; + } + + return ret; +} + +static void clk_hifiberry_dachd_i2c_remove(struct i2c_client *i2c) +{ + clk_hifiberry_dachd_remove(&i2c->dev); +} + +static const struct i2c_device_id clk_hifiberry_dachd_i2c_id[] = { + { "dachd-clk", }, + { } +}; +MODULE_DEVICE_TABLE(i2c, clk_hifiberry_dachd_i2c_id); + +static const struct of_device_id clk_hifiberry_dachd_of_match[] = { + { .compatible = "hifiberry,dachd-clk", }, + { } +}; +MODULE_DEVICE_TABLE(of, clk_hifiberry_dachd_of_match); + +static struct i2c_driver clk_hifiberry_dachd_i2c_driver = { + .probe = clk_hifiberry_dachd_i2c_probe, + .remove = clk_hifiberry_dachd_i2c_remove, + .id_table = clk_hifiberry_dachd_i2c_id, + .driver = { + .name = "dachd-clk", + .of_match_table = of_match_ptr(clk_hifiberry_dachd_of_match), + }, +}; + +module_i2c_driver(clk_hifiberry_dachd_i2c_driver); + + +MODULE_DESCRIPTION("HiFiBerry DAC+ HD clock driver"); +MODULE_AUTHOR("Joerg Schambacher "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:clk-hifiberry-dachd"); diff --git a/drivers/clk/clk-hifiberry-dacpro.c b/drivers/clk/clk-hifiberry-dacpro.c new file mode 100644 index 000000000000..0cbc0349f9f6 --- /dev/null +++ b/drivers/clk/clk-hifiberry-dacpro.c @@ -0,0 +1,180 @@ +/* + * Clock Driver for HiFiBerry DAC Pro + * + * Author: Stuart MacLean + * Copyright 2015 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include + +struct ext_clk_rates { + /* Clock rate of CLK44EN attached to GPIO6 pin */ + unsigned long clk_44en; + /* Clock rate of CLK48EN attached to GPIO3 pin */ + unsigned long clk_48en; +}; + +/** + * struct hifiberry_dacpro_clk - Common struct to the HiFiBerry DAC Pro + * @hw: clk_hw for the common clk framework + * @mode: 0 => CLK44EN, 1 => CLK48EN + */ +struct clk_hifiberry_hw { + struct clk_hw hw; + uint8_t mode; + struct ext_clk_rates clk_rates; +}; + +#define to_hifiberry_clk(_hw) container_of(_hw, struct clk_hifiberry_hw, hw) + +static const struct ext_clk_rates hifiberry_dacpro_clks = { + .clk_44en = 22579200UL, + .clk_48en = 24576000UL, +}; + +static const struct ext_clk_rates allo_dac_clks = { + .clk_44en = 45158400UL, + .clk_48en = 49152000UL, +}; + +static const struct of_device_id clk_hifiberry_dacpro_dt_ids[] = { + { .compatible = "hifiberry,dacpro-clk", &hifiberry_dacpro_clks }, + { .compatible = "allo,dac-clk", &allo_dac_clks }, + { } +}; +MODULE_DEVICE_TABLE(of, clk_hifiberry_dacpro_dt_ids); + +static unsigned long clk_hifiberry_dacpro_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk_hifiberry_hw *clk = to_hifiberry_clk(hw); + return (clk->mode == 0) ? clk->clk_rates.clk_44en : + clk->clk_rates.clk_48en; +} + +static long clk_hifiberry_dacpro_round_rate(struct clk_hw *hw, + unsigned long rate, unsigned long *parent_rate) +{ + struct clk_hifiberry_hw *clk = to_hifiberry_clk(hw); + long actual_rate; + + if (rate <= clk->clk_rates.clk_44en) { + actual_rate = (long)clk->clk_rates.clk_44en; + } else if (rate >= clk->clk_rates.clk_48en) { + actual_rate = (long)clk->clk_rates.clk_48en; + } else { + long diff44Rate = (long)(rate - clk->clk_rates.clk_44en); + long diff48Rate = (long)(clk->clk_rates.clk_48en - rate); + + if (diff44Rate < diff48Rate) + actual_rate = (long)clk->clk_rates.clk_44en; + else + actual_rate = (long)clk->clk_rates.clk_48en; + } + return actual_rate; +} + + +static int clk_hifiberry_dacpro_set_rate(struct clk_hw *hw, + unsigned long rate, unsigned long parent_rate) +{ + struct clk_hifiberry_hw *clk = to_hifiberry_clk(hw); + unsigned long actual_rate; + + actual_rate = (unsigned long)clk_hifiberry_dacpro_round_rate(hw, rate, + &parent_rate); + clk->mode = (actual_rate == clk->clk_rates.clk_44en) ? 0 : 1; + return 0; +} + + +const struct clk_ops clk_hifiberry_dacpro_rate_ops = { + .recalc_rate = clk_hifiberry_dacpro_recalc_rate, + .round_rate = clk_hifiberry_dacpro_round_rate, + .set_rate = clk_hifiberry_dacpro_set_rate, +}; + +static int clk_hifiberry_dacpro_probe(struct platform_device *pdev) +{ + const struct of_device_id *of_id; + struct clk_hifiberry_hw *proclk; + struct clk *clk; + struct device *dev; + struct clk_init_data init; + int ret; + + dev = &pdev->dev; + of_id = of_match_node(clk_hifiberry_dacpro_dt_ids, dev->of_node); + if (!of_id) + return -EINVAL; + + proclk = kzalloc(sizeof(struct clk_hifiberry_hw), GFP_KERNEL); + if (!proclk) + return -ENOMEM; + + init.name = "clk-hifiberry-dacpro"; + init.ops = &clk_hifiberry_dacpro_rate_ops; + init.flags = 0; + init.parent_names = NULL; + init.num_parents = 0; + + proclk->mode = 0; + proclk->hw.init = &init; + memcpy(&proclk->clk_rates, of_id->data, sizeof(proclk->clk_rates)); + + clk = devm_clk_register(dev, &proclk->hw); + if (!IS_ERR(clk)) { + ret = of_clk_add_provider(dev->of_node, of_clk_src_simple_get, + clk); + } else { + dev_err(dev, "Fail to register clock driver\n"); + kfree(proclk); + ret = PTR_ERR(clk); + } + return ret; +} + +static void clk_hifiberry_dacpro_remove(struct platform_device *pdev) +{ + of_clk_del_provider(pdev->dev.of_node); +} + +static struct platform_driver clk_hifiberry_dacpro_driver = { + .probe = clk_hifiberry_dacpro_probe, + .remove = clk_hifiberry_dacpro_remove, + .driver = { + .name = "clk-hifiberry-dacpro", + .of_match_table = clk_hifiberry_dacpro_dt_ids, + }, +}; + +static int __init clk_hifiberry_dacpro_init(void) +{ + return platform_driver_register(&clk_hifiberry_dacpro_driver); +} +core_initcall(clk_hifiberry_dacpro_init); + +static void __exit clk_hifiberry_dacpro_exit(void) +{ + platform_driver_unregister(&clk_hifiberry_dacpro_driver); +} +module_exit(clk_hifiberry_dacpro_exit); + +MODULE_DESCRIPTION("HiFiBerry DAC Pro clock driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:clk-hifiberry-dacpro"); diff --git a/sound/drivers/Kconfig b/sound/drivers/Kconfig index 6debd8e95cb7..d6fd0c6be56a 100644 --- a/sound/drivers/Kconfig +++ b/sound/drivers/Kconfig @@ -263,4 +263,23 @@ config SND_AC97_POWER_SAVE_DEFAULT See SND_AC97_POWER_SAVE for more details. +config SND_PIMIDI + tristate "Pimidi driver" + depends on SND_SEQUENCER && CRC8 + select SND_RAWMIDI + help + Say Y here to include support for Blokas Pimidi. + + To compile this driver as a module, choose M here: the module + will be called snd-pimidi. + +config SND_PISOUND_MICRO + tristate "Pisound Micro driver" + depends on SND_SEQUENCER && CRC8 + help + Say Y here to include support for Blokas Pisound Micro. + + To compile this driver as modules, choose M here: the modules + will be called snd-soc-upisnd-ctrl and snd-soc-upisnd-codec. + endif # SND_DRIVERS diff --git a/sound/drivers/Makefile b/sound/drivers/Makefile index a08bdd70ec9c..1cce9943ddef 100644 --- a/sound/drivers/Makefile +++ b/sound/drivers/Makefile @@ -9,6 +9,7 @@ snd-aloop-y := aloop.o snd-mtpav-y := mtpav.o snd-mts64-y := mts64.o snd-pcmtest-y := pcmtest.o +snd-pimidi-y := pimidi.o snd-portman2x4-y := portman2x4.o snd-serial-u16550-y := serial-u16550.o snd-serial-generic-y := serial-generic.o @@ -23,6 +24,7 @@ obj-$(CONFIG_SND_SERIAL_U16550) += snd-serial-u16550.o obj-$(CONFIG_SND_SERIAL_GENERIC) += snd-serial-generic.o obj-$(CONFIG_SND_MTPAV) += snd-mtpav.o obj-$(CONFIG_SND_MTS64) += snd-mts64.o +obj-$(CONFIG_SND_PIMIDI) += snd-pimidi.o obj-$(CONFIG_SND_PORTMAN2X4) += snd-portman2x4.o -obj-$(CONFIG_SND) += opl3/ opl4/ mpu401/ vx/ pcsp/ +obj-$(CONFIG_SND) += opl3/ opl4/ mpu401/ vx/ pcsp/ upisnd/ diff --git a/sound/drivers/pimidi.c b/sound/drivers/pimidi.c new file mode 100644 index 000000000000..c399707425b7 --- /dev/null +++ b/sound/drivers/pimidi.c @@ -0,0 +1,1113 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Pimidi Linux kernel module. + * Copyright (C) 2017-2024 Vilniaus Blokas UAB, https://blokas.io/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 of the + * License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define PIMIDI_LOG_IMPL(instance, log_func, msg, ...) log_func("pimidi(%s)[%c]: " msg "\n", \ + __func__, (instance) ? (instance)->d + '0' : 'G', ## __VA_ARGS__) + +#ifdef PIMIDI_DEBUG +# define printd(instance, ...) PIMIDI_LOG_IMPL(instance, pr_alert, __VA_ARGS__) +# define printd_rl(instance, ...) PIMIDI_LOG_IMPL(instance, pr_alert_ratelimited, __VA_ARGS__) +# define printd_g(...) printd((struct pimidi_instance *)NULL, __VA_ARGS__) +#else +# define printd(instance, ...) do {} while (0) +# define printd_rl(instance, ...) do {} while (0) +# define printd_g(...) do {} while (0) +#endif + +#define printe(instance, ...) PIMIDI_LOG_IMPL(instance, pr_err, __VA_ARGS__) +#define printe_rl(instance, ...) PIMIDI_LOG_IMPL(instance, pr_err_ratelimited, __VA_ARGS__) +#define printi(instance, ...) PIMIDI_LOG_IMPL(instance, pr_info, __VA_ARGS__) +#define printw(instance, ...) PIMIDI_LOG_IMPL(instance, pr_warn, __VA_ARGS__) +#define printw_rl(instance, ...) PIMIDI_LOG_IMPL(instance, pr_warn_ratelimited, __VA_ARGS__) + +#define printe_g(...) printe((struct pimidi_instance *)NULL, __VA_ARGS__) +#define printi_g(...) printi((struct pimidi_instance *)NULL, __VA_ARGS__) + +DECLARE_CRC8_TABLE(pimidi_crc8_table); +enum { PIMIDI_CRC8_POLYNOMIAL = 0x83 }; +enum { PIMIDI_MAX_DEVICES = 4 }; +enum { PIMIDI_MAX_PACKET_SIZE = 17 }; +enum { PIMIDI_PORTS = 2 }; + +struct pimidi_shared { + // lock protects the shared reset_gpio and devices list. + struct mutex lock; + struct gpio_desc *reset_gpio; + struct workqueue_struct *work_queue; + struct list_head devices; +}; + +static struct pimidi_shared pimidi_global = { + .devices = LIST_HEAD_INIT(pimidi_global.devices), +}; + +struct pimidi_version_t { + u8 hwrev; + u8 major; + u8 minor; + u8 build; +}; + +enum { PIMIDI_IN_FIFO_SIZE = 4096 }; + +struct pimidi_midi_port { + // in_lock protects the input substream. + struct mutex in_lock; + // out_lock protects the output substream. + struct mutex out_lock; + DECLARE_KFIFO(in_fifo, uint8_t, PIMIDI_IN_FIFO_SIZE); + unsigned int last_output_at; + unsigned int output_buffer_used_in_millibytes; + struct work_struct in_handler; + struct delayed_work out_handler; + unsigned long enabled_streams; + unsigned int tx_cnt; + unsigned int rx_cnt; +}; + +struct pimidi_instance { + struct list_head list; + struct i2c_client *i2c_client; + struct pimidi_version_t version; + char serial[11]; + char d; + struct gpio_desc *data_ready_gpio; + + struct work_struct drdy_handler; + + // comm_lock serializes I2C communication. + struct mutex comm_lock; + char *rx_buf; + size_t rx_len; + int rx_status; + struct completion *rx_completion; + + struct snd_rawmidi *rawmidi; + struct pimidi_midi_port midi_port[PIMIDI_PORTS]; + bool stopping; +}; + +static struct snd_rawmidi_substream *pimidi_find_substream(struct snd_rawmidi *rawmidi, + int stream, + int number + ) +{ + struct snd_rawmidi_substream *substream; + + list_for_each_entry(substream, &rawmidi->streams[stream].substreams, list) { + if (substream->number == number) + return substream; + } + return NULL; +} + +static void pimidi_midi_in_handler(struct pimidi_instance *instance, int port) +{ + int i, n, err; + + printd(instance, "(%d)", port); + + struct pimidi_midi_port *midi_port = &instance->midi_port[port]; + + if (!test_bit(SNDRV_RAWMIDI_STREAM_INPUT, &midi_port->enabled_streams)) { + printd(instance, "Input not enabled for %d", port); + return; + } + + u8 data[512]; + + n = kfifo_out_peek(&midi_port->in_fifo, data, sizeof(data)); + printd(instance, "Peeked %d MIDI bytes", n); + + mutex_lock(&midi_port->in_lock); + struct snd_rawmidi_substream *substream = + pimidi_find_substream(instance->rawmidi, + SNDRV_RAWMIDI_STREAM_INPUT, + port); + + err = snd_rawmidi_receive(substream, data, n); + if (err > 0) + midi_port->rx_cnt += err; + mutex_unlock(&midi_port->in_lock); + + for (i = 0; i < err; ++i) + kfifo_skip(&midi_port->in_fifo); + + if (n != err) + printw_rl(instance, + "Not all MIDI data consumed for port %d: %d / %d", port, err, n); + + if (!kfifo_is_empty(&midi_port->in_fifo) && !instance->stopping) + queue_work(pimidi_global.work_queue, &midi_port->in_handler); + + printd(instance, "Done"); +} + +static void pimidi_midi_in_handler_0(struct work_struct *work) +{ + pimidi_midi_in_handler(container_of(work, struct pimidi_instance, midi_port[0].in_handler), + 0); +} + +static void pimidi_midi_in_handler_1(struct work_struct *work) +{ + pimidi_midi_in_handler(container_of(work, struct pimidi_instance, midi_port[1].in_handler), + 1); +} + +static void pimidi_midi_out_handler(struct pimidi_instance *instance, int port) +{ + printd(instance, "(%d)", port); + if (!test_bit(SNDRV_RAWMIDI_STREAM_OUTPUT, &instance->midi_port[port].enabled_streams)) { + printd(instance, "Output not enabled for %d", port); + return; + } + + struct pimidi_midi_port *midi_port = &instance->midi_port[port]; + + struct snd_rawmidi_substream *substream = + pimidi_find_substream(instance->rawmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, port); + + mutex_lock(&midi_port->out_lock); + + enum { MIDI_MILLI_BYTES_PER_JIFFY = 3125000 / HZ }; + enum { MIDI_MAX_OUTPUT_BUFFER_SIZE_IN_MILLIBYTES = + (512 - PIMIDI_MAX_PACKET_SIZE - 1) * 1000 }; + + unsigned int now = jiffies; + unsigned int millibytes_became_available = + (MIDI_MILLI_BYTES_PER_JIFFY) * (now - midi_port->last_output_at); + + midi_port->output_buffer_used_in_millibytes = + midi_port->output_buffer_used_in_millibytes <= + millibytes_became_available ? 0 : midi_port->output_buffer_used_in_millibytes - + millibytes_became_available; + + unsigned int output_buffer_available = + (MIDI_MAX_OUTPUT_BUFFER_SIZE_IN_MILLIBYTES + - midi_port->output_buffer_used_in_millibytes) + / 1000; + + u8 buffer[PIMIDI_MAX_PACKET_SIZE]; + int n, batch, err; + + for (batch = 0; batch < 3; ++batch) { + if (output_buffer_available == 0) + printd(instance, "Buffer full"); + + printd(instance, "Buffer available: %u (%u +%u, %u -> %u, dt %u) (%u) @ %u", + output_buffer_available, midi_port->output_buffer_used_in_millibytes, + millibytes_became_available, midi_port->last_output_at, now, + now - midi_port->last_output_at, midi_port->tx_cnt, HZ); + midi_port->last_output_at = now; + + n = output_buffer_available + ? snd_rawmidi_transmit_peek(substream, buffer + 1, + min(output_buffer_available, + sizeof(buffer) - 2)) + : 0; + if (n > 0) { + printd(instance, "Peeked: %d", n); + snd_rawmidi_transmit_ack(substream, n); + + buffer[0] = (port << 4) | n; + buffer[n + 1] = ~crc8(pimidi_crc8_table, buffer, n + 1, CRC8_INIT_VALUE); + +#ifdef PIMIDI_DEBUG + pr_debug("%s[%d]: Sending %d bytes:", __func__, instance->d, n + 2); + int i; + + for (i = 0; i < n + 2; ++i) + pr_cont(" %02x", buffer[i]); + + pr_cont("\n"); +#endif + mutex_lock(&instance->comm_lock); + err = i2c_master_send(instance->i2c_client, buffer, n + 2); + mutex_unlock(&instance->comm_lock); + + if (err < 0) { + printe(instance, + "Error occurred when sending MIDI data over I2C! (%d)", + err); + goto cleanup; + } + + midi_port->tx_cnt += n; + midi_port->output_buffer_used_in_millibytes += n * 1000; + output_buffer_available -= n; + } else if (n < 0) { + err = n; + printe(instance, "snd_rawmidi_transmit_peek returned error %d!", err); + goto cleanup; + } else { + break; + } + } + + printd(instance, "Checking if empty %p", substream); + if (!snd_rawmidi_transmit_empty(substream) && !instance->stopping) { + unsigned int delay = 1; + + if (output_buffer_available == 0) + delay = 125000 / MIDI_MILLI_BYTES_PER_JIFFY; + printd(instance, "Queue more work after %u jiffies", delay); + mod_delayed_work(pimidi_global.work_queue, &midi_port->out_handler, delay); + } + +cleanup: + mutex_unlock(&midi_port->out_lock); + printd(instance, "Done"); +} + +static void pimidi_midi_out_handler_0(struct work_struct *work) +{ + pimidi_midi_out_handler(container_of(work, struct pimidi_instance, + midi_port[0].out_handler.work), 0); +} + +static void pimidi_midi_out_handler_1(struct work_struct *work) +{ + pimidi_midi_out_handler(container_of(work, struct pimidi_instance, + midi_port[1].out_handler.work), 1); +} + +static void pimidi_midi_output_trigger(struct snd_rawmidi_substream *substream, int up) +{ + struct pimidi_instance *instance = substream->rmidi->private_data; + + printd(instance, "(%d, %d, %d)", substream->stream, substream->number, up); + + if (up == 0) { + clear_bit(substream->stream, + &instance->midi_port[substream->number].enabled_streams); + } else { + set_bit(substream->stream, + &instance->midi_port[substream->number].enabled_streams); + if (!delayed_work_pending(&instance->midi_port[substream->number].out_handler)) { + printd(instance, "Queueing work"); + queue_delayed_work(pimidi_global.work_queue, + &instance->midi_port[substream->number].out_handler, 0); + } + } +} + +static void pimidi_midi_output_drain(struct snd_rawmidi_substream *substream) +{ + struct pimidi_instance *instance = substream->rmidi->private_data; + + printd(instance, "(%d, %d)", substream->stream, substream->number); + + printd(instance, "Begin draining!"); + + queue_delayed_work(pimidi_global.work_queue, + &instance->midi_port[substream->number].out_handler, 0); + + unsigned long deadline = jiffies + 5 * HZ; + + do { + printd(instance, "Before flush"); + while (delayed_work_pending(&instance->midi_port[substream->number].out_handler)) + flush_delayed_work(&instance->midi_port[substream->number].out_handler); + printd(instance, "Flushed"); + } while (!snd_rawmidi_transmit_empty(substream) && time_before(jiffies, deadline)); + + printd(instance, "Done!"); +} + +static int pimidi_midi_output_close(struct snd_rawmidi_substream *substream) +{ + struct pimidi_instance *instance = substream->rmidi->private_data; + struct pimidi_midi_port *midi_port = &instance->midi_port[substream->number]; + + mutex_lock(&midi_port->out_lock); + clear_bit(substream->stream, &midi_port->enabled_streams); + mutex_unlock(&midi_port->out_lock); + return 0; +} + +static int pimidi_midi_input_close(struct snd_rawmidi_substream *substream) +{ + struct pimidi_instance *instance = substream->rmidi->private_data; + struct pimidi_midi_port *midi_port = &instance->midi_port[substream->number]; + + mutex_lock(&midi_port->in_lock); + clear_bit(substream->stream, &midi_port->enabled_streams); + mutex_unlock(&midi_port->in_lock); + return 0; +} + +static void pimidi_midi_input_trigger(struct snd_rawmidi_substream *substream, int up) +{ + struct pimidi_instance *instance = substream->rmidi->private_data; + + printd(instance, "(%d, %d, %d)", substream->stream, substream->number, up); + + if (up == 0) { + clear_bit(substream->stream, + &instance->midi_port[substream->number].enabled_streams); + cancel_work_sync(&instance->midi_port[substream->number].in_handler); + } else { + set_bit(substream->stream, + &instance->midi_port[substream->number].enabled_streams); + if (!instance->stopping) + queue_work(pimidi_global.work_queue, + &instance->midi_port[substream->number].in_handler); + } +} + +static void pimidi_get_port_info(struct snd_rawmidi *rmidi, int number, + struct snd_seq_port_info *seq_port_info) +{ + printd_g("%p, %d, %p", rmidi, number, seq_port_info); + seq_port_info->type = + SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | + SNDRV_SEQ_PORT_TYPE_HARDWARE | + SNDRV_SEQ_PORT_TYPE_PORT; + strscpy(seq_port_info->name, number == 0 ? "a" : "b", + sizeof(seq_port_info->name)); + seq_port_info->midi_voices = 0; +} + +static const struct snd_rawmidi_global_ops pimidi_midi_ops = { + .get_port_info = pimidi_get_port_info, +}; + +static int pimidi_midi_open(struct snd_rawmidi_substream *substream) +{ + printd_g("(%p) stream=%d number=%d", substream, substream->stream, substream->number); + return 0; +} + +static const struct snd_rawmidi_ops pimidi_midi_output_ops = { + .open = pimidi_midi_open, + .close = pimidi_midi_output_close, + .trigger = pimidi_midi_output_trigger, + .drain = pimidi_midi_output_drain, +}; + +static const struct snd_rawmidi_ops pimidi_midi_input_ops = { + .open = pimidi_midi_open, + .close = pimidi_midi_input_close, + .trigger = pimidi_midi_input_trigger, +}; + +static int pimidi_register(struct pimidi_instance *instance) +{ + int err = 0; + + mutex_lock(&pimidi_global.lock); + printd(instance, "Registering..."); + if (!pimidi_global.reset_gpio) { + printd_g("Getting reset pin."); + pimidi_global.reset_gpio = gpiod_get(&instance->i2c_client->dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(pimidi_global.reset_gpio)) { + err = PTR_ERR(pimidi_global.reset_gpio); + printe_g("gpiod_get failed: %d", err); + pimidi_global.reset_gpio = NULL; + mutex_unlock(&pimidi_global.lock); + return err; + } + } + list_add_tail(&instance->list, &pimidi_global.devices); + mutex_unlock(&pimidi_global.lock); + return err; +} + +static void pimidi_unregister(struct pimidi_instance *instance) +{ + mutex_lock(&pimidi_global.lock); + printd(instance, "Unregistering..."); + list_del(&instance->list); + if (list_empty(&pimidi_global.devices)) { + printd_g("Releasing reset pin"); + gpiod_put(pimidi_global.reset_gpio); + pimidi_global.reset_gpio = NULL; + } + mutex_unlock(&pimidi_global.lock); +} + +static void pimidi_perform_reset(void) +{ + mutex_lock(&pimidi_global.lock); + + printd_g("Performing reset."); + + struct list_head *p; + + list_for_each(p, &pimidi_global.devices) { + struct pimidi_instance *instance = list_entry(p, struct pimidi_instance, list); + + printd(instance, "Pausing..."); + instance->stopping = true; + disable_irq(instance->i2c_client->irq); + cancel_work(&instance->drdy_handler); + + int i; + + for (i = 0; i < PIMIDI_PORTS; ++i) { + cancel_work(&instance->midi_port[i].in_handler); + cancel_delayed_work(&instance->midi_port[i].out_handler); + } + + drain_workqueue(pimidi_global.work_queue); + } + + printd_g("Reset = low"); + gpiod_set_value(pimidi_global.reset_gpio, 1); + + list_for_each(p, &pimidi_global.devices) { + struct pimidi_instance *instance = list_entry(p, struct pimidi_instance, list); + + if (gpiod_is_active_low(instance->data_ready_gpio)) + gpiod_toggle_active_low(instance->data_ready_gpio); + gpiod_direction_output(instance->data_ready_gpio, 1); + printd(instance, "DRDY high"); + } + + usleep_range(1000, 5000); + printd_g("Reset = high"); + gpiod_set_value(pimidi_global.reset_gpio, 0); + msleep(30); + + int i; + + for (i = 0; i < PIMIDI_MAX_DEVICES; ++i) { + usleep_range(1000, 3000); + list_for_each(p, &pimidi_global.devices) { + struct pimidi_instance *instance = list_entry(p, struct pimidi_instance, + list); + + if (instance->d < i) + continue; + printd(instance, "DRDY -> %d", !gpiod_get_value(instance->data_ready_gpio)); + gpiod_set_value(instance->data_ready_gpio, + !gpiod_get_value(instance->data_ready_gpio)); + } + } + usleep_range(16000, 20000); + + list_for_each(p, &pimidi_global.devices) { + struct pimidi_instance *instance = list_entry(p, struct pimidi_instance, list); + + if (!gpiod_is_active_low(instance->data_ready_gpio)) + gpiod_toggle_active_low(instance->data_ready_gpio); + + printd(instance, "DRDY input"); + gpiod_direction_input(instance->data_ready_gpio); + + printd(instance, "Resume..."); + instance->stopping = false; + enable_irq(instance->i2c_client->irq); + } + + printd_g("Reset done."); + usleep_range(16000, 20000); + + mutex_unlock(&pimidi_global.lock); +} + +static int pimidi_read_version(struct pimidi_version_t *version, struct pimidi_instance *instance) +{ + memset(version, 0, sizeof(*version)); + + const char cmd[4] = { 0xb2, 0x01, 0x01, 0x95 }; + + char result[9]; + + memset(result, 0, sizeof(result)); + + DECLARE_COMPLETION_ONSTACK(done); + + mutex_lock(&instance->comm_lock); + int err = i2c_master_send(instance->i2c_client, cmd, sizeof(cmd)); + + if (err < 0) { + mutex_unlock(&instance->comm_lock); + return err; + } + instance->rx_buf = result; + instance->rx_len = sizeof(result); + instance->rx_completion = &done; + mutex_unlock(&instance->comm_lock); + + printd(instance, "Waiting for drdy"); + wait_for_completion_io_timeout(&done, msecs_to_jiffies(1000u)); + printd(instance, "Done waiting"); + + if (!completion_done(&done)) { + mutex_lock(&instance->comm_lock); + instance->rx_buf = NULL; + instance->rx_len = 0; + instance->rx_status = -ETIMEDOUT; + instance->rx_completion = NULL; + mutex_unlock(&instance->comm_lock); + return -ETIMEDOUT; + } + + if (CRC8_GOOD_VALUE(pimidi_crc8_table) != crc8(pimidi_crc8_table, result, sizeof(result), + CRC8_INIT_VALUE)) + return -EIO; + + const char expected[4] = { 0xb7, 0x81, 0x01, 0x00 }; + + if (memcmp(result, expected, sizeof(expected)) != 0) + return -EPROTO; + + u32 v = ntohl(*(uint32_t *)(result + 4)); + + version->hwrev = v >> 24; + version->major = (v & 0x00ff0000) >> 16; + version->minor = (v & 0x0000ff00) >> 8; + version->build = v & 0x000000ff; + + return 0; +} + +static int pimidi_read_serial(char serial[11], struct pimidi_instance *instance) +{ + memset(serial, 0, sizeof(char[11])); + + const char cmd[4] = { 0xb2, 0x03, 0x04, 0x97 }; + + char result[PIMIDI_MAX_PACKET_SIZE]; + + memset(result, 0, sizeof(result)); + + DECLARE_COMPLETION_ONSTACK(done); + + mutex_lock(&instance->comm_lock); + int err = i2c_master_send(instance->i2c_client, cmd, sizeof(cmd)); + + if (err < 0) { + mutex_unlock(&instance->comm_lock); + return err; + } + instance->rx_buf = result; + instance->rx_len = sizeof(result); + instance->rx_completion = &done; + mutex_unlock(&instance->comm_lock); + + printd(instance, "Waiting for drdy"); + wait_for_completion_io_timeout(&done, msecs_to_jiffies(1000u)); + printd(instance, "Done waiting"); + + if (!completion_done(&done)) { + mutex_lock(&instance->comm_lock); + instance->rx_buf = NULL; + instance->rx_len = 0; + instance->rx_status = -ETIMEDOUT; + instance->rx_completion = NULL; + mutex_unlock(&instance->comm_lock); + printe(instance, "Timed out"); + return -ETIMEDOUT; + } + + if (CRC8_GOOD_VALUE(pimidi_crc8_table) != crc8(pimidi_crc8_table, result, + (result[0] & 0x0f) + 2, CRC8_INIT_VALUE)) + return -EIO; + + const char expected[4] = { 0xbd, 0x83, 0x04, 0x0a }; + + if (memcmp(result, expected, sizeof(expected)) != 0) { + printe(instance, "Unexpected response: %02x %02x %02x %02x", result[0], result[1], + result[2], result[3]); + return -EPROTO; + } + + memcpy(serial, result + 4, 10); + + if (strspn(serial, "\xff") == 10) + strscpy(serial, "(unset)", 8); + + return 0; +} + +static void pimidi_handle_midi_data(struct pimidi_instance *instance, int port, const uint8_t *data, + unsigned int n) +{ + printd(instance, "Handling MIDI data for port %d (%u bytes)", port, n); + if (n == 0) + return; + + struct pimidi_midi_port *midi_port = &instance->midi_port[port]; + + kfifo_in(&midi_port->in_fifo, data, n); + + if (!instance->stopping) + queue_work(pimidi_global.work_queue, &midi_port->in_handler); + + printd(instance, "Done"); +} + +static void pimidi_drdy_continue(struct pimidi_instance *instance) +{ + if (instance->stopping) { + printd(instance, "Refusing to queue work / enable IRQ due to stopping."); + return; + } + + if (gpiod_get_value(instance->data_ready_gpio)) { + printd_rl(instance, "Queue work due to DRDY line still low"); + queue_work(pimidi_global.work_queue, &instance->drdy_handler); + } else { + printd_rl(instance, "Enabling irq for more data"); + enable_irq(gpiod_to_irq(instance->data_ready_gpio)); + } +} + +static void pimidi_drdy_handler(struct work_struct *work) +{ + struct pimidi_instance *instance = container_of(work, struct pimidi_instance, drdy_handler); + + printd(instance, "(%p)", work); + + mutex_lock(&instance->comm_lock); + if (!instance->rx_completion) { + u8 data[PIMIDI_MAX_PACKET_SIZE]; + int n = i2c_master_recv(instance->i2c_client, data, 3); + + if (n < 0) { + printe(instance, "Error reading from device: %d", n); + mutex_unlock(&instance->comm_lock); + pimidi_drdy_continue(instance); + return; + } + + if (data[0] == 0xfe) { + printe_rl(instance, "Invalid packet 0x%02x 0x%02x 0x%02x", data[0], data[1], + data[2]); + mutex_unlock(&instance->comm_lock); + pimidi_drdy_continue(instance); + return; + } + + int len = (data[0] & 0x0f) + 2; + + if (len > n) { + printd(instance, "Need %d more bytes", len - n); + int err = i2c_master_recv(instance->i2c_client, data + n, len - n); + + if (err < 0) { + printe(instance, "Error reading remainder from device: %d", err); + mutex_unlock(&instance->comm_lock); + pimidi_drdy_continue(instance); + return; +#ifdef PIMIDI_DEBUG + } else { + pr_debug("Recv_2:"); + int i; + + for (i = n; i < len; ++i) + pr_cont(" %02x", data[i]); + pr_cont("\n"); +#endif + } + } + + if (CRC8_GOOD_VALUE(pimidi_crc8_table) == crc8(pimidi_crc8_table, data, len, + CRC8_INIT_VALUE)) { + switch (data[0] & 0xf0) { + case 0x00: + pimidi_handle_midi_data(instance, 0, data + 1, len - 2); + break; + case 0x10: + pimidi_handle_midi_data(instance, 1, data + 1, len - 2); + break; + default: + printd(instance, "Unhandled command %02x", data[0]); + break; + } + } else { + printe(instance, "I2C rx corruption detected."); + pr_info("Packet [%d]:", len); + int i; + + for (i = 0; i < len; ++i) + pr_cont(" %02x", data[i]); + pr_cont("\n"); + } + + mutex_unlock(&instance->comm_lock); + } else { + printd(instance, "Completing drdy"); + instance->rx_status = i2c_master_recv(instance->i2c_client, instance->rx_buf, 3); + printd(instance, "Recv_1 %02x %02x %02x", instance->rx_buf[0], instance->rx_buf[1], + instance->rx_buf[2]); + if (instance->rx_len > 3 && instance->rx_status == 3) { + instance->rx_status = i2c_master_recv(instance->i2c_client, + instance->rx_buf + 3, + instance->rx_len - 3); + if (instance->rx_status >= 0) + instance->rx_status += 3; +#ifdef PIMIDI_DEBUG + pr_debug("Recv_2:"); + int i; + + for (i = 3; i < instance->rx_len; ++i) + pr_cont(" %02x", instance->rx_buf[i]); + pr_cont("\n"); +#endif + } + struct completion *done = instance->rx_completion; + + instance->rx_buf = NULL; + instance->rx_len = 0; + instance->rx_completion = NULL; + complete_all(done); + mutex_unlock(&instance->comm_lock); + } + + pimidi_drdy_continue(instance); +} + +static irqreturn_t pimidi_drdy_interrupt_handler(int irq, void *dev_id) +{ + struct pimidi_instance *instance = (struct pimidi_instance *)dev_id; + + if (instance->stopping) { + printd(instance, "DRDY interrupt, but stopping, ignoring..."); + return IRQ_HANDLED; + } + + printd(instance, "DRDY interrupt, masking"); + disable_irq_nosync(irq); + + printd(instance, "Queue work due to DRDY interrupt"); + queue_work(pimidi_global.work_queue, &instance->drdy_handler); + + return IRQ_HANDLED; +} + +static void pimidi_proc_stat_show(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + const unsigned int *d = entry->private_data; + + snd_iprintf(buffer, "%u\n", *d); +} + +static void pimidi_proc_serial_show(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + struct pimidi_instance *instance = entry->private_data; + + snd_iprintf(buffer, "%s\n", instance->serial); +} + +static void pimidi_proc_version_show(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + struct pimidi_instance *instance = entry->private_data; + + snd_iprintf(buffer, "%u.%u.%u\n", instance->version.major, instance->version.minor, + instance->version.build); +} + +static void pimidi_proc_hwrev_show(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + struct pimidi_instance *instance = entry->private_data; + + snd_iprintf(buffer, "%u\n", instance->version.hwrev); +} + +static int pimidi_i2c_probe(struct i2c_client *client) +{ + struct snd_card *card = NULL; + int err, d, i; + + d = client->addr - 0x20; + + if (d < 0 || d >= 8) { + printe_g("Unexpected device address: %d", client->addr); + err = -EINVAL; + goto finalize; + } + + err = snd_card_new(&client->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, THIS_MODULE, + sizeof(struct pimidi_instance), &card); + + if (err) { + printe_g("snd_card_new failed: %d", err); + return err; + } + + struct pimidi_instance *instance = (struct pimidi_instance *)card->private_data; + + instance->i2c_client = client; + instance->d = d; + + struct snd_rawmidi *rawmidi; + + err = snd_rawmidi_new(card, card->shortname, 0, 2, 2, &rawmidi); + if (err < 0) { + printe(instance, "snd_rawmidi_new failed: %d", err); + goto finalize; + } + + instance->rawmidi = rawmidi; + strscpy(rawmidi->name, "pimidi", sizeof(rawmidi->name)); + + rawmidi->info_flags = + SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT | SNDRV_RAWMIDI_INFO_DUPLEX; + rawmidi->private_data = instance; + rawmidi->ops = &pimidi_midi_ops; + + snd_rawmidi_set_ops(rawmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &pimidi_midi_output_ops); + snd_rawmidi_set_ops(rawmidi, SNDRV_RAWMIDI_STREAM_INPUT, &pimidi_midi_input_ops); + + instance->data_ready_gpio = devm_gpiod_get(&client->dev, "data-ready", GPIOD_OUT_HIGH); + if (IS_ERR(instance->data_ready_gpio)) { + err = PTR_ERR(instance->data_ready_gpio); + printe(instance, "devm_gpiod_get failed: %d", err); + goto finalize; + } + + err = pimidi_register(instance); + if (err < 0) { + printe(instance, "pimidi_register failed: %d", err); + goto finalize; + } + + pimidi_perform_reset(); + + INIT_WORK(&instance->drdy_handler, pimidi_drdy_handler); + mutex_init(&instance->comm_lock); + + err = devm_request_irq(&client->dev, client->irq, pimidi_drdy_interrupt_handler, + IRQF_SHARED | IRQF_TRIGGER_LOW, "data_ready_int", instance); + + if (err != 0) { + printe(instance, "data_available IRQ request failed! %d", err); + goto finalize; + } + + err = pimidi_read_version(&instance->version, instance); + if (err < 0) { + printe(instance, "pimidi_read_version failed: %d", err); + goto finalize; + } + + err = pimidi_read_serial(instance->serial, instance); + if (err < 0) { + printe(instance, "pimidi_read_serial failed: %d", err); + goto finalize; + } else if (instance->serial[0] != 'P' || instance->serial[1] != 'M' || + strlen(instance->serial) != 10) { + printe(instance, "Unexpected serial number: %s", instance->serial); + err = -EIO; + goto finalize; + } + + printi(instance, "pimidi%d hw:%d version %u.%u.%u-%u, serial %s", + d, + card->number, + instance->version.major, + instance->version.minor, + instance->version.build, + instance->version.hwrev, + instance->serial + ); + + strscpy(card->driver, "snd-pimidi", sizeof(card->driver)); + snprintf(card->shortname, sizeof(card->shortname), "pimidi%d", d); + snprintf(card->longname, sizeof(card->longname), "pimidi%d %s", d, instance->serial); + snprintf(card->id, sizeof(card->id), "pimidi%d", d); + + snprintf(pimidi_find_substream(rawmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, 0)->name, + 10u, "pimidi%d-a", d); + snprintf(pimidi_find_substream(rawmidi, SNDRV_RAWMIDI_STREAM_INPUT, 0)->name, + 10u, "pimidi%d-a", d); + snprintf(pimidi_find_substream(rawmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, 1)->name, + 10u, "pimidi%d-b", d); + snprintf(pimidi_find_substream(rawmidi, SNDRV_RAWMIDI_STREAM_INPUT, 1)->name, + 10u, "pimidi%d-b", d); + + err = snd_card_ro_proc_new(card, "a-tx", &instance->midi_port[0].tx_cnt, + pimidi_proc_stat_show); + err = snd_card_ro_proc_new(card, "a-rx", &instance->midi_port[0].rx_cnt, + pimidi_proc_stat_show); + err = snd_card_ro_proc_new(card, "b-tx", &instance->midi_port[1].tx_cnt, + pimidi_proc_stat_show); + err = snd_card_ro_proc_new(card, "b-rx", &instance->midi_port[1].rx_cnt, + pimidi_proc_stat_show); + err = snd_card_ro_proc_new(card, "serial", instance, pimidi_proc_serial_show); + err = snd_card_ro_proc_new(card, "version", instance, pimidi_proc_version_show); + err = snd_card_ro_proc_new(card, "hwrev", instance, pimidi_proc_hwrev_show); + if (err < 0) { + printe(instance, "snd_card_ro_proc_new failed: %d", err); + goto finalize; + } + + err = snd_card_register(card); + if (err < 0) { + printe(instance, "snd_card_register failed: %d", err); + goto finalize; + } + +finalize: + if (err) { + instance->stopping = true; + cancel_work_sync(&instance->drdy_handler); + mutex_destroy(&instance->comm_lock); + pimidi_unregister(instance); + snd_card_free(card); + return err; + } + + for (i = 0; i < PIMIDI_PORTS; ++i) { + struct pimidi_midi_port *port = &instance->midi_port[i]; + + mutex_init(&port->in_lock); + mutex_init(&port->out_lock); + INIT_WORK(&port->in_handler, + i == 0 ? pimidi_midi_in_handler_0 : pimidi_midi_in_handler_1); + INIT_DELAYED_WORK(&port->out_handler, + i == 0 ? pimidi_midi_out_handler_0 : pimidi_midi_out_handler_1); + INIT_KFIFO(port->in_fifo); + port->last_output_at = jiffies; + } + + i2c_set_clientdata(client, card); + return 0; +} + +static void pimidi_i2c_remove(struct i2c_client *client) +{ + printd_g("(%p)", client); + + int i; + struct snd_card *card = i2c_get_clientdata(client); + + if (card) { + printi_g("Unloading hw:%d %s", card->number, card->longname); + struct pimidi_instance *instance = (struct pimidi_instance *)card->private_data; + + instance->stopping = true; + i2c_set_clientdata(client, NULL); + devm_free_irq(&client->dev, client->irq, instance); + cancel_work_sync(&instance->drdy_handler); + + for (i = 0; i < PIMIDI_PORTS; ++i) { + cancel_work_sync(&instance->midi_port[i].in_handler); + cancel_delayed_work_sync(&instance->midi_port[i].out_handler); + mutex_destroy(&instance->midi_port[i].out_lock); + mutex_destroy(&instance->midi_port[i].in_lock); + kfifo_free(&instance->midi_port[i].in_fifo); + } + + mutex_destroy(&instance->comm_lock); + pimidi_unregister(instance); + snd_card_free(card); + } +} + +static const struct i2c_device_id pimidi_i2c_ids[] = { + { "pimidi", 0 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, pimidi_i2c_ids); + +static const struct of_device_id pimidi_i2c_dt_ids[] = { + { .compatible = "blokaslabs,pimidi", }, + {} +}; +MODULE_DEVICE_TABLE(of, pimidi_i2c_dt_ids); + +static struct i2c_driver pimidi_i2c_driver = { + .driver = { + .name = "pimidi", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(pimidi_i2c_dt_ids), + }, + .probe = pimidi_i2c_probe, + .remove = pimidi_i2c_remove, + .id_table = pimidi_i2c_ids, +}; + +static int pimidi_module_init(void) +{ + int err = 0; + + mutex_init(&pimidi_global.lock); + + INIT_LIST_HEAD(&pimidi_global.devices); + + pimidi_global.work_queue = create_singlethread_workqueue("pimidi"); + if (!pimidi_global.work_queue) { + err = -ENOMEM; + goto cleanup; + } + + err = i2c_add_driver(&pimidi_i2c_driver); + if (err < 0) + goto cleanup; + + crc8_populate_msb(pimidi_crc8_table, PIMIDI_CRC8_POLYNOMIAL); + + return 0; + +cleanup: + mutex_destroy(&pimidi_global.lock); + return err; +} + +static void pimidi_module_exit(void) +{ + i2c_del_driver(&pimidi_i2c_driver); + mutex_lock(&pimidi_global.lock); + if (pimidi_global.reset_gpio) { + gpiod_put(pimidi_global.reset_gpio); + pimidi_global.reset_gpio = NULL; + } + mutex_unlock(&pimidi_global.lock); + + destroy_workqueue(pimidi_global.work_queue); + pimidi_global.work_queue = NULL; + + mutex_destroy(&pimidi_global.lock); +} + +module_init(pimidi_module_init); +module_exit(pimidi_module_exit); + +MODULE_AUTHOR("Giedrius Trainavi\xc4\x8dius "); +MODULE_DESCRIPTION("MIDI driver for Blokas Pimidi, https://blokas.io/"); +MODULE_LICENSE("GPL"); + +/* vim: set ts=8 sw=8 noexpandtab: */ diff --git a/sound/drivers/upisnd/Makefile b/sound/drivers/upisnd/Makefile new file mode 100644 index 000000000000..00f3d5125380 --- /dev/null +++ b/sound/drivers/upisnd/Makefile @@ -0,0 +1,23 @@ +# Pisound Micro Linux kernel module. +# Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/ +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the +# License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# + +-include $(shell pwd)/Makefile.dev + +ifdef M +-include $(M)/Makefile.dev +endif + +obj-$(CONFIG_SND_PISOUND_MICRO) := snd-soc-upisnd-ctrl.o snd-soc-upisnd-codec.o +snd-soc-upisnd-ctrl-objs := upisnd_module.o upisnd_ctrl.o upisnd_comm.o upisnd_sound.o upisnd_midi.o upisnd_pins.o upisnd_gpio.o upisnd_sysfs.o upisnd_utils.o +snd-soc-upisnd-codec-objs := upisnd_codec.o diff --git a/sound/drivers/upisnd/upisnd_codec.c b/sound/drivers/upisnd/upisnd_codec.c new file mode 100644 index 000000000000..9d0fe01e9a7d --- /dev/null +++ b/sound/drivers/upisnd/upisnd_codec.c @@ -0,0 +1,1748 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Pisound Micro Linux kernel module. + * Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 of the + * License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "upisnd_codec.h" +#include "upisnd_debug.h" + +// Based on ADAU1361/ADAU1461/ADAU1761/ADAU1961 driver by Lars-Peter Clausen. +// Customized specifically for ADAU1961 on Pisound Micro. + +static int adau_calc_pll_cfg(unsigned int freq_in, unsigned int freq_out, + u8 regs[5]) +{ + unsigned int r, n, m, i, j; + unsigned int div; + + if (!freq_out) { + r = 0; + n = 0; + m = 0; + div = 0; + } else { + if (freq_out % freq_in != 0) { + div = DIV_ROUND_UP(freq_in, 13500000); + freq_in /= div; + r = freq_out / freq_in; + i = freq_out % freq_in; + j = gcd(i, freq_in); + n = i / j; + m = freq_in / j; + div--; + } else { + r = freq_out / freq_in; + n = 0; + m = 0; + div = 0; + } + if (n > 0xffff || m > 0xffff || div > 3 || r > 8 || r < 2) + return -EINVAL; + } + + regs[0] = m >> 8; + regs[1] = m & 0xff; + regs[2] = n >> 8; + regs[3] = n & 0xff; + regs[4] = (r << 3) | (div << 1); + if (m != 0) + regs[4] |= 1; /* Fractional mode */ + + return 0; +} + +/** + * enum adau1961_micbias_voltage - Microphone bias voltage + * @ADAU1961_MICBIAS_0_90_AVDD: 0.9 * AVDD + * @ADAU1961_MICBIAS_0_65_AVDD: 0.65 * AVDD + */ +enum adau1961_micbias_voltage { + ADAU1961_MICBIAS_0_90_AVDD = 0, + ADAU1961_MICBIAS_0_65_AVDD = 1, +}; + +/** + * enum adau1961_output_mode - Output mode configuration + * @ADAU1961_OUTPUT_MODE_HEADPHONE: Headphone output + * @ADAU1961_OUTPUT_MODE_HEADPHONE_CAPLESS: Capless headphone output + * @ADAU1961_OUTPUT_MODE_LINE: Line output + */ +enum adau1961_output_mode { + ADAU1961_OUTPUT_MODE_HEADPHONE, + ADAU1961_OUTPUT_MODE_HEADPHONE_CAPLESS, + ADAU1961_OUTPUT_MODE_LINE, +}; + +/** + * struct adau1961_platform_data - ADAU1961 Codec driver platform data + * @input_differential: If true the input pins will be configured in + * differential mode. + * @lineout_mode: Output mode for the LOUT/ROUT pins + * @headphone_mode: Output mode for the LHP/RHP pins + * @monoout_mode: Output mode for the MONOOUT pin. Ignored if headphone_mode + * is set to ADAU1961_OUTPUT_MODE_HEADPHONE_CAPLESS. + */ +struct adau1961_platform_data { + bool input_differential; + enum adau1961_output_mode lineout_mode; + enum adau1961_output_mode headphone_mode; + enum adau1961_output_mode monoout_mode; +}; + +struct snd_soc_component; +struct snd_pcm_substream; + +struct i2c_client; + +enum adau1961_pll { + ADAU1961_PLL, +}; + +enum adau1961_pll_src { + ADAU1961_PLL_SRC_MCLK, +}; + +enum adau1961_clk_src { + /* Automatically configure PLL based on the sample rate */ + ADAU1961_CLK_SRC_PLL_AUTO, + ADAU1961_CLK_SRC_MCLK, + ADAU1961_CLK_SRC_PLL, +}; + +struct clk; + +struct adau { + unsigned int sysclk; + unsigned int pll_freq; + struct clk *mclk; + + enum adau1961_clk_src clk_src; + void (*switch_mode)(struct device *dev); + + unsigned int dai_fmt; + + u8 pll_regs[6]; + + bool master; + bool vgnd_shorted; + bool hpl_unmuted; + bool hpr_unmuted; + + unsigned int tdm_slot[2]; + + struct regmap *regmap; +}; + +#define ADAU1961_CLOCK_CONTROL 0x4000 +#define ADAU1961_PLL_CONTROL 0x4002 +#define ADAU1961_DIGMIC_JACKDETECT 0x4008 +#define ADAU1961_REC_MIXER_LEFT0 0x400a +#define ADAU1961_REC_MIXER_LEFT1 0x400b +#define ADAU1961_REC_MIXER_RIGHT0 0x400c +#define ADAU1961_REC_MIXER_RIGHT1 0x400d +#define ADAU1961_LEFT_DIFF_INPUT_VOL 0x400e +#define ADAU1961_RIGHT_DIFF_INPUT_VOL 0x400f +#define ADAU1961_MICBIAS 0x4010 +#define ADAU1961_ALC_CTRL0 0x4011 +#define ADAU1961_ALC_CTRL1 0x4012 +#define ADAU1961_ALC_CTRL2 0x4013 +#define ADAU1961_ALC_CTRL3 0x4014 +#define ADAU1961_SERIAL_PORT0 0x4015 +#define ADAU1961_SERIAL_PORT1 0x4016 +#define ADAU1961_CONVERTER0 0x4017 +#define ADAU1961_CONVERTER1 0x4018 +#define ADAU1961_ADC_CONTROL 0x4019 +#define ADAU1961_LEFT_INPUT_DIGITAL_VOL 0x401a +#define ADAU1961_RIGHT_INPUT_DIGITAL_VOL 0x401b +#define ADAU1961_PLAY_MIXER_LEFT0 0x401c +#define ADAU1961_PLAY_MIXER_LEFT1 0x401d +#define ADAU1961_PLAY_MIXER_RIGHT0 0x401e +#define ADAU1961_PLAY_MIXER_RIGHT1 0x401f +#define ADAU1961_PLAY_LR_MIXER_LEFT 0x4020 +#define ADAU1961_PLAY_LR_MIXER_RIGHT 0x4021 +#define ADAU1961_PLAY_MIXER_MONO 0x4022 +#define ADAU1961_PLAY_HP_LEFT_VOL 0x4023 +#define ADAU1961_PLAY_HP_RIGHT_VOL 0x4024 +#define ADAU1961_PLAY_LINE_LEFT_VOL 0x4025 +#define ADAU1961_PLAY_LINE_RIGHT_VOL 0x4026 +#define ADAU1961_PLAY_MONO_OUTPUT_VOL 0x4027 +#define ADAU1961_POP_CLICK_SUPPRESS 0x4028 +#define ADAU1961_PLAY_POWER_MGMT 0x4029 +#define ADAU1961_DAC_CONTROL0 0x402a +#define ADAU1961_DAC_CONTROL1 0x402b +#define ADAU1961_DAC_CONTROL2 0x402c +#define ADAU1961_SERIAL_PORT_PAD 0x402d +#define ADAU1961_CONTROL_PORT_PAD0 0x402f +#define ADAU1961_CONTROL_PORT_PAD1 0x4030 +#define ADAU1961_JACK_DETECT_PIN 0x4031 +#define ADAU1961_DEJITTER 0x4036 + +#define ADAU1961_CLOCK_CONTROL_INFREQ_MASK 0x6 +#define ADAU1961_CLOCK_CONTROL_CORECLK_SRC_PLL BIT(3) +#define ADAU1961_CLOCK_CONTROL_SYSCLK_EN BIT(0) + +#define ADAU1961_SERIAL_PORT0_BCLK_POL BIT(4) +#define ADAU1961_SERIAL_PORT0_LRCLK_POL BIT(3) +#define ADAU1961_SERIAL_PORT0_MASTER BIT(0) +#define ADAU1961_SERIAL_PORT0_STEREO (0x0 << 1) +#define ADAU1961_SERIAL_PORT0_TDM4 (0x1 << 1) +#define ADAU1961_SERIAL_PORT0_TDM_MASK (0x3 << 1) +#define ADAU1961_SERIAL_PORT0_PULSE_MODE BIT(5) + +#define ADAU1961_SERIAL_PORT1_DELAY1 0x00 +#define ADAU1961_SERIAL_PORT1_DELAY0 0x01 +#define ADAU1961_SERIAL_PORT1_DELAY8 0x02 +#define ADAU1961_SERIAL_PORT1_DELAY16 0x03 +#define ADAU1961_SERIAL_PORT1_DELAY_MASK 0x03 +#define ADAU1961_SERIAL_PORT1_BCLK64 (0x0 << 5) +#define ADAU1961_SERIAL_PORT1_BCLK32 (0x1 << 5) +#define ADAU1961_SERIAL_PORT1_BCLK48 (0x2 << 5) +#define ADAU1961_SERIAL_PORT1_BCLK128 (0x3 << 5) +#define ADAU1961_SERIAL_PORT1_BCLK256 (0x4 << 5) +#define ADAU1961_SERIAL_PORT1_BCLK_MASK (0x7 << 5) + +#define ADAU1961_CONVERTER0_DAC_PAIR(x) (((x) - 1) << 5) +#define ADAU1961_CONVERTER0_DAC_PAIR_MASK (0x3 << 5) +#define ADAU1961_CONVERTER0_CONVSR_MASK 0x7 +#define ADAU1961_CONVERTER0_ADOSR BIT(3) + +#define ADAU1961_CONVERTER1_ADC_PAIR(x) ((x) - 1) +#define ADAU1961_CONVERTER1_ADC_PAIR_MASK 0x3 + +#define ADAU1961_DIFF_INPUT_VOL_LDEN BIT(0) + +#define ADAU1961_PLAY_MIXER_MONO_EN BIT(0) + +#define ADAU1961_PLAY_MONO_OUTPUT_VOL_MODE_HP BIT(0) +#define ADAU1961_PLAY_MONO_OUTPUT_VOL_UNMUTE BIT(1) + +#define ADAU1961_PLAY_HP_RIGHT_VOL_MODE_HP BIT(0) + +#define ADAU1961_PLAY_LINE_LEFT_VOL_MODE_HP BIT(0) + +#define ADAU1961_PLAY_LINE_RIGHT_VOL_MODE_HP BIT(0) + +static const DECLARE_TLV_DB_MINMAX(adau1961_digital_tlv, -9563, 0); + +static int adau1961_pll_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct adau *adau = snd_soc_component_get_drvdata(component); + + if (SND_SOC_DAPM_EVENT_ON(event)) { + adau->pll_regs[5] = 1; + } else { + adau->pll_regs[5] = 0; + // Bypass the PLL when disabled, otherwise registers will become + // inaccessible. + regmap_update_bits(adau->regmap, ADAU1961_CLOCK_CONTROL, + ADAU1961_CLOCK_CONTROL_CORECLK_SRC_PLL, 0); + } + + // The PLL register is 6 bytes long and can only be written at once. + regmap_raw_write(adau->regmap, ADAU1961_PLL_CONTROL, + adau->pll_regs, ARRAY_SIZE(adau->pll_regs)); + + if (SND_SOC_DAPM_EVENT_ON(event)) { + mdelay(5); + regmap_update_bits(adau->regmap, ADAU1961_CLOCK_CONTROL, + ADAU1961_CLOCK_CONTROL_CORECLK_SRC_PLL, + ADAU1961_CLOCK_CONTROL_CORECLK_SRC_PLL); + } + + return 0; +} + +static int adau1961_adc_fixup(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct adau *adau = snd_soc_component_get_drvdata(component); + + /* + * If we are capturing, toggle the ADOSR bit in Converter Control 0 to + * avoid losing SNR (workaround from ADI). This must be done after + * the ADC(s) have been enabled. According to the data sheet, it is + * normally illegal to set this bit when the sampling rate is 96 kHz, + * but according to ADI it is acceptable for this workaround. + */ + regmap_update_bits(adau->regmap, ADAU1961_CONVERTER0, + ADAU1961_CONVERTER0_ADOSR, ADAU1961_CONVERTER0_ADOSR); + regmap_update_bits(adau->regmap, ADAU1961_CONVERTER0, + ADAU1961_CONVERTER0_ADOSR, 0); + + return 0; +} + +static const char * const adau1961_mono_stereo_text[] = { + "Stereo", + "Mono Left Channel (L+R)", + "Mono Right Channel (L+R)", + "Mono (L+R)", +}; + +static SOC_ENUM_SINGLE_DECL(adau1961_dac_mode_enum, + ADAU1961_DAC_CONTROL0, 6, adau1961_mono_stereo_text); + +static const struct snd_kcontrol_new adau1961_dac_mode_mux = + SOC_DAPM_ENUM("DAC Mono-Stereo-Mode", adau1961_dac_mode_enum); + +static const struct snd_soc_dapm_route adau1961_dapm_pll_route = { + "SYSCLK", NULL, "PLL", +}; + +static int adau1961_set_dai_pll(struct snd_soc_dai *dai, int pll_id, + int source, unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_component *component = dai->component; + struct adau *adau = snd_soc_component_get_drvdata(component); + int ret; + + if (freq_in < 8000000 || freq_in > 27000000) + return -EINVAL; + + ret = adau_calc_pll_cfg(freq_in, freq_out, adau->pll_regs); + if (ret < 0) + return ret; + + /* The PLL register is 6 bytes long and can only be written at once. */ + ret = regmap_raw_write(adau->regmap, ADAU1961_PLL_CONTROL, + adau->pll_regs, ARRAY_SIZE(adau->pll_regs)); + if (ret) + return ret; + + adau->pll_freq = freq_out; + + return 0; +} + +static int adau1961_set_dai_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(dai->component); + struct adau *adau = snd_soc_component_get_drvdata(dai->component); + bool is_pll; + bool was_pll; + + switch (clk_id) { + case ADAU1961_CLK_SRC_MCLK: + is_pll = false; + break; + case ADAU1961_CLK_SRC_PLL_AUTO: + if (!adau->mclk) + return -EINVAL; + fallthrough; + case ADAU1961_CLK_SRC_PLL: + is_pll = true; + break; + default: + return -EINVAL; + } + + switch (adau->clk_src) { + case ADAU1961_CLK_SRC_MCLK: + was_pll = false; + break; + case ADAU1961_CLK_SRC_PLL: + case ADAU1961_CLK_SRC_PLL_AUTO: + was_pll = true; + break; + default: + return -EINVAL; + } + + adau->sysclk = freq; + + if (is_pll != was_pll) { + if (is_pll) { + snd_soc_dapm_add_routes(dapm, + &adau1961_dapm_pll_route, 1); + } else { + snd_soc_dapm_del_routes(dapm, + &adau1961_dapm_pll_route, 1); + } + } + + adau->clk_src = clk_id; + + return 0; +} + +static int adau1961_auto_pll(struct snd_soc_dai *dai, + struct snd_pcm_hw_params *params) +{ + struct adau *adau = snd_soc_dai_get_drvdata(dai); + unsigned int pll_rate; + + switch (params_rate(params)) { + case 48000: + case 8000: + case 12000: + case 16000: + case 24000: + case 32000: + case 96000: + pll_rate = 48000 * 1024; + break; + case 44100: + case 7350: + case 11025: + case 14700: + case 22050: + case 29400: + case 88200: + pll_rate = 44100 * 1024; + break; + default: + return -EINVAL; + } + + return adau1961_set_dai_pll(dai, ADAU1961_PLL, ADAU1961_PLL_SRC_MCLK, + clk_get_rate(adau->mclk), pll_rate); +} + +static int adau1961_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct adau *adau = snd_soc_component_get_drvdata(component); + unsigned int val, div, dsp_div; + unsigned int freq; + int ret; + + switch (adau->clk_src) { + case ADAU1961_CLK_SRC_PLL_AUTO: + ret = adau1961_auto_pll(dai, params); + if (ret) + return ret; + fallthrough; + case ADAU1961_CLK_SRC_PLL: + freq = adau->pll_freq; + break; + default: + freq = adau->sysclk; + break; + } + + if (freq % params_rate(params) != 0) + return -EINVAL; + + switch (freq / params_rate(params)) { + case 1024: /* fs */ + div = 0; + dsp_div = 1; + break; + case 6144: /* fs / 6 */ + div = 1; + dsp_div = 6; + break; + case 4096: /* fs / 4 */ + div = 2; + dsp_div = 5; + break; + case 3072: /* fs / 3 */ + div = 3; + dsp_div = 4; + break; + case 2048: /* fs / 2 */ + div = 4; + dsp_div = 3; + break; + case 1536: /* fs / 1.5 */ + div = 5; + dsp_div = 2; + break; + case 512: /* fs / 0.5 */ + div = 6; + dsp_div = 0; + break; + default: + return -EINVAL; + } + + regmap_update_bits(adau->regmap, ADAU1961_CONVERTER0, + ADAU1961_CONVERTER0_CONVSR_MASK, div); + + if (adau->dai_fmt != SND_SOC_DAIFMT_RIGHT_J) + return 0; + + switch (params_width(params)) { + case 16: + val = ADAU1961_SERIAL_PORT1_DELAY16; + break; + case 24: + val = ADAU1961_SERIAL_PORT1_DELAY8; + break; + case 32: + val = ADAU1961_SERIAL_PORT1_DELAY0; + break; + default: + return -EINVAL; + } + + return regmap_update_bits(adau->regmap, ADAU1961_SERIAL_PORT1, + ADAU1961_SERIAL_PORT1_DELAY_MASK, val); +} + +static int adau1961_set_dai_fmt(struct snd_soc_dai *dai, + unsigned int fmt) +{ + struct adau *adau = snd_soc_component_get_drvdata(dai->component); + unsigned int ctrl0, ctrl1; + unsigned int ctrl0_mask; + int lrclk_pol; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBP_CFP: + ctrl0 = ADAU1961_SERIAL_PORT0_MASTER; + adau->master = true; + break; + case SND_SOC_DAIFMT_CBC_CFC: + ctrl0 = 0; + adau->master = false; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + lrclk_pol = 0; + ctrl1 = ADAU1961_SERIAL_PORT1_DELAY1; + break; + case SND_SOC_DAIFMT_LEFT_J: + case SND_SOC_DAIFMT_RIGHT_J: + lrclk_pol = 1; + ctrl1 = ADAU1961_SERIAL_PORT1_DELAY0; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + ctrl0 |= ADAU1961_SERIAL_PORT0_BCLK_POL; + break; + case SND_SOC_DAIFMT_NB_IF: + lrclk_pol = !lrclk_pol; + break; + case SND_SOC_DAIFMT_IB_IF: + ctrl0 |= ADAU1961_SERIAL_PORT0_BCLK_POL; + lrclk_pol = !lrclk_pol; + break; + default: + return -EINVAL; + } + + if (lrclk_pol) + ctrl0 |= ADAU1961_SERIAL_PORT0_LRCLK_POL; + + /* Set the mask to update all relevant bits in ADAU1961_SERIAL_PORT0 */ + ctrl0_mask = ADAU1961_SERIAL_PORT0_MASTER | + ADAU1961_SERIAL_PORT0_LRCLK_POL | + ADAU1961_SERIAL_PORT0_BCLK_POL | + ADAU1961_SERIAL_PORT0_PULSE_MODE; + + regmap_update_bits(adau->regmap, ADAU1961_SERIAL_PORT0, ctrl0_mask, + ctrl0); + regmap_update_bits(adau->regmap, ADAU1961_SERIAL_PORT1, + ADAU1961_SERIAL_PORT1_DELAY_MASK, ctrl1); + + adau->dai_fmt = fmt & SND_SOC_DAIFMT_FORMAT_MASK; + + return 0; +} + +static int adau1961_xlate_tdm_slot_mask(unsigned int slots, + unsigned int *tx_mask, + unsigned int *rx_mask) +{ + // Workaround for snd_soc_dai_set_tdm_slot issue when slots=0. + return 0; +} + +static int adau1961_set_dai_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, + unsigned int rx_mask, + int slots, + int slot_width) +{ + struct adau *adau = snd_soc_component_get_drvdata(dai->component); + unsigned int ser_ctrl0, ser_ctrl1; + unsigned int conv_ctrl0, conv_ctrl1; + + /* I2S mode */ + if (slots == 0) { + slots = 2; + rx_mask = 3; + tx_mask = 3; + slot_width = 32; + } + + switch (slots) { + case 2: + ser_ctrl0 = ADAU1961_SERIAL_PORT0_STEREO; + break; + case 4: + ser_ctrl0 = ADAU1961_SERIAL_PORT0_TDM4; + break; + default: + return -EINVAL; + } + + switch (slot_width * slots) { + case 32: + ser_ctrl1 = ADAU1961_SERIAL_PORT1_BCLK32; + break; + case 64: + ser_ctrl1 = ADAU1961_SERIAL_PORT1_BCLK64; + break; + case 48: + ser_ctrl1 = ADAU1961_SERIAL_PORT1_BCLK48; + break; + case 128: + ser_ctrl1 = ADAU1961_SERIAL_PORT1_BCLK128; + break; + default: + return -EINVAL; + } + + switch (rx_mask) { + case 0x03: + conv_ctrl1 = ADAU1961_CONVERTER1_ADC_PAIR(1); + adau->tdm_slot[SNDRV_PCM_STREAM_CAPTURE] = 0; + break; + case 0x0c: + conv_ctrl1 = ADAU1961_CONVERTER1_ADC_PAIR(2); + adau->tdm_slot[SNDRV_PCM_STREAM_CAPTURE] = 1; + break; + case 0x30: + conv_ctrl1 = ADAU1961_CONVERTER1_ADC_PAIR(3); + adau->tdm_slot[SNDRV_PCM_STREAM_CAPTURE] = 2; + break; + case 0xc0: + conv_ctrl1 = ADAU1961_CONVERTER1_ADC_PAIR(4); + adau->tdm_slot[SNDRV_PCM_STREAM_CAPTURE] = 3; + break; + default: + return -EINVAL; + } + + switch (tx_mask) { + case 0x03: + conv_ctrl0 = ADAU1961_CONVERTER0_DAC_PAIR(1); + adau->tdm_slot[SNDRV_PCM_STREAM_PLAYBACK] = 0; + break; + case 0x0c: + conv_ctrl0 = ADAU1961_CONVERTER0_DAC_PAIR(2); + adau->tdm_slot[SNDRV_PCM_STREAM_PLAYBACK] = 1; + break; + case 0x30: + conv_ctrl0 = ADAU1961_CONVERTER0_DAC_PAIR(3); + adau->tdm_slot[SNDRV_PCM_STREAM_PLAYBACK] = 2; + break; + case 0xc0: + conv_ctrl0 = ADAU1961_CONVERTER0_DAC_PAIR(4); + adau->tdm_slot[SNDRV_PCM_STREAM_PLAYBACK] = 3; + break; + default: + return -EINVAL; + } + + regmap_update_bits(adau->regmap, ADAU1961_CONVERTER0, + ADAU1961_CONVERTER0_DAC_PAIR_MASK, conv_ctrl0); + regmap_update_bits(adau->regmap, ADAU1961_CONVERTER1, + ADAU1961_CONVERTER1_ADC_PAIR_MASK, conv_ctrl1); + regmap_update_bits(adau->regmap, ADAU1961_SERIAL_PORT0, + ADAU1961_SERIAL_PORT0_TDM_MASK, ser_ctrl0); + regmap_update_bits(adau->regmap, ADAU1961_SERIAL_PORT1, + ADAU1961_SERIAL_PORT1_BCLK_MASK, ser_ctrl1); + + return 0; +} + +static int adau1961_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct adau *adau = snd_soc_component_get_drvdata(dai->component); + (void)adau; + return 0; +} + +static const struct snd_soc_dai_ops adau1961_dai_ops = { + .hw_params = adau1961_hw_params, + .set_sysclk = adau1961_set_dai_sysclk, + .set_fmt = adau1961_set_dai_fmt, + .set_pll = adau1961_set_dai_pll, + .set_tdm_slot = adau1961_set_dai_tdm_slot, + .startup = adau1961_startup, + .xlate_tdm_slot_mask = adau1961_xlate_tdm_slot_mask, +}; + +static bool adau1961_precious_register(struct device *dev, unsigned int reg) +{ + return false; +} + +static bool adau1961_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + /* The PLL register is 6 bytes long */ + case ADAU1961_PLL_CONTROL: + case ADAU1961_PLL_CONTROL + 1: + case ADAU1961_PLL_CONTROL + 2: + case ADAU1961_PLL_CONTROL + 3: + case ADAU1961_PLL_CONTROL + 4: + case ADAU1961_PLL_CONTROL + 5: + return true; + default: + break; + } + + return false; +} + +static int adau1961_add_routes(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct adau *adau = snd_soc_component_get_drvdata(component); + int ret; + + if (adau->clk_src != ADAU1961_CLK_SRC_MCLK) + ret = snd_soc_dapm_add_routes(dapm, &adau1961_dapm_pll_route, 1); + + return ret; +} + +static int adau1961_resume(struct snd_soc_component *component) +{ + struct adau *adau = snd_soc_component_get_drvdata(component); + + if (adau->switch_mode) + adau->switch_mode(component->dev); + + regcache_sync(adau->regmap); + + return 0; +} + +static void adau1961_remove(struct device *dev) +{ + struct adau *adau = dev_get_drvdata(dev); + + clk_disable_unprepare(adau->mclk); +} + +void adau1961_set_vgnd_shorted(struct snd_soc_component *component, bool shorted) +{ + struct adau *p = snd_soc_component_get_drvdata(component); + + struct snd_ctl_elem_value value; + struct snd_kcontrol *k; + + long prev_left, prev_right; + + if (!p) { + printe("Failed to get adau driver data!"); + return; + } + + p->vgnd_shorted = shorted; + + k = snd_soc_card_get_kcontrol(component->card, "Headphone Playback Switch"); + if (!k) { + printe("Failed to get Headphone Playback Switch control!"); + return; + } + + snd_soc_get_volsw(k, &value); + + prev_left = value.value.integer.value[0]; + prev_right = value.value.integer.value[1]; + + if (shorted) { + p->hpl_unmuted = value.value.integer.value[0]; + p->hpr_unmuted = value.value.integer.value[1]; + + value.value.integer.value[0] = 0; + value.value.integer.value[1] = 0; + } else { + value.value.integer.value[0] = p->hpl_unmuted ? 1 : 0; + value.value.integer.value[1] = p->hpr_unmuted ? 1 : 0; + } + + if (value.value.integer.value[0] != prev_left || + value.value.integer.value[1] != prev_right) { + snd_soc_put_volsw(k, &value); + snd_ctl_notify(component->card->snd_card, SNDRV_CTL_EVENT_MASK_VALUE, &k->id); + } + + regmap_update_bits(p->regmap, ADAU1961_PLAY_MIXER_MONO, + ADAU1961_PLAY_MIXER_MONO_EN, shorted ? 0 : ADAU1961_PLAY_MIXER_MONO_EN); +} +EXPORT_SYMBOL(adau1961_set_vgnd_shorted); + +bool adau1961_is_hp_capless(struct snd_soc_component *component) +{ + struct adau1961_platform_data *pdata = component->dev->platform_data; + + return pdata->headphone_mode == ADAU1961_OUTPUT_MODE_HEADPHONE_CAPLESS; +} +EXPORT_SYMBOL(adau1961_is_hp_capless); + +static int upisnd_put_hp_mute_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct adau *adau = snd_soc_component_get_drvdata(component); + + if (adau->vgnd_shorted) { + adau->hpl_unmuted = ucontrol->value.integer.value[0]; + adau->hpr_unmuted = ucontrol->value.integer.value[1]; + + if (ucontrol->value.integer.value[0] != 0 || + ucontrol->value.integer.value[1] != 0) { + printe("Ignoring unmute request while VGND is shorted!"); + + // Ensure alsamixer shows the correct state. + snd_ctl_notify(component->card->snd_card, + SNDRV_CTL_EVENT_MASK_VALUE, + &kcontrol->id + ); + return 0; + } + } + + return snd_soc_put_volsw(kcontrol, ucontrol); +} + +static const struct reg_default adau1961_reg_defaults[] = { + { ADAU1961_DEJITTER, 0x03 }, + { ADAU1961_REC_MIXER_LEFT0, 0x01 }, + { ADAU1961_REC_MIXER_LEFT1, 0x00 }, + { ADAU1961_REC_MIXER_RIGHT0, 0x01 }, + { ADAU1961_REC_MIXER_RIGHT1, 0x00 }, + { ADAU1961_LEFT_DIFF_INPUT_VOL, 0x00 }, + { ADAU1961_ALC_CTRL0, 0x00 }, + { ADAU1961_ALC_CTRL1, 0x00 }, + { ADAU1961_ALC_CTRL2, 0x00 }, + { ADAU1961_ALC_CTRL3, 0x00 }, + { ADAU1961_RIGHT_DIFF_INPUT_VOL, 0x00 }, + { ADAU1961_PLAY_LR_MIXER_LEFT, 0x01 }, + { ADAU1961_PLAY_MIXER_LEFT0, 0x00 }, + { ADAU1961_PLAY_MIXER_LEFT1, 0x00 }, + { ADAU1961_PLAY_MIXER_RIGHT0, 0x00 }, + { ADAU1961_PLAY_MIXER_RIGHT1, 0x00 }, + { ADAU1961_PLAY_LR_MIXER_RIGHT, 0x01 }, + { ADAU1961_PLAY_MIXER_MONO, 0x00 }, + { ADAU1961_PLAY_HP_LEFT_VOL, 0x00 }, + { ADAU1961_PLAY_HP_RIGHT_VOL, 0x00 }, + { ADAU1961_PLAY_LINE_LEFT_VOL, 0x00 }, + { ADAU1961_PLAY_LINE_RIGHT_VOL, 0x00 }, + { ADAU1961_PLAY_MONO_OUTPUT_VOL, 0x02 }, + { ADAU1961_POP_CLICK_SUPPRESS, 0x00 }, + { ADAU1961_JACK_DETECT_PIN, 0x00 }, + { ADAU1961_CLOCK_CONTROL, 0x00 }, + { ADAU1961_PLL_CONTROL, 0x00 }, + { ADAU1961_MICBIAS, 0x00 }, + { ADAU1961_SERIAL_PORT0, 0x00 }, + { ADAU1961_SERIAL_PORT1, 0x00 }, + { ADAU1961_CONVERTER0, 0x00 }, + { ADAU1961_CONVERTER1, 0x00 }, + { ADAU1961_LEFT_INPUT_DIGITAL_VOL, 0x00 }, + { ADAU1961_RIGHT_INPUT_DIGITAL_VOL, 0x00 }, + { ADAU1961_ADC_CONTROL, 0x00 }, + { ADAU1961_PLAY_POWER_MGMT, 0x03 }, + { ADAU1961_DAC_CONTROL0, 0x00 }, + { ADAU1961_DAC_CONTROL1, 0x00 }, + { ADAU1961_DAC_CONTROL2, 0x00 }, + { ADAU1961_SERIAL_PORT_PAD, 0xaa }, + { ADAU1961_CONTROL_PORT_PAD0, 0xaa }, + { ADAU1961_CONTROL_PORT_PAD1, 0x00 }, +}; + +static const DECLARE_TLV_DB_RANGE(adau1961_mono_output_tlv, + 1, 1, TLV_DB_SCALE_ITEM(0, 0, 0), // 0dB MX7[2:1]=01 + 2, 2, TLV_DB_SCALE_ITEM(600, 0, 0) // +6dB MX7[2:1]=10 +); + +static const DECLARE_TLV_DB_SCALE(adau1961_sing_in_tlv, -1500, 300, 1); +static const DECLARE_TLV_DB_SCALE(adau1961_diff_in_tlv, -1200, 75, 0); +static const DECLARE_TLV_DB_SCALE(adau1961_out_tlv, -5700, 100, 0); +static const DECLARE_TLV_DB_SCALE(adau1961_sidetone_tlv, -1800, 300, 1); +static const DECLARE_TLV_DB_SCALE(adau1961_boost_tlv, -600, 600, 1); +static const DECLARE_TLV_DB_SCALE(adau1961_pga_boost_tlv, -2000, 2000, 1); + +static const DECLARE_TLV_DB_SCALE(adau1961_alc_max_gain_tlv, -1200, 600, 0); +static const DECLARE_TLV_DB_SCALE(adau1961_alc_target_tlv, -2850, 150, 0); +static const DECLARE_TLV_DB_SCALE(adau1961_alc_ng_threshold_tlv, -7650, 150, 0); + +static const unsigned int adau1961_pga_slew_time_values[] = { + 3, 0, 1, 2, +}; + +static const char * const adau1961_pga_slew_time_text[] = { + "Off", + "24 ms", + "48 ms", + "96 ms", +}; + +static const char * const adau1961_alc_function_text[] = { + "Off", + "Right", + "Left", + "Stereo", +}; + +static const char * const adau1961_alc_hold_time_text[] = { + "2.67 ms", + "5.34 ms", + "10.68 ms", + "21.36 ms", + "42.72 ms", + "85.44 ms", + "170.88 ms", + "341.76 ms", + "683.52 ms", + "1367 ms", + "2734.1 ms", + "5468.2 ms", + "10936 ms", + "21873 ms", + "43745 ms", + "87491 ms", +}; + +static const char * const adau1961_alc_attack_time_text[] = { + "6 ms", + "12 ms", + "24 ms", + "48 ms", + "96 ms", + "192 ms", + "384 ms", + "768 ms", + "1540 ms", + "3070 ms", + "6140 ms", + "12290 ms", + "24580 ms", + "49150 ms", + "98300 ms", + "196610 ms", +}; + +static const char * const adau1961_alc_decay_time_text[] = { + "24 ms", + "48 ms", + "96 ms", + "192 ms", + "384 ms", + "768 ms", + "15400 ms", + "30700 ms", + "61400 ms", + "12290 ms", + "24580 ms", + "49150 ms", + "98300 ms", + "196610 ms", + "393220 ms", + "786430 ms", +}; + +static const char * const adau1961_alc_ng_type_text[] = { + "Hold", + "Mute", + "Fade", + "Fade + Mute", +}; + +static SOC_VALUE_ENUM_SINGLE_DECL(adau1961_pga_slew_time_enum, + ADAU1961_ALC_CTRL0, 6, 0x3, adau1961_pga_slew_time_text, + adau1961_pga_slew_time_values); +static SOC_ENUM_SINGLE_DECL(adau1961_alc_function_enum, + ADAU1961_ALC_CTRL0, 0, adau1961_alc_function_text); +static SOC_ENUM_SINGLE_DECL(adau1961_alc_hold_time_enum, + ADAU1961_ALC_CTRL1, 4, adau1961_alc_hold_time_text); +static SOC_ENUM_SINGLE_DECL(adau1961_alc_attack_time_enum, + ADAU1961_ALC_CTRL2, 4, adau1961_alc_attack_time_text); +static SOC_ENUM_SINGLE_DECL(adau1961_alc_decay_time_enum, + ADAU1961_ALC_CTRL2, 0, adau1961_alc_decay_time_text); +static SOC_ENUM_SINGLE_DECL(adau1961_alc_ng_type_enum, + ADAU1961_ALC_CTRL3, 6, adau1961_alc_ng_type_text); + +static const struct snd_kcontrol_new adau1961_differential_mode_controls[] = { + SOC_DOUBLE_R_TLV("Capture Volume", ADAU1961_LEFT_DIFF_INPUT_VOL, + ADAU1961_RIGHT_DIFF_INPUT_VOL, 2, 0x3f, 0, + adau1961_diff_in_tlv), + SOC_DOUBLE_R("Capture Switch", ADAU1961_LEFT_DIFF_INPUT_VOL, + ADAU1961_RIGHT_DIFF_INPUT_VOL, 1, 1, 0), + + SOC_DOUBLE_R_TLV("PGA Boost Capture Volume", ADAU1961_REC_MIXER_LEFT1, + ADAU1961_REC_MIXER_RIGHT1, 3, 2, 0, adau1961_pga_boost_tlv), + + SOC_ENUM("PGA Capture Slew Time", adau1961_pga_slew_time_enum), + + SOC_SINGLE_TLV("ALC Capture Max Gain Volume", ADAU1961_ALC_CTRL0, + 3, 7, 0, adau1961_alc_max_gain_tlv), + SOC_ENUM("ALC Capture Function", adau1961_alc_function_enum), + SOC_ENUM("ALC Capture Hold Time", adau1961_alc_hold_time_enum), + SOC_SINGLE_TLV("ALC Capture Target Volume", ADAU1961_ALC_CTRL1, + 0, 15, 0, adau1961_alc_target_tlv), + SOC_ENUM("ALC Capture Attack Time", adau1961_alc_decay_time_enum), + SOC_ENUM("ALC Capture Decay Time", adau1961_alc_attack_time_enum), + SOC_ENUM("ALC Capture Noise Gate Type", adau1961_alc_ng_type_enum), + SOC_SINGLE("ALC Capture Noise Gate Switch", + ADAU1961_ALC_CTRL3, 5, 1, 0), + SOC_SINGLE_TLV("ALC Capture Noise Gate Threshold Volume", + ADAU1961_ALC_CTRL3, 0, 31, 0, adau1961_alc_ng_threshold_tlv), +}; + +static const struct snd_kcontrol_new adau1961_single_mode_controls[] = { + SOC_SINGLE_TLV("Input 1 Capture Volume", ADAU1961_REC_MIXER_LEFT0, + 4, 7, 0, adau1961_sing_in_tlv), + SOC_SINGLE_TLV("Input 2 Capture Volume", ADAU1961_REC_MIXER_LEFT0, + 1, 7, 0, adau1961_sing_in_tlv), + SOC_SINGLE_TLV("Input 3 Capture Volume", ADAU1961_REC_MIXER_RIGHT0, + 4, 7, 0, adau1961_sing_in_tlv), + SOC_SINGLE_TLV("Input 4 Capture Volume", ADAU1961_REC_MIXER_RIGHT0, + 1, 7, 0, adau1961_sing_in_tlv), +}; + +static const struct snd_kcontrol_new adau1961_mono_controls[] = { + SOC_SINGLE_TLV("Mono Playback Volume", ADAU1961_PLAY_MONO_OUTPUT_VOL, + 2, 0x3f, 0, adau1961_out_tlv), + SOC_SINGLE("Mono Playback Switch", ADAU1961_PLAY_MONO_OUTPUT_VOL, + 1, 1, 0), + SOC_SINGLE_TLV("Mono Playback Boost Volume", ADAU1961_PLAY_MIXER_MONO, + 1, 2, 0, adau1961_mono_output_tlv), +}; + +static const struct snd_kcontrol_new adau1961_left_mixer_controls[] = { + SOC_DAPM_SINGLE_AUTODISABLE("Left DAC Switch", + ADAU1961_PLAY_MIXER_LEFT0, 5, 1, 0), + SOC_DAPM_SINGLE_AUTODISABLE("Right DAC Switch", + ADAU1961_PLAY_MIXER_LEFT0, 6, 1, 0), + SOC_DAPM_SINGLE_TLV("Aux Bypass Volume", + ADAU1961_PLAY_MIXER_LEFT0, 1, 8, 0, adau1961_sidetone_tlv), + SOC_DAPM_SINGLE_TLV("Right Bypass Volume", + ADAU1961_PLAY_MIXER_LEFT1, 4, 8, 0, adau1961_sidetone_tlv), + SOC_DAPM_SINGLE_TLV("Left Bypass Volume", + ADAU1961_PLAY_MIXER_LEFT1, 0, 8, 0, adau1961_sidetone_tlv), +}; + +static const struct snd_kcontrol_new adau1961_right_mixer_controls[] = { + SOC_DAPM_SINGLE_AUTODISABLE("Left DAC Switch", + ADAU1961_PLAY_MIXER_RIGHT0, 5, 1, 0), + SOC_DAPM_SINGLE_AUTODISABLE("Right DAC Switch", + ADAU1961_PLAY_MIXER_RIGHT0, 6, 1, 0), + SOC_DAPM_SINGLE_TLV("Aux Bypass Volume", + ADAU1961_PLAY_MIXER_RIGHT0, 1, 8, 0, adau1961_sidetone_tlv), + SOC_DAPM_SINGLE_TLV("Right Bypass Volume", + ADAU1961_PLAY_MIXER_RIGHT1, 4, 8, 0, adau1961_sidetone_tlv), + SOC_DAPM_SINGLE_TLV("Left Bypass Volume", + ADAU1961_PLAY_MIXER_RIGHT1, 0, 8, 0, adau1961_sidetone_tlv), +}; + +static const struct snd_kcontrol_new adau1961_left_lr_mixer_controls[] = { + SOC_DAPM_SINGLE_TLV("Left Volume", + ADAU1961_PLAY_LR_MIXER_LEFT, 1, 2, 0, adau1961_boost_tlv), + SOC_DAPM_SINGLE_TLV("Right Volume", + ADAU1961_PLAY_LR_MIXER_LEFT, 3, 2, 0, adau1961_boost_tlv), +}; + +static const struct snd_kcontrol_new adau1961_right_lr_mixer_controls[] = { + SOC_DAPM_SINGLE_TLV("Left Volume", + ADAU1961_PLAY_LR_MIXER_RIGHT, 1, 2, 0, adau1961_boost_tlv), + SOC_DAPM_SINGLE_TLV("Right Volume", + ADAU1961_PLAY_LR_MIXER_RIGHT, 3, 2, 0, adau1961_boost_tlv), +}; + +static const struct snd_kcontrol_new adau1961_controls[] = { + SOC_DOUBLE_R_TLV("Digital Capture Volume", + ADAU1961_LEFT_INPUT_DIGITAL_VOL, + ADAU1961_RIGHT_INPUT_DIGITAL_VOL, + 0, 0xff, 1, adau1961_digital_tlv), + SOC_DOUBLE_R_TLV("Digital Playback Volume", ADAU1961_DAC_CONTROL1, + ADAU1961_DAC_CONTROL2, 0, 0xff, 1, adau1961_digital_tlv), + + SOC_SINGLE("ADC High Pass Filter Switch", ADAU1961_ADC_CONTROL, + 5, 1, 0), + SOC_SINGLE("Playback De-emphasis Switch", ADAU1961_DAC_CONTROL0, + 2, 1, 0), + + SOC_DOUBLE_R_TLV("Aux Capture Volume", ADAU1961_REC_MIXER_LEFT1, + ADAU1961_REC_MIXER_RIGHT1, 0, 7, 0, adau1961_sing_in_tlv), + + SOC_DOUBLE_R_TLV("Headphone Playback Volume", ADAU1961_PLAY_HP_LEFT_VOL, + ADAU1961_PLAY_HP_RIGHT_VOL, 2, 0x3f, 0, adau1961_out_tlv), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Headphone Playback Switch", + .info = snd_soc_info_volsw, + .get = snd_soc_get_volsw, + .put = upisnd_put_hp_mute_volsw, + .private_value = SOC_DOUBLE_R_VALUE(ADAU1961_PLAY_HP_LEFT_VOL, + ADAU1961_PLAY_HP_RIGHT_VOL, 1, 0, 1, 0) + }, + SOC_DOUBLE_R_TLV("Lineout Playback Volume", ADAU1961_PLAY_LINE_LEFT_VOL, + ADAU1961_PLAY_LINE_RIGHT_VOL, 2, 0x3f, 0, adau1961_out_tlv), + SOC_DOUBLE_R("Lineout Playback Switch", ADAU1961_PLAY_LINE_LEFT_VOL, + ADAU1961_PLAY_LINE_RIGHT_VOL, 1, 1, 0), + SOC_SINGLE("MicBias Switch", ADAU1961_MICBIAS, 0, 1, 0), +}; + +static int adau1961_dejitter_fixup(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct adau *adau = snd_soc_component_get_drvdata(component); + + // After any power changes have been made the dejitter circuit + // has to be reinitialized. + regmap_write(adau->regmap, ADAU1961_DEJITTER, 0); + if (!adau->master) + regmap_write(adau->regmap, ADAU1961_DEJITTER, 3); + + return 0; +} + +static const struct snd_soc_dapm_widget adau1961_dapm_widgets[] = { + SND_SOC_DAPM_MIXER("Left Input Mixer", SND_SOC_NOPM, 0, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("Right Input Mixer", SND_SOC_NOPM, 0, 0, + NULL, 0), + + SND_SOC_DAPM_SUPPLY("MICBIAS", ADAU1961_MICBIAS, 0, 0, NULL, 0), + + SOC_MIXER_ARRAY("Left Playback Mixer", ADAU1961_PLAY_MIXER_LEFT0, + 0, 0, adau1961_left_mixer_controls), + SOC_MIXER_ARRAY("Right Playback Mixer", ADAU1961_PLAY_MIXER_RIGHT0, + 0, 0, adau1961_right_mixer_controls), + + SOC_MIXER_ARRAY("Left LR Playback Mixer", SND_SOC_NOPM, + 0, 0, adau1961_left_lr_mixer_controls), + SOC_MIXER_ARRAY("Right LR Playback Mixer", SND_SOC_NOPM, + 0, 0, adau1961_right_lr_mixer_controls), + + SND_SOC_DAPM_SUPPLY("Headphone", ADAU1961_PLAY_HP_LEFT_VOL, + 0, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY_S("SYSCLK", 2, SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_POST("Dejitter fixup", adau1961_dejitter_fixup), + + SND_SOC_DAPM_INPUT("LAUX"), + SND_SOC_DAPM_INPUT("RAUX"), + SND_SOC_DAPM_INPUT("LINP"), + SND_SOC_DAPM_INPUT("LINN"), + SND_SOC_DAPM_INPUT("RINP"), + SND_SOC_DAPM_INPUT("RINN"), + + SND_SOC_DAPM_OUTPUT("LOUT"), + SND_SOC_DAPM_OUTPUT("ROUT"), + SND_SOC_DAPM_OUTPUT("LHP"), + SND_SOC_DAPM_OUTPUT("RHP"), + + SND_SOC_DAPM_SUPPLY_S("PLL", 3, SND_SOC_NOPM, 0, 0, adau1961_pll_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_SUPPLY("AIFCLK", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("Left Playback Enable", SND_SOC_NOPM, + 0, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Right Playback Enable", SND_SOC_NOPM, + 1, 0, NULL, 0), + + SND_SOC_DAPM_MUX("Left DAC Mode Mux", SND_SOC_NOPM, 0, 0, + &adau1961_dac_mode_mux), + SND_SOC_DAPM_MUX("Right DAC Mode Mux", SND_SOC_NOPM, 0, 0, + &adau1961_dac_mode_mux), + + SND_SOC_DAPM_ADC_E("Left Decimator", NULL, ADAU1961_ADC_CONTROL, 0, 0, + adau1961_adc_fixup, SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_ADC("Right Decimator", NULL, ADAU1961_ADC_CONTROL, 1, 0), + SND_SOC_DAPM_DAC("Left DAC", NULL, ADAU1961_DAC_CONTROL0, 0, 0), + SND_SOC_DAPM_DAC("Right DAC", NULL, ADAU1961_DAC_CONTROL0, 1, 0), +}; + +static const struct snd_kcontrol_new adau1961_mono_mixer_controls[] = { + SOC_DAPM_SINGLE_TLV("Mono Mixer Gain", ADAU1961_PLAY_MIXER_MONO, + 1, 2, 0, adau1961_mono_output_tlv), +}; + +static const struct snd_soc_dapm_widget adau1961_mono_dapm_widgets[] = { + SND_SOC_DAPM_MIXER("Mono Playback Mixer", ADAU1961_PLAY_MIXER_MONO, + 0, 0, adau1961_mono_mixer_controls, + ARRAY_SIZE(adau1961_mono_mixer_controls)), + + SND_SOC_DAPM_OUTPUT("MONOOUT"), +}; + +static const struct snd_soc_dapm_widget adau1961_capless_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY_S("Headphone VGND", 1, ADAU1961_PLAY_MIXER_MONO, + 0, 0, NULL, 0), +}; + +static const struct snd_soc_dapm_route adau1961_dapm_routes[] = { + { "Left Input Mixer", NULL, "LINP" }, + { "Left Input Mixer", NULL, "LINN" }, + { "Left Input Mixer", NULL, "LAUX" }, + + { "Right Input Mixer", NULL, "RINP" }, + { "Right Input Mixer", NULL, "RINN" }, + { "Right Input Mixer", NULL, "RAUX" }, + + { "Left Input Mixer", NULL, "MICBIAS" }, + { "Right Input Mixer", NULL, "MICBIAS" }, + + { "Left Playback Mixer", NULL, "Left Playback Enable"}, + { "Right Playback Mixer", NULL, "Right Playback Enable"}, + { "Left LR Playback Mixer", NULL, "Left Playback Enable"}, + { "Right LR Playback Mixer", NULL, "Right Playback Enable"}, + + { "Left Playback Mixer", "Left DAC Switch", "Left DAC" }, + { "Left Playback Mixer", "Right DAC Switch", "Right DAC" }, + + { "Right Playback Mixer", "Left DAC Switch", "Left DAC" }, + { "Right Playback Mixer", "Right DAC Switch", "Right DAC" }, + + { "Left LR Playback Mixer", "Left Volume", "Left Playback Mixer" }, + { "Left LR Playback Mixer", "Right Volume", "Right Playback Mixer" }, + + { "Right LR Playback Mixer", "Left Volume", "Left Playback Mixer" }, + { "Right LR Playback Mixer", "Right Volume", "Right Playback Mixer" }, + + { "LHP", NULL, "Left Playback Mixer" }, + { "RHP", NULL, "Right Playback Mixer" }, + + { "LHP", NULL, "Headphone" }, + { "RHP", NULL, "Headphone" }, + + { "LOUT", NULL, "Left LR Playback Mixer" }, + { "ROUT", NULL, "Right LR Playback Mixer" }, + + { "Left Playback Mixer", "Aux Bypass Volume", "LAUX" }, + { "Left Playback Mixer", "Left Bypass Volume", "Left Input Mixer" }, + { "Left Playback Mixer", "Right Bypass Volume", "Right Input Mixer" }, + { "Right Playback Mixer", "Aux Bypass Volume", "RAUX" }, + { "Right Playback Mixer", "Left Bypass Volume", "Left Input Mixer" }, + { "Right Playback Mixer", "Right Bypass Volume", "Right Input Mixer" }, + + { "Left Decimator", NULL, "SYSCLK" }, + { "Right Decimator", NULL, "SYSCLK" }, + { "Left DAC", NULL, "SYSCLK" }, + { "Right DAC", NULL, "SYSCLK" }, + { "Capture", NULL, "SYSCLK" }, + { "Playback", NULL, "SYSCLK" }, + + { "Left DAC", NULL, "Left DAC Mode Mux" }, + { "Right DAC", NULL, "Right DAC Mode Mux" }, + + { "Capture", NULL, "AIFCLK" }, + { "Playback", NULL, "AIFCLK" }, + + { "Left DAC Mode Mux", "Stereo", "Playback" }, + { "Left DAC Mode Mux", "Mono (L+R)", "Playback" }, + { "Left DAC Mode Mux", "Mono Left Channel (L+R)", "Playback" }, + { "Right DAC Mode Mux", "Stereo", "Playback" }, + { "Right DAC Mode Mux", "Mono (L+R)", "Playback" }, + { "Right DAC Mode Mux", "Mono Right Channel (L+R)", "Playback" }, + { "Capture", NULL, "Left Decimator" }, + { "Capture", NULL, "Right Decimator" }, + + { "Left Decimator", NULL, "Left Input Mixer" }, + { "Right Decimator", NULL, "Right Input Mixer" }, +}; + +static const struct snd_soc_dapm_route adau1961_mono_dapm_routes[] = { + { "Mono Playback Mixer", NULL, "Left Playback Mixer" }, + { "Mono Playback Mixer", NULL, "Right Playback Mixer" }, + + { "MONOOUT", NULL, "Mono Playback Mixer" }, +}; + +static const struct snd_soc_dapm_route adau1961_capless_dapm_routes[] = { + { "Headphone", NULL, "Headphone VGND" }, +}; + +static int adau1961_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct adau *adau = snd_soc_component_get_drvdata(component); + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + regcache_cache_only(adau->regmap, false); + regmap_update_bits(adau->regmap, ADAU1961_CLOCK_CONTROL, + ADAU1961_CLOCK_CONTROL_SYSCLK_EN, + ADAU1961_CLOCK_CONTROL_SYSCLK_EN); + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) + regcache_sync(adau->regmap); + break; + case SND_SOC_BIAS_OFF: + regmap_update_bits(adau->regmap, ADAU1961_CLOCK_CONTROL, + ADAU1961_CLOCK_CONTROL_SYSCLK_EN, 0); + regcache_cache_only(adau->regmap, true); + break; + } + return 0; +} + +static enum adau1961_output_mode adau1961_get_lineout_mode(struct snd_soc_component *component) +{ + struct adau1961_platform_data *pdata = component->dev->platform_data; + + if (pdata) + return pdata->lineout_mode; + + return ADAU1961_OUTPUT_MODE_LINE; +} + +static enum adau1961_output_mode adau1961_get_monoout_mode(struct snd_soc_component *component) +{ + struct adau1961_platform_data *pdata = component->dev->platform_data; + + if (pdata) + return pdata->monoout_mode; + + return ADAU1961_OUTPUT_MODE_LINE; +} + +static int adau1961_setup_headphone_mode(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct adau *adau = snd_soc_component_get_drvdata(component); + struct adau1961_platform_data *pdata = component->dev->platform_data; + enum adau1961_output_mode mode; + int ret; + + if (pdata) + mode = pdata->headphone_mode; + else + mode = ADAU1961_OUTPUT_MODE_HEADPHONE; + + switch (mode) { + case ADAU1961_OUTPUT_MODE_LINE: + break; + case ADAU1961_OUTPUT_MODE_HEADPHONE_CAPLESS: + regmap_update_bits(adau->regmap, ADAU1961_PLAY_MONO_OUTPUT_VOL, + ADAU1961_PLAY_MONO_OUTPUT_VOL_MODE_HP | + ADAU1961_PLAY_MONO_OUTPUT_VOL_UNMUTE, + ADAU1961_PLAY_MONO_OUTPUT_VOL_MODE_HP | + ADAU1961_PLAY_MONO_OUTPUT_VOL_UNMUTE); + fallthrough; + case ADAU1961_OUTPUT_MODE_HEADPHONE: + regmap_update_bits(adau->regmap, ADAU1961_PLAY_HP_RIGHT_VOL, + ADAU1961_PLAY_HP_RIGHT_VOL_MODE_HP, + ADAU1961_PLAY_HP_RIGHT_VOL_MODE_HP); + break; + default: + return -EINVAL; + } + + if (mode == ADAU1961_OUTPUT_MODE_HEADPHONE_CAPLESS) { + ret = snd_soc_dapm_new_controls(dapm, + adau1961_capless_dapm_widgets, + ARRAY_SIZE(adau1961_capless_dapm_widgets)); + if (ret) + return ret; + ret = snd_soc_dapm_add_routes(dapm, + adau1961_capless_dapm_routes, + ARRAY_SIZE(adau1961_capless_dapm_routes)); + } else { + ret = snd_soc_add_component_controls(component, adau1961_mono_controls, + ARRAY_SIZE(adau1961_mono_controls)); + if (ret) + return ret; + ret = snd_soc_dapm_new_controls(dapm, + adau1961_mono_dapm_widgets, + ARRAY_SIZE(adau1961_mono_dapm_widgets)); + if (ret) + return ret; + ret = snd_soc_dapm_add_routes(dapm, + adau1961_mono_dapm_routes, + ARRAY_SIZE(adau1961_mono_dapm_routes)); + } + + return ret; +} + +static bool adau1961_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ADAU1961_REC_MIXER_LEFT0: + case ADAU1961_REC_MIXER_LEFT1: + case ADAU1961_REC_MIXER_RIGHT0: + case ADAU1961_REC_MIXER_RIGHT1: + case ADAU1961_LEFT_DIFF_INPUT_VOL: + case ADAU1961_RIGHT_DIFF_INPUT_VOL: + case ADAU1961_PLAY_LR_MIXER_LEFT: + case ADAU1961_PLAY_MIXER_LEFT0: + case ADAU1961_PLAY_MIXER_LEFT1: + case ADAU1961_PLAY_MIXER_RIGHT0: + case ADAU1961_PLAY_MIXER_RIGHT1: + case ADAU1961_PLAY_LR_MIXER_RIGHT: + case ADAU1961_PLAY_MIXER_MONO: + case ADAU1961_PLAY_HP_LEFT_VOL: + case ADAU1961_PLAY_HP_RIGHT_VOL: + case ADAU1961_PLAY_LINE_LEFT_VOL: + case ADAU1961_PLAY_LINE_RIGHT_VOL: + case ADAU1961_PLAY_MONO_OUTPUT_VOL: + case ADAU1961_POP_CLICK_SUPPRESS: + case ADAU1961_JACK_DETECT_PIN: + case ADAU1961_DEJITTER: + case ADAU1961_ALC_CTRL0: + case ADAU1961_ALC_CTRL1: + case ADAU1961_ALC_CTRL2: + case ADAU1961_ALC_CTRL3: + case ADAU1961_CLOCK_CONTROL: + case ADAU1961_PLL_CONTROL: + case ADAU1961_MICBIAS: + case ADAU1961_SERIAL_PORT0: + case ADAU1961_SERIAL_PORT1: + case ADAU1961_CONVERTER0: + case ADAU1961_CONVERTER1: + case ADAU1961_LEFT_INPUT_DIGITAL_VOL: + case ADAU1961_RIGHT_INPUT_DIGITAL_VOL: + case ADAU1961_ADC_CONTROL: + case ADAU1961_PLAY_POWER_MGMT: + case ADAU1961_DAC_CONTROL0: + case ADAU1961_DAC_CONTROL1: + case ADAU1961_DAC_CONTROL2: + case ADAU1961_SERIAL_PORT_PAD: + case ADAU1961_CONTROL_PORT_PAD0: + case ADAU1961_CONTROL_PORT_PAD1: + return true; + default: + break; + } + + return false; +} + +static int adau1961_component_probe(struct snd_soc_component *component) +{ + struct adau1961_platform_data *pdata = component->dev->platform_data; + struct adau *adau = snd_soc_component_get_drvdata(component); + int ret; + + if (pdata && pdata->input_differential) { + regmap_update_bits(adau->regmap, ADAU1961_LEFT_DIFF_INPUT_VOL, + ADAU1961_DIFF_INPUT_VOL_LDEN, + ADAU1961_DIFF_INPUT_VOL_LDEN); + regmap_update_bits(adau->regmap, ADAU1961_RIGHT_DIFF_INPUT_VOL, + ADAU1961_DIFF_INPUT_VOL_LDEN, + ADAU1961_DIFF_INPUT_VOL_LDEN); + ret = snd_soc_add_component_controls(component, + adau1961_differential_mode_controls, + ARRAY_SIZE(adau1961_differential_mode_controls)); + if (ret) + return ret; + } else { + ret = snd_soc_add_component_controls(component, + adau1961_single_mode_controls, + ARRAY_SIZE(adau1961_single_mode_controls)); + if (ret) + return ret; + } + + switch (adau1961_get_lineout_mode(component)) { + case ADAU1961_OUTPUT_MODE_LINE: + break; + case ADAU1961_OUTPUT_MODE_HEADPHONE: + regmap_update_bits(adau->regmap, ADAU1961_PLAY_LINE_LEFT_VOL, + ADAU1961_PLAY_LINE_LEFT_VOL_MODE_HP, + ADAU1961_PLAY_LINE_LEFT_VOL_MODE_HP); + regmap_update_bits(adau->regmap, ADAU1961_PLAY_LINE_RIGHT_VOL, + ADAU1961_PLAY_LINE_RIGHT_VOL_MODE_HP, + ADAU1961_PLAY_LINE_RIGHT_VOL_MODE_HP); + break; + default: + return -EINVAL; + } + + switch (adau1961_get_monoout_mode(component)) { + case ADAU1961_OUTPUT_MODE_LINE: + break; + case ADAU1961_OUTPUT_MODE_HEADPHONE: + regmap_update_bits(adau->regmap, ADAU1961_PLAY_MONO_OUTPUT_VOL, + ADAU1961_PLAY_MONO_OUTPUT_VOL_MODE_HP, + ADAU1961_PLAY_MONO_OUTPUT_VOL_MODE_HP); + break; + default: + return -EINVAL; + } + + ret = adau1961_setup_headphone_mode(component); + if (ret) + return ret; + + ret = adau1961_add_routes(component); + if (ret < 0) + return ret; + + return 0; +} + +static const struct snd_soc_component_driver adau1961_component_driver = { + .probe = adau1961_component_probe, + .resume = adau1961_resume, + .set_bias_level = adau1961_set_bias_level, + .controls = adau1961_controls, + .num_controls = ARRAY_SIZE(adau1961_controls), + .dapm_widgets = adau1961_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(adau1961_dapm_widgets), + .dapm_routes = adau1961_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(adau1961_dapm_routes), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .legacy_dai_naming = 0, +}; + +#define ADAU1961_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver adau1961_dai_driver = { + .name = "adau-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = ADAU1961_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = ADAU1961_FORMATS, + }, + .ops = &adau1961_dai_ops, +}; + +static int adau1961_probe(struct device *dev, struct regmap *regmap, + void (*switch_mode)(struct device *dev)) +{ + struct snd_soc_dai_driver *dai_drv; + int ret; + + dai_drv = &adau1961_dai_driver; + + struct adau *adau; + + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + adau = devm_kzalloc(dev, sizeof(*adau), GFP_KERNEL); + if (!adau) + return -ENOMEM; + + adau->mclk = devm_clk_get(dev, "mclk"); + if (IS_ERR(adau->mclk)) { + if (PTR_ERR(adau->mclk) != -ENOENT) + return PTR_ERR(adau->mclk); + /* Clock is optional (for the driver) */ + adau->mclk = NULL; + } else if (adau->mclk) { + adau->clk_src = ADAU1961_CLK_SRC_PLL_AUTO; + + /* + * Any valid PLL output rate will work at this point, use one + * that is likely to be chosen later as well. The register will + * be written when the PLL is powered up for the first time. + */ + ret = adau_calc_pll_cfg(clk_get_rate(adau->mclk), 48000 * 1024, + adau->pll_regs); + if (ret < 0) + return ret; + + ret = clk_prepare_enable(adau->mclk); + if (ret) + return ret; + } + + adau->regmap = regmap; + adau->switch_mode = switch_mode; + + dev_set_drvdata(dev, adau); + + if (switch_mode) + switch_mode(dev); + + // Enable cache only mode as we could miss writes before bias level + // reaches standby and the core clock is enabled + regcache_cache_only(regmap, true); + + return devm_snd_soc_register_component(dev, &adau1961_component_driver, dai_drv, 1); +} + +static const struct regmap_config adau1961_regmap_config = { + .val_bits = 8, + .reg_bits = 16, + .max_register = 0x4036, + .reg_defaults = adau1961_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(adau1961_reg_defaults), + .readable_reg = adau1961_readable_register, + .volatile_reg = adau1961_volatile_register, + .precious_reg = adau1961_precious_register, + .cache_type = REGCACHE_RBTREE, +}; + +static int upisnd_codec_fill_pdata(struct device_node *of_node, + struct adau1961_platform_data *pdata) +{ + const char *input_mode = "differential"; + const char *hp_out_mode = "capless-headphone"; + const char *line_out_mode = "line-out"; + const char *mono_out_mode = "line-out"; + + of_property_read_string(of_node, "input-mode", &input_mode); + of_property_read_string(of_node, "hp-out-mode", &hp_out_mode); + of_property_read_string(of_node, "line-out-mode", &line_out_mode); + of_property_read_string(of_node, "mono-out-mode", &mono_out_mode); + + if (strncmp(input_mode, "differential", 13) == 0) { + pdata->input_differential = true; + } else if (strncmp(input_mode, "single-ended", 13) == 0) { + pdata->input_differential = false; + } else { + printe("Invalid input mode: %s", input_mode); + return -EINVAL; + } + + if (strncmp(hp_out_mode, "headphone", 10) == 0) { + pdata->headphone_mode = ADAU1961_OUTPUT_MODE_HEADPHONE; + } else if (strncmp(hp_out_mode, "capless-headphone", 18) == 0) { + pdata->headphone_mode = ADAU1961_OUTPUT_MODE_HEADPHONE_CAPLESS; + } else if (strncmp(hp_out_mode, "line-out", 9) == 0) { + pdata->headphone_mode = ADAU1961_OUTPUT_MODE_LINE; + } else { + printe("Invalid headphone output mode: %s", hp_out_mode); + return -EINVAL; + } + + if (strncmp(line_out_mode, "line-out", 9) == 0) { + pdata->lineout_mode = ADAU1961_OUTPUT_MODE_LINE; + } else if (strncmp(line_out_mode, "headphone", 10) == 0) { + pdata->lineout_mode = ADAU1961_OUTPUT_MODE_HEADPHONE; + } else { + printe("Invalid line-out mode: %s", line_out_mode); + return -EINVAL; + } + + if (strncmp(mono_out_mode, "line-out", 9) == 0) { + pdata->monoout_mode = ADAU1961_OUTPUT_MODE_LINE; + } else if (strncmp(line_out_mode, "headphone", 10) == 0) { + pdata->monoout_mode = ADAU1961_OUTPUT_MODE_HEADPHONE; + } else { + printe("Invalid mono-out mode: %s", mono_out_mode); + return -EINVAL; + } + + return 0; +} + +static int upisnd_codec_i2c_probe(struct i2c_client *client) +{ + struct adau1961_platform_data *pdata = NULL; + + if (client->dev.of_node) { + pdata = devm_kzalloc(&client->dev, + sizeof(struct adau1961_platform_data), GFP_KERNEL); + + if (!pdata) + return -ENOMEM; + + int err = upisnd_codec_fill_pdata(client->dev.of_node, pdata); + + if (err < 0) { + printe("Failed to fill platform data from device tree! (%d)", err); + return err; + } + } + + client->dev.platform_data = pdata; + + struct regmap_config config; + + config = adau1961_regmap_config; + config.val_bits = 8; + config.reg_bits = 16; + + return adau1961_probe(&client->dev, devm_regmap_init_i2c(client, &config), NULL); +} + +static void upisnd_codec_i2c_remove(struct i2c_client *client) +{ + adau1961_remove(&client->dev); +} + +static const struct i2c_device_id upisnd_codec_i2c_ids[] = { + { "upisnd-codec", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, upisnd_codec_i2c_ids); + +static const struct of_device_id upisnd_codec_i2c_dt_ids[] = { + { .compatible = "blokas,upisnd-codec", }, + { }, +}; +MODULE_DEVICE_TABLE(of, upisnd_codec_i2c_dt_ids); + +struct i2c_driver upisnd_codec_i2c_driver = { + .driver = { + .name = "upisnd_codec", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(upisnd_codec_i2c_dt_ids), + }, + .probe = upisnd_codec_i2c_probe, + .remove = upisnd_codec_i2c_remove, + .id_table = upisnd_codec_i2c_ids, +}; +module_i2c_driver(upisnd_codec_i2c_driver); + +MODULE_DESCRIPTION("Codec Driver for Pisound Micro"); +MODULE_AUTHOR("Giedrius Trainavi\xc4\x8dius "); +MODULE_LICENSE("GPL v2"); + +/* vim: set ts=8 sw=8 noexpandtab: */ diff --git a/sound/drivers/upisnd/upisnd_codec.h b/sound/drivers/upisnd/upisnd_codec.h new file mode 100644 index 000000000000..c82be33711ff --- /dev/null +++ b/sound/drivers/upisnd/upisnd_codec.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Pisound Micro Linux kernel module. + * Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 of the + * License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + + #ifndef UPISOUND_CODEC_H + #define UPISOUND_CODEC_H + +extern void adau1961_set_vgnd_shorted(struct snd_soc_component *component, bool shorted); +extern bool adau1961_is_hp_capless(struct snd_soc_component *component); + +#endif // UPISOUND_CODEC_H diff --git a/sound/drivers/upisnd/upisnd_comm.c b/sound/drivers/upisnd/upisnd_comm.c new file mode 100644 index 000000000000..361098324c1d --- /dev/null +++ b/sound/drivers/upisnd/upisnd_comm.c @@ -0,0 +1,638 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Pisound Micro Linux kernel module. + * Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 of the + * License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "upisnd_common.h" + +DECLARE_CRC8_TABLE(upisnd_crc8_table); +enum { UPISND_CRC8_POLYNOMIAL = 0x83 }; + +static upisnd_msg_id_t upisnd_next_msg_id(struct upisnd_instance *instance) +{ + instance->comm.msg_id_counter = instance->comm.msg_id_counter != 127 ? + instance->comm.msg_id_counter + 1 : 1; + + return instance->comm.msg_id_counter; +} + +struct upisnd_command_task_t { + struct list_head list; + upisnd_msg_id_t msg_id; + struct completion done; + int result; + u8 cmd[UPISND_MAX_PACKET_LENGTH]; + u8 response[UPISND_MAX_PACKET_LENGTH]; +}; + +static void upisnd_command_response(struct upisnd_command_task_t *task, + const struct upisnd_cmd_t *response, + unsigned int n) +{ + memcpy(task->response, response, min_t(unsigned int, n, UPISND_MAX_PACKET_LENGTH)); + if (upisnd_cmd_matches(response, UPISND_CMD_RESULT, sizeof(struct upisnd_cmd_result_t))) { + int response_code = ((const struct upisnd_cmd_result_t *)response)->result; + + printd("hi %p %d %d", task, response_code == -ETIME, response_code); + task->result = response_code; + } else { + task->result = 0; + } +} + +static void upisnd_command_timeout(struct upisnd_command_task_t *task) +{ + struct upisnd_cmd_result_t result; + + result.cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_RESULT, sizeof(result)); + result.cmd.flags_and_msg_id = upisnd_msg_id_encode(task->msg_id, true); + result.result = -ETIME; + upisnd_command_response(task, &result.cmd, sizeof(result)); +} + +static int upisnd_i2c_send(struct i2c_client *client, const void *data, int n) +{ + u8 buffer[UPISND_MAX_PACKET_LENGTH + 1]; + + memcpy(buffer, data, n); + buffer[n] = ~crc8(upisnd_crc8_table, buffer, n, CRC8_INIT_VALUE); + ++n; + +#ifdef UPISOUND_DEBUG + printk(KERN_DEBUG "%s: Sending %d bytes:", __func__, n); + int i; + + for (i = 0; i < n; ++i) + printk(KERN_CONT " %02x", buffer[i]); + printk(KERN_CONT "\n"); + + u8 check = crc8(upisnd_crc8_table, buffer, n, CRC8_INIT_VALUE); + + printd("CRC CHECK: %u, good: %u", check, CRC8_GOOD_VALUE(upisnd_crc8_table)); +#endif + + return i2c_master_send(client, buffer, n); +} + +static void upisnd_command_send(struct upisnd_instance *instance, + struct upisnd_command_task_t *task, + struct upisnd_cmd_t *cmd) +{ + mutex_lock(&instance->comm.comm_lock); + upisnd_i2c_send(instance->comm.client, cmd, upisnd_cmd_decode_length(cmd->cmd_and_size)); + mutex_unlock(&instance->comm.comm_lock); +} + +static void upisnd_init_command_task(struct upisnd_command_task_t *task) +{ + memset(task, 0, sizeof(struct upisnd_command_task_t)); + init_completion(&task->done); + task->result = -EINPROGRESS; + task->cmd[0] = UPISND_CMD_INVALID; +} + +static int upisnd_execute_void_command(struct upisnd_instance *instance, const void *cmd) +{ + mutex_lock(&instance->comm.comm_lock); + int result = upisnd_i2c_send(instance->comm.client, cmd, + upisnd_cmd_decode_length(*(const uint8_t *)cmd)); + + mutex_unlock(&instance->comm.comm_lock); + return result; +} + +static int upisnd_execute_command(struct upisnd_instance *instance, + struct upisnd_command_task_t *task) +{ + struct upisnd_cmd_t *cmd = (struct upisnd_cmd_t *)task->cmd; + + mutex_lock(&instance->comm.msg_lock); + upisnd_msg_id_t msg_id = upisnd_next_msg_id(instance); + + cmd->flags_and_msg_id = upisnd_msg_id_encode(msg_id, false); + task->msg_id = msg_id; + list_add_tail(&task->list, &instance->comm.msg_handlers); + mutex_unlock(&instance->comm.msg_lock); + + upisnd_command_send(instance, task, cmd); + + printd("Before wait"); + unsigned long t = wait_for_completion_io_timeout(&task->done, msecs_to_jiffies(5000u)); + (void)t; + + mutex_lock(&instance->comm.msg_lock); + if (!completion_done(&task->done)) { + printe("Message %d timed out!", msg_id); + upisnd_command_timeout(task); + } + list_del(&task->list); + mutex_unlock(&instance->comm.msg_lock); + printd("Wait complete! %lu %d", t, task->result); + + return task->result; +} + +static void upisnd_handle_response(struct upisnd_instance *instance, + const struct upisnd_cmd_t *response, + unsigned int n) +{ + upisnd_msg_id_t msg_id = upisnd_msg_id_decode_id(response->flags_and_msg_id); + + printd("Response to %d", msg_id); + + struct list_head *p; + struct upisnd_command_task_t *t; + + mutex_lock(&instance->comm.msg_lock); + list_for_each(p, &instance->comm.msg_handlers) { + t = list_entry(p, struct upisnd_command_task_t, list); + if (t->msg_id == msg_id) { + upisnd_command_response(t, response, n); + t->msg_id = UPISND_MSG_ID_INVALID; + complete_all(&t->done); + break; + } + } + mutex_unlock(&instance->comm.msg_lock); +} + +void upisnd_handle_comm_interrupt(struct upisnd_instance *instance) +{ + printd("Comm handler"); + + u8 data[UPISND_MAX_PACKET_LENGTH + 1]; + + mutex_lock(&instance->comm.comm_lock); + int i, n = 0, err; + + err = i2c_master_recv(instance->comm.client, data, 3); + + if (err == 3 && data[0] != UPISND_CMD_INVALID) { + n = upisnd_cmd_decode_length(data[0]) + 1; // Includes CRC8 byte at the end. + if (n > 3) { + err = i2c_master_recv(instance->comm.client, data + 3, n - 3); + if (err != n - 3) { + printe("Error occurred when receiving data over I2C! (%d)", err); + mutex_unlock(&instance->comm.comm_lock); + return; + } + } + } else { + printe("Error occurred when receiving data over I2C! (%d)", err); + } + +#ifdef UPISOUND_DEBUG + printk(KERN_DEBUG "upisnd_handle_comm_interrupt: Read %d bytes:", n); + for (i = 0; i < n; ++i) + printk(KERN_CONT " %02x", data[i]); + printk(KERN_CONT "\n"); +#endif + + mutex_unlock(&instance->comm.comm_lock); + + if (n <= 0 || data[0] == UPISND_CMD_INVALID) { + printe("Error occurred when receiving data over I2C! (%d)", n); + return; + } + + u8 crc = crc8(upisnd_crc8_table, data, n, CRC8_INIT_VALUE); + + if (crc != CRC8_GOOD_VALUE(upisnd_crc8_table)) { + printe("CRC check failed, calculated value: %02x (expected value: %02x)", + crc, CRC8_GOOD_VALUE(upisnd_crc8_table)); + return; + } + + enum upisnd_cmd_type_e type = upisnd_cmd_decode_type(data[0]); + + if (upisnd_cmd_type_has_msg_id(type)) { + upisnd_handle_response(instance, (const struct upisnd_cmd_t *)data, n); + } else { + switch (type) { + case UPISND_CMD_MIDI: + instance->comm.handler_ops->handle_midi_data(instance, + &data[1], + upisnd_cmd_decode_length(data[0]) - 1 + ); + break; + case UPISND_CMD_IRQ_EVENT: + { + int j = 0; + int k = 0; + struct irq_event_t gpio_events[15]; + struct irq_event_t sound_events[15]; + unsigned int n = upisnd_cmd_decode_length(data[0]) - 1; + + for (i = 0; i < n; ++i) { + struct irq_event_t e; + + e.num = data[i + 1] & UPISND_IRQ_NUM_MASK; + e.high = (data[i + 1] & UPISND_ON_BIT_MASK) != 0; + + switch (e.num) { + case UPISND_IRQ_VGND_SHORT_ALERT: + sound_events[j++] = e; + break; + case UPISND_IRQ_GPIO_START...UPISND_IRQ_GPIO_END: + gpio_events[k++] = e; + break; + default: + printe("Unknown IRQ event %d", e.num); + continue; + } + } + + if (j > 0) { + instance->comm.handler_ops->handle_sound_irq_events + (instance, + sound_events, + j); + } + if (k > 0) { + instance->comm.handler_ops->handle_gpio_irq_events + (instance, + gpio_events, + k); + } + } + break; + case UPISND_CMD_CONTROL_EVENT: + { + const struct upisnd_cmd_control_event_t *cmd = + (const struct upisnd_cmd_control_event_t *)data; + int i; + unsigned int n = upisnd_cmd_decode_length(data[0]) + / sizeof(uint16_t); + struct control_event_t events[7]; + + printd("Received %u control events", n); + for (i = 0; i < n; ++i) { + u16 e = ntohs(cmd->values[i]); + + events[i].pin = e >> 10; + events[i].raw_value = e & 0x3ff; + printd("%d: %d 0x%04x u=%u d=%d", i, events[i].pin, + events[i].raw_value, events[i].raw_value, + events[i].raw_value); + } + + printd("Start handling control events"); + instance->comm.handler_ops->handle_control_events(instance, + events, + n); + printd("Done handling control events"); + } + break; + default: + printe("Unknown command received (%02x)", data[0]); + break; + } + } +} + +int upisnd_comm_module_init(void) +{ + crc8_populate_msb(upisnd_crc8_table, UPISND_CRC8_POLYNOMIAL); + return 0; +} + +int upisnd_comm_init(struct upisnd_instance *instance, + struct i2c_client *client, + const struct upisnd_comm_handler_ops *ops) +{ + struct upisnd_comm *comm = &instance->comm; + + mutex_init(&comm->comm_lock); + mutex_init(&comm->msg_lock); + + comm->client = client; + comm->handler_ops = ops; + + INIT_LIST_HEAD(&comm->msg_handlers); + comm->msg_id_counter = 0; + + return 0; +} + +int upisnd_comm_send_midi(struct upisnd_instance *instance, const void *data, unsigned int n) +{ + if (n == 0 || n >= UPISND_MAX_PACKET_LENGTH) + return -EINVAL; + + u8 buffer[UPISND_MAX_PACKET_LENGTH]; + + buffer[0] = upisnd_cmd_encode(UPISND_CMD_MIDI, n + 1); + memcpy(&buffer[1], data, n); + + mutex_lock(&instance->comm.comm_lock); + int err = upisnd_i2c_send(instance->comm.client, buffer, n + 1); + + mutex_unlock(&instance->comm.comm_lock); + + if (err < 0) + printe("Error occurred when sending MIDI data over I2C! (%d)", err); + + return err; +} + +int upisnd_comm_commit_setup(struct upisnd_instance *instance, upisnd_setup_t setup) +{ + struct upisnd_command_task_t task; + + upisnd_init_command_task(&task); + + struct upisnd_cmd_setup_t *cmd = (struct upisnd_cmd_setup_t *)task.cmd; + + cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_SETUP, sizeof(*cmd)); + cmd->setup = htonl(setup); + + int result = upisnd_execute_command(instance, &task); + + return result <= 0 ? result : 0; +} + +int upisnd_comm_gpio_set(struct upisnd_instance *instance, uint8_t pin, bool high) +{ + struct upisnd_cmd_set_gpio_t cmd; + + cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_SET_GPIO, sizeof(cmd)); + cmd.on_and_pin = pin | (high ? UPISND_ON_BIT_MASK : 0x00); + + return upisnd_execute_void_command(instance, &cmd); +} + +int upisnd_comm_gpio_get(struct upisnd_instance *instance, uint8_t pin) +{ + struct upisnd_command_task_t task; + + upisnd_init_command_task(&task); + + struct upisnd_cmd_get_gpio_t *cmd = (struct upisnd_cmd_get_gpio_t *)task.cmd; + + cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_GET_GPIO, sizeof(*cmd)); + cmd->pin = pin; + + return upisnd_execute_command(instance, &task); +} + +int upisnd_comm_gpio_set_all(struct upisnd_instance *instance, + const struct upisnd_all_gpio_state_t *state) +{ + struct upisnd_cmd_set_all_gpios_t cmd; + + cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_SET_ALL_GPIOS, sizeof(cmd)); + + memcpy(cmd.state.state, state->state, sizeof(cmd.state)); + + return upisnd_execute_void_command(instance, &cmd); +} + +int upisnd_comm_gpio_get_all(struct upisnd_instance *instance, + struct upisnd_all_gpio_state_t *result) +{ + int err; + struct upisnd_command_task_t task; + + upisnd_init_command_task(&task); + + struct upisnd_cmd_get_all_gpios_t *cmd = (struct upisnd_cmd_get_all_gpios_t *)task.cmd; + + cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_GET_ALL_GPIOS, + sizeof(struct upisnd_cmd_get_all_gpios_t)); + + err = upisnd_execute_command(instance, &task); + if (err >= 0) { + const struct upisnd_cmd_get_all_gpios_response_t *resp = + (const struct upisnd_cmd_get_all_gpios_response_t *)task.response; + + if (upisnd_cmd_is_response(&resp->cmd) && + upisnd_cmd_decode_type(resp->cmd.cmd_and_size) == UPISND_CMD_GET_ALL_GPIOS && + upisnd_cmd_decode_length(resp->cmd.cmd_and_size) == + sizeof(struct upisnd_cmd_get_all_gpios_response_t)) { + memcpy(result->state, resp->state.state, sizeof(result->state)); + err = 0; + } else { + err = -EINVAL; + } + } + + return err; +} + +int upisnd_comm_set_irq_types(struct upisnd_instance *instance, + const struct upisnd_irq_type_config_t *irq_type_config) +{ + struct upisnd_command_task_t task; + + upisnd_init_command_task(&task); + + struct upisnd_cmd_set_irq_types_t *cmd = (struct upisnd_cmd_set_irq_types_t *)task.cmd; + + cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_SET_IRQ_TYPES, sizeof(*cmd)); + memcpy(&cmd->config, irq_type_config, sizeof(struct upisnd_irq_type_config_t)); + + return upisnd_execute_command(instance, &task); +} + +int upisnd_comm_set_irq_masks(struct upisnd_instance *instance, + const struct upisnd_irq_mask_config_t *irq_mask_config) +{ + struct upisnd_command_task_t task; + + upisnd_init_command_task(&task); + + struct upisnd_cmd_set_irq_masks_t *cmd = (struct upisnd_cmd_set_irq_masks_t *)task.cmd; + + cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_SET_IRQ_MASKS, sizeof(*cmd)); + memcpy(&cmd->config, irq_mask_config, sizeof(struct upisnd_irq_mask_config_t)); + + return upisnd_execute_command(instance, &task); +} + +int upisnd_comm_set_subscription(struct upisnd_instance *instance, upisnd_irq_num_t irq, bool on) +{ + struct upisnd_command_task_t task; + + upisnd_init_command_task(&task); + + struct upisnd_cmd_set_subscription_t *cmd = + (struct upisnd_cmd_set_subscription_t *)task.cmd; + + cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_SET_SUBSCRIPTION, sizeof(*cmd)); + cmd->on_and_irq_num = (irq & UPISND_IRQ_NUM_MASK) | (on ? UPISND_ON_BIT_MASK : 0); + + return upisnd_execute_command(instance, &task); +} + +int upisnd_comm_get_version(struct upisnd_instance *instance, struct upisnd_version_t *result) +{ + memset(result, 0, sizeof(*result)); + + struct upisnd_command_task_t task; + + upisnd_init_command_task(&task); + + struct upisnd_cmd_get_t *cmd = (struct upisnd_cmd_get_t *)task.cmd; + + cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_GET, sizeof(*cmd)); + cmd->value_id = UPISND_VALUE_VERSION_INFO; + + int err = upisnd_execute_command(instance, &task); + + if (err >= 0) { + const struct upisnd_cmd_get_response_t *cmd = + (const struct upisnd_cmd_get_response_t *)task.response; + + if (upisnd_cmd_decode_length(cmd->cmd_get.cmd.cmd_and_size) == + UPISND_CMD_GET_RESPONSE_INT32_SIZE && + upisnd_cmd_is_response(&cmd->cmd_get.cmd) && + cmd->cmd_get.value_id == UPISND_VALUE_VERSION_INFO) { + u32 ver = (uint32_t)ntohl(cmd->value); + + result->bootloader_mode = (ver & 0x80000000) >> 31; + result->hwrev = (ver & 0x7f000000) >> 24; + result->major = (ver & 0x00ff0000) >> 16; + result->minor = (ver & 0x0000ff00) >> 8; + result->build = ver & 0x000000ff; + err = cmd->result; + } else { + err = -EINVAL; + } + } + + return err; +} + +int upisnd_comm_get_serial_number(struct upisnd_instance *instance, char result[12]) +{ + memset(result, 0, 12); + + struct upisnd_command_task_t task; + + upisnd_init_command_task(&task); + + struct upisnd_cmd_get_t *cmd = (struct upisnd_cmd_get_t *)task.cmd; + + cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_GET, sizeof(*cmd)); + cmd->value_id = UPISND_VALUE_SERIAL_NUMBER; + + int err = upisnd_execute_command(instance, &task); + + if (err >= 0) { + const struct upisnd_cmd_get_response_t *cmd = + (const struct upisnd_cmd_get_response_t *)task.response; + + if (upisnd_cmd_decode_length(cmd->cmd_get.cmd.cmd_and_size) == + (UPISND_CMD_GET_RESPONSE_DATA_MIN_SIZE + 11) && + upisnd_cmd_is_response(&cmd->cmd_get.cmd) && + cmd->cmd_get.value_id == UPISND_VALUE_SERIAL_NUMBER) { + memcpy(result, cmd->data, 11); + err = cmd->result; + } else { + err = -EINVAL; + } + } + + return err; +} + +int upisnd_comm_get_element_value(struct upisnd_instance *instance, uint8_t pin, int32_t *result) +{ + if (pin >= UPISND_NUM_GPIOS) + return -EINVAL; + + *result = 0; + + struct upisnd_command_task_t task; + + upisnd_init_command_task(&task); + + struct upisnd_cmd_get_t *cmd = (struct upisnd_cmd_get_t *)task.cmd; + + cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_GET, sizeof(*cmd)); + cmd->value_id = UPISND_VALUE_ELEMENT_VAL_BASE + pin; + + int err = upisnd_execute_command(instance, &task); + + if (err >= 0) { + const struct upisnd_cmd_get_response_t *cmd = + (const struct upisnd_cmd_get_response_t *)task.response; + + if (upisnd_cmd_decode_length(cmd->cmd_get.cmd.cmd_and_size) == + UPISND_CMD_GET_RESPONSE_INT32_SIZE && + upisnd_cmd_is_response(&cmd->cmd_get.cmd) && + cmd->cmd_get.value_id == UPISND_VALUE_ELEMENT_VAL_BASE + pin) { + *result = (int32_t)ntohl(cmd->value); + err = cmd->result; + } else { + err = -EINVAL; + } + } + + return err; +} + +int upisnd_comm_get_value(struct upisnd_instance *instance, + enum upisnd_value_id_t value_id, + int32_t *result) +{ + memset(result, 0, sizeof(*result)); + + struct upisnd_command_task_t task; + + upisnd_init_command_task(&task); + + struct upisnd_cmd_get_t *cmd = (struct upisnd_cmd_get_t *)task.cmd; + + cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_GET, sizeof(*cmd)); + cmd->value_id = value_id; + + int err = upisnd_execute_command(instance, &task); + + if (err >= 0) { + const struct upisnd_cmd_get_response_t *cmd = + (const struct upisnd_cmd_get_response_t *)task.response; + + if (upisnd_cmd_decode_length(cmd->cmd_get.cmd.cmd_and_size) == + UPISND_CMD_GET_RESPONSE_INT32_SIZE && + upisnd_cmd_is_response(&cmd->cmd_get.cmd) && + cmd->cmd_get.value_id == value_id) { + *result = (int32_t)ntohl(cmd->value); + err = cmd->result; + } else { + err = -EINVAL; + } + } + + return err; +} + +int upisnd_comm_set_value(struct upisnd_instance *instance, + enum upisnd_value_id_t value_id, + int32_t value) +{ + struct upisnd_command_task_t task; + + upisnd_init_command_task(&task); + + struct upisnd_cmd_set_t *cmd = (struct upisnd_cmd_set_t *)task.cmd; + + cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_SET, sizeof(*cmd)); + cmd->value_id = value_id; + cmd->value = htonl(value); + + return upisnd_execute_command(instance, &task); +} + +/* vim: set ts=8 sw=8 noexpandtab: */ diff --git a/sound/drivers/upisnd/upisnd_comm.h b/sound/drivers/upisnd/upisnd_comm.h new file mode 100644 index 000000000000..5ecbaa14f81a --- /dev/null +++ b/sound/drivers/upisnd/upisnd_comm.h @@ -0,0 +1,104 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Pisound Micro Linux kernel module. + * Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 of the + * License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef UPISOUND_COMM_H +#define UPISOUND_COMM_H + +struct upisnd_instance; + +struct upisnd_version_t { + uint8_t bootloader_mode:1; + uint8_t hwrev:7; + u8 major; + u8 minor; + u8 build; +}; + +struct irq_event_t { + upisnd_irq_num_t num; + bool high; +}; + +struct control_event_t { + upisnd_pin_t pin:6; + int16_t raw_value:10; +}; + +struct upisnd_comm_handler_ops { + void (*handle_midi_data)(struct upisnd_instance *instance, + const u8 *data, + unsigned int n); + void (*handle_gpio_irq_events)(struct upisnd_instance *instance, + const struct irq_event_t *events, + unsigned int n); + void (*handle_sound_irq_events)(struct upisnd_instance *instance, + const struct irq_event_t *events, + unsigned int n); + void (*handle_control_events)(struct upisnd_instance *instance, + const struct control_event_t *events, + unsigned int n); +}; + +struct upisnd_comm { + struct mutex comm_lock; + // comm_lock serializes I2C communication. + struct i2c_client *client; + + const struct upisnd_comm_handler_ops *handler_ops; + + // msg_lock protects the message handlers list and id. + struct mutex msg_lock; + struct list_head msg_handlers; + upisnd_msg_id_t msg_id_counter; +}; + +int upisnd_comm_module_init(void); +int upisnd_comm_init(struct upisnd_instance *instance, + struct i2c_client *client, + const struct upisnd_comm_handler_ops *ops); +int upisnd_comm_send_midi(struct upisnd_instance *instance, const void *data, unsigned int n); +int upisnd_comm_commit_setup(struct upisnd_instance *instance, upisnd_setup_t setup); +int upisnd_comm_gpio_set(struct upisnd_instance *instance, u8 pin, bool high); +int upisnd_comm_gpio_get(struct upisnd_instance *instance, u8 pin); +int upisnd_comm_gpio_set_all(struct upisnd_instance *instance, + const struct upisnd_all_gpio_state_t *state); +int upisnd_comm_gpio_get_all(struct upisnd_instance *instance, + struct upisnd_all_gpio_state_t *result); +int upisnd_comm_set_irq_types(struct upisnd_instance *instance, + const struct upisnd_irq_type_config_t *irq_type_config); +int upisnd_comm_set_irq_masks(struct upisnd_instance *instance, + const struct upisnd_irq_mask_config_t *irq_mask_config); +int upisnd_comm_set_subscription(struct upisnd_instance *instance, + upisnd_irq_num_t irq, bool on); +int upisnd_comm_get_version(struct upisnd_instance *instance, + struct upisnd_version_t *result); +int upisnd_comm_get_element_value(struct upisnd_instance *instance, + u8 pin, + int32_t *result); +int upisnd_comm_get_serial_number(struct upisnd_instance *instance, + char result[12]); +int upisnd_comm_get_value(struct upisnd_instance *instance, + enum upisnd_value_id_t value_id, + int32_t *result); +int upisnd_comm_set_value(struct upisnd_instance *instance, + enum upisnd_value_id_t value_id, + int32_t value); + +void upisnd_handle_comm_interrupt(struct upisnd_instance *instance); + +#endif // UPISOUND_COMM_H + +/* vim: set ts=8 sw=8 noexpandtab: */ diff --git a/sound/drivers/upisnd/upisnd_common.h b/sound/drivers/upisnd/upisnd_common.h new file mode 100644 index 000000000000..0f810de86030 --- /dev/null +++ b/sound/drivers/upisnd/upisnd_common.h @@ -0,0 +1,97 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Pisound Micro Linux kernel module. + * Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 of the + * License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef UPISOUND_COMMON_H +#define UPISOUND_COMMON_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include "upisnd_protocol.h" +#include "upisnd_comm.h" +#include "upisnd_debug.h" +#include "upisnd_midi.h" +#include "upisnd_sound.h" +#include "upisnd_sysfs.h" +#include "upisnd_ctrl.h" +#include "upisnd_pins.h" +#include "upisnd_gpio.h" +#include "upisnd_utils.h" + +enum upisnd_flags_e { + UPISND_FLAG_DUMMY = 1 << 0, + UPISND_FLAG_ADC_CALIBRATION = 1 << 1, +}; + +struct upisnd_instance { + struct kref refcount; + struct platform_device *pdev; + struct device *ctrl_dev; + struct device *codec_dev; + struct workqueue_struct *work_queue; + + struct upisnd_ctrl ctrl; + struct upisnd_comm comm; + struct upisnd_midi midi; + struct upisnd_gpio gpio; + + struct rw_semaphore rw_gpio_config_sem; + struct upisnd_config *config; + DECLARE_KFIFO(ctrl_event_fifo, struct control_event_t, 128); + struct work_struct ctrl_event_handler; + + struct snd_soc_card sound_card; + struct snd_soc_dai_link dai_link; + + u32 flags; +}; + +void upisnd_instance_release(struct kref *kref); + +#endif // UPISOUND_COMMON_H + +/* vim: set ts=8 sw=8 noexpandtab: */ diff --git a/sound/drivers/upisnd/upisnd_ctrl.c b/sound/drivers/upisnd/upisnd_ctrl.c new file mode 100644 index 000000000000..fcd0b326bef7 --- /dev/null +++ b/sound/drivers/upisnd/upisnd_ctrl.c @@ -0,0 +1,246 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Pisound Micro Linux kernel module. + * Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 of the + * License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "upisnd_common.h" + +static void upisnd_ctrl_handle_irq_events(struct upisnd_instance *instance, + const struct irq_event_t *events, + unsigned int n); +static void upisnd_ctrl_handle_control_events(struct upisnd_instance *instance, + const struct control_event_t *events, + unsigned int n); + +static const struct upisnd_comm_handler_ops upisnd_comm_handlers = { + .handle_midi_data = &upisnd_handle_midi_data, + .handle_gpio_irq_events = &upisnd_ctrl_handle_irq_events, + .handle_sound_irq_events = &upisnd_sound_handle_irq_events, + .handle_control_events = &upisnd_ctrl_handle_control_events, +}; + +static void upisnd_ctrl_cleanup(struct upisnd_instance *instance); + +static inline bool upisnd_has_data(struct upisnd_instance *instance) +{ + bool x = gpiod_get_value(instance->ctrl.data_available); + + printd("data_available = %d", x); + return x; +} + +static irqreturn_t upisnd_data_available_interrupt_handler(int irq, void *dev_id) +{ + printd("irq_handler called"); + + struct upisnd_instance *instance = dev_id; + + if (irq == instance->ctrl.client->irq) { + printd("handling comm interrupt"); + upisnd_handle_comm_interrupt(instance); + printd("handling done"); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static void upisnd_reset(struct upisnd_instance *instance) +{ + printd("Resetting..."); + + gpiod_direction_output(instance->ctrl.reset, 1); + usleep_range(1000, 5000); +#ifdef UPISOUND_DEV + gpiod_direction_input(ctrl->reset); +#else + gpiod_set_value(instance->ctrl.reset, 0); +#endif + msleep(30); + + upisnd_gpio_reset(instance); + + printd("Done!"); +} + +int upisnd_ctrl_probe(struct i2c_client *client) +{ + int err = 0; + + printd("Ctrl %p irq %d", client, client->irq); + printd("Dev %p", &client->dev); + printd("Data %p", dev_get_platdata(&client->dev)); + + struct upisnd_instance *instance = dev_get_platdata(&client->dev); + + if (!instance) + return -EPROBE_DEFER; + + struct upisnd_ctrl *ctrl = &instance->ctrl; + + ctrl->client = client; + + err = upisnd_gpio_init(instance); + if (err < 0) { + printe("GPIO init failed! %d", err); + goto cleanup; + } + + ctrl->reset = devm_gpiod_get_index(&client->dev, "reset", 0, GPIOD_ASIS); + if (IS_ERR(ctrl->reset)) { + printe("Failed getting reset gpio!"); + err = PTR_ERR(ctrl->reset); + ctrl->reset = NULL; + goto cleanup; + } + + upisnd_reset(instance); + + err = upisnd_comm_init(instance, client, &upisnd_comm_handlers); + if (err < 0) { + printe("Communication init failed! (%d)", err); + goto cleanup; + } + + ctrl->data_available = devm_gpiod_get_index(&client->dev, "data_available", 0, GPIOD_IN); + if (IS_ERR(ctrl->data_available)) { + printe("Failed getting data_available gpio!"); + err = PTR_ERR(ctrl->data_available); + ctrl->data_available = NULL; + goto cleanup; + } + + err = devm_request_threaded_irq(&client->dev, + ctrl->client->irq, + NULL, + upisnd_data_available_interrupt_handler, + IRQF_SHARED | IRQF_ONESHOT | IRQF_TRIGGER_LOW, + "data_available_int", + instance); + if (err != 0) { + printe("data_available IRQ request failed! %d", err); + goto cleanup; + } + + err = upisnd_comm_get_version(instance, &instance->ctrl.version); + if (err < 0) { + printe("Failed getting version! %d", err); + goto cleanup; + } + err = upisnd_comm_get_serial_number(instance, instance->ctrl.serial); + if (err < 0) { + printe("Failed getting serial number! %d", err); + goto cleanup; + } + + if (instance->ctrl.serial[0] != 'P' || + instance->ctrl.serial[1] != 'S' || + instance->ctrl.serial[2] != 'M' || + strlen(instance->ctrl.serial) != 11) { + printe("Unexpected serial number %s!", instance->ctrl.serial); + err = -EINVAL; + goto cleanup; + } + + printi("Pisound Micro %s version %d.%d.%d, hw rev: %d", + instance->ctrl.serial, + instance->ctrl.version.major, + instance->ctrl.version.minor, + instance->ctrl.version.build, + instance->ctrl.version.hwrev); + + if (instance->ctrl.version.bootloader_mode) { + printe("Pisound Micro is in bootloader mode! Please reflash the firmware, refer to documentation on https://blokas.io/"); + err = -EINVAL; + goto cleanup; + } + + const char *name = NULL; + + if (client->dev.of_node) { + err = of_property_read_string(client->dev.of_node, "kobj-name", &name); + + if (of_property_read_bool(client->dev.of_node, "adc-calibration")) + instance->flags |= UPISND_FLAG_ADC_CALIBRATION; + } + err = upisnd_sysfs_init(instance, name); + if (err != 0) { + printe("sysfs init failed! %d", err); + goto cleanup; + } + +cleanup: + if (err != 0) { + printe("Error %d!", err); + upisnd_ctrl_cleanup(instance); + return err; + } + + return 0; +} + +void upisnd_ctrl_remove(struct i2c_client *client) +{ + printd("Ctrl %p", client); + + struct upisnd_instance *instance = dev_get_platdata(&client->dev); + + if (!instance) + return; + + upisnd_ctrl_cleanup(instance); + + kref_put(&instance->refcount, &upisnd_instance_release); + client->dev.platform_data = NULL; +} + +static void upisnd_off(struct upisnd_instance *instance) +{ + printd("Turning off..."); + +#ifndef UPISOUND_DEV + gpiod_direction_output(instance->ctrl.reset, 1); +#else + upisnd_reset(&instance->ctrl); +#endif + printd("Done!"); +} + +static void upisnd_ctrl_cleanup(struct upisnd_instance *instance) +{ + printd("cleanup"); + upisnd_sysfs_uninit(instance); + + if (instance->ctrl.reset) { + upisnd_off(instance); + instance->ctrl.reset = NULL; + } +} + +static void upisnd_ctrl_handle_irq_events(struct upisnd_instance *instance, + const struct irq_event_t *events, + unsigned int n) +{ + upisnd_gpio_handle_irq_event(instance, events, n); + upisnd_sysfs_handle_irq_event(instance, events, n); +} + +static void upisnd_ctrl_handle_control_events(struct upisnd_instance *instance, + const struct control_event_t *events, + unsigned int n) +{ + upisnd_sysfs_handle_control_event(instance, events, n); +} + +/* vim: set ts=8 sw=8 noexpandtab: */ diff --git a/sound/drivers/upisnd/upisnd_ctrl.h b/sound/drivers/upisnd/upisnd_ctrl.h new file mode 100644 index 000000000000..6851a491cc6a --- /dev/null +++ b/sound/drivers/upisnd/upisnd_ctrl.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Pisound Micro Linux kernel module. + * Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 of the + * License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef UPISOUND_CTRL_H +#define UPISOUND_CTRL_H + +struct upisnd_ctrl { + struct i2c_client *client; + struct upisnd_gpio *gpio; + + struct upisnd_version_t version; + char serial[12]; + + struct gpio_desc *data_available; + struct gpio_desc *reset; +}; + +int upisnd_ctrl_probe(struct i2c_client *client); +void upisnd_ctrl_remove(struct i2c_client *client); + +#endif // UPISOUND_CTRL_H + +/* vim: set ts=8 sw=8 noexpandtab: */ diff --git a/sound/drivers/upisnd/upisnd_debug.h b/sound/drivers/upisnd/upisnd_debug.h new file mode 100644 index 000000000000..b0b3fda9451f --- /dev/null +++ b/sound/drivers/upisnd/upisnd_debug.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Pisound Micro Linux kernel module. + * Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 of the + * License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef UPISOUND_DEBUG_H +#define UPISOUND_DEBUG_H + +#define UPISOUND_LOG_IMPL(log_func, msg, ...) \ + log_func("pisound-micro(%s): " msg "\n", __func__, ## __VA_ARGS__) + +#ifdef UPISOUND_DEBUG +# define printd(...) UPISOUND_LOG_IMPL(pr_alert, __VA_ARGS__) +#else +# define printd(...) do {} while (0) +#endif + +#define printe(...) UPISOUND_LOG_IMPL(pr_err, __VA_ARGS__) +#define printi(...) UPISOUND_LOG_IMPL(pr_info, __VA_ARGS__) + +#endif // UPISOUND_DEBUG_H + +/* vim: set ts=8 sw=8 noexpandtab: */ diff --git a/sound/drivers/upisnd/upisnd_gpio.c b/sound/drivers/upisnd/upisnd_gpio.c new file mode 100644 index 000000000000..8b0df275792c --- /dev/null +++ b/sound/drivers/upisnd/upisnd_gpio.c @@ -0,0 +1,869 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Pisound Micro Linux kernel module. + * Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 of the + * License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "upisnd_common.h" + +enum upisnd_pinconf_dir_e { + UPISND_PINCONF_DIR_INPUT_PULL_AS_IS_OR_NONE = 0, + UPISND_PINCONF_DIR_INPUT_PULL_UP = 1, + UPISND_PINCONF_DIR_INPUT_PULL_DOWN = 2, + UPISND_PINCONF_DIR_OUTPUT_LOW = 4, + UPISND_PINCONF_DIR_OUTPUT_HIGH = 5, + + // If 3rd bit is set, direction is output, otherwise it's input. + UPISND_PINCONF_DIR_MASK = 0x04, +}; + +enum { UPISND_PINCONF_DIRECTION = (PIN_CONFIG_END + 1) }; // enum upisnd_pinconf_dir_e + +static int upisnd_gpio_chip_get_direction(struct gpio_chip *gc, unsigned int offset); +static int upisnd_gpio_chip_get(struct gpio_chip *gc, unsigned int offset); +static int upisnd_gpio_chip_set(struct gpio_chip *gc, unsigned int offset, int val); +static int upisnd_gpio_chip_get_multiple(struct gpio_chip *gc, + unsigned long *mask, + unsigned long *bits); +static int upisnd_gpio_chip_set_multiple(struct gpio_chip *gc, + unsigned long *mask, + unsigned long *bits); +static int upisnd_gpio_chip_set_config(struct gpio_chip *gc, + unsigned int offset, + unsigned long config); +static int upisnd_gpio_chip_dir_in(struct gpio_chip *gc, unsigned int offset); +static int upisnd_gpio_chip_dir_out(struct gpio_chip *gc, unsigned int offset, int val); +static int upisnd_gpio_chip_request(struct gpio_chip *gc, unsigned int offset); +static void upisnd_gpio_chip_free(struct gpio_chip *gc, unsigned int offset); + +static const char * const upisnd_gpio_names[UPISND_NUM_GPIOS] = { + "A27", "A28", "A29", "A30", "A31", "A32", "B03", "B04", + "B05", "B06", "B07", "B08", "B09", "B10", "B11", "B12", + "B13", "B14", "B15", "B16", "B17", "B18", "B23", "B24", + "B25", "B26", "B27", "B28", "B29", "B30", "B31", "B32", + "B33", "B34", "B37", "B38", "B39" +}; + +static const struct gpio_chip upisnd_gpio_chip = { + .label = "pisound-micro-gpio", + .owner = THIS_MODULE, + .request = &upisnd_gpio_chip_request, + .free = &upisnd_gpio_chip_free, + .direction_input = &upisnd_gpio_chip_dir_in, + .direction_output = &upisnd_gpio_chip_dir_out, + .get_direction = &upisnd_gpio_chip_get_direction, + .get = &upisnd_gpio_chip_get, + .set = &upisnd_gpio_chip_set, + .get_multiple = &upisnd_gpio_chip_get_multiple, + .set_multiple = &upisnd_gpio_chip_set_multiple, + .set_config = &upisnd_gpio_chip_set_config, + .base = -1, + .ngpio = UPISND_NUM_GPIOS, + .can_sleep = true, + .names = upisnd_gpio_names, +}; + +enum { + IRQ_FLAG_TYPES_CHANGED_BIT = 0, + IRQ_FLAG_MASKS_CHANGED_BIT = 1, + IRQ_FLAG_HANDLING_IRQ_BIT = 2, +}; + +static int upisnd_gpio_chip_get_direction(struct gpio_chip *gc, unsigned int offset) +{ + struct upisnd_instance *instance = gpiochip_get_data(gc); + struct upisnd_gpio *gpio = &instance->gpio; + + down_read(&instance->rw_gpio_config_sem); + upisnd_setup_t setup = gpio->pin_configs[offset].setup; + + up_read(&instance->rw_gpio_config_sem); + + int dir = -EINVAL; + + if (upisnd_setup_get_element_type(setup) == UPISND_ELEMENT_TYPE_GPIO) + dir = upisnd_setup_get_gpio_dir(setup) == UPISND_PIN_DIR_INPUT ? 1 : 0; + + printd("%p %u %d %08x", gc, offset, dir, setup); + return dir; +} + +static const char *upisnd_gpio_pull_to_str(enum upisnd_pin_pull_e pull) +{ + switch (pull) { + default: + case UPISND_PIN_PULL_NONE: return "pull_none"; + case UPISND_PIN_PULL_UP: return "pull_up"; + case UPISND_PIN_PULL_DOWN: return "pull_down"; + } +} + +static void upisnd_print_setup(const upisnd_setup_t setup) +{ + switch (upisnd_setup_get_element_type(setup)) { + case UPISND_ELEMENT_TYPE_NONE: + printi("Setup None %d", upisnd_setup_get_pin_id(setup)); + break; + case UPISND_ELEMENT_TYPE_ENCODER: + printi("Setup Encoder %d %s %d %s", + upisnd_setup_get_pin_id(setup), + upisnd_gpio_pull_to_str(upisnd_setup_get_gpio_pull(setup)), + upisnd_setup_get_encoder_pin_b_id(setup), + upisnd_gpio_pull_to_str(upisnd_setup_get_encoder_pin_b_pull(setup))); + break; + case UPISND_ELEMENT_TYPE_ANALOG_IN: + printi("Setup Analog In %d (%d)", + upisnd_setup_get_pin_id(setup), + upisnd_setup_get_pin_id(setup)); + break; + case UPISND_ELEMENT_TYPE_GPIO: + if (upisnd_setup_get_gpio_dir(setup) == UPISND_PIN_DIR_INPUT) + printi("Setup GPIO Input %d %s", + upisnd_setup_get_pin_id(setup), + upisnd_gpio_pull_to_str(upisnd_setup_get_gpio_pull(setup))); + else + printi("Setup GPIO Output %d %s", + upisnd_setup_get_pin_id(setup), + upisnd_setup_get_gpio_output(setup) ? "high" : "low"); + break; + case UPISND_ELEMENT_TYPE_ACTIVITY: + printi("Setup Activity %d %d", + upisnd_setup_get_pin_id(setup), + upisnd_setup_get_activity_type(setup)); + break; + default: + printe("Unknown Setup type %d", upisnd_setup_get_element_type(setup)); + break; + } +} + +int upisnd_gpio_setup(struct upisnd_instance *instance, upisnd_setup_t setup) +{ + int err; + +#ifdef UPISND_DEBUG + printd("Committing setup:"); + upisnd_print_setup(setup); + err = upisnd_comm_commit_setup(instance, setup); + printd("Result: %d", err) +#else + err = upisnd_comm_commit_setup(instance, setup); + + if (err < 0) { + printe("Failed to commit setup (%d), failed request:", err); + upisnd_print_setup(setup); + } +#endif + + return err; +} + +static int upisnd_gpio_chip_get(struct gpio_chip *gc, unsigned int offset) +{ + printd("%p %u", gc, offset); + + int value = -EINVAL; + + struct upisnd_instance *instance = gpiochip_get_data(gc); + + unsigned long state[2]; + + down_read(&instance->rw_gpio_config_sem); + upisnd_setup_t setup = instance->gpio.pin_configs[offset].setup; + + state[0] = instance->gpio.gpio_state[0]; + state[1] = instance->gpio.gpio_state[1]; + up_read(&instance->rw_gpio_config_sem); + + if (upisnd_setup_get_element_type(setup) == UPISND_ELEMENT_TYPE_GPIO) { + switch (upisnd_setup_get_gpio_dir(setup)) { + case UPISND_PIN_DIR_OUTPUT: + value = test_bit(offset, state) ? 1 : 0; + break; + case UPISND_PIN_DIR_INPUT: + if (test_bit(IRQ_FLAG_HANDLING_IRQ_BIT, &instance->gpio.irq_flags)) { + value = test_bit(offset, state) ? 1 : 0; + } else { + down_write(&instance->rw_gpio_config_sem); + value = upisnd_comm_gpio_get(instance, offset); + assign_bit(offset, instance->gpio.gpio_state, value); + up_write(&instance->rw_gpio_config_sem); + } + break; + default: + break; + } + } + + return value; +} + +static int upisnd_gpio_chip_set(struct gpio_chip *gc, unsigned int offset, int val) +{ + printd("%p %u %d", gc, offset, val); + + struct upisnd_instance *instance = gpiochip_get_data(gc); + + down_write(&instance->rw_gpio_config_sem); + upisnd_setup_t setup = instance->gpio.pin_configs[offset].setup; + + if (upisnd_setup_get_element_type(setup) == UPISND_ELEMENT_TYPE_GPIO) { + if (upisnd_setup_get_gpio_dir(setup) == UPISND_PIN_DIR_OUTPUT) { + bool on = val != 0; + + assign_bit(offset, instance->gpio.gpio_state, on); + upisnd_comm_gpio_set(instance, offset, on); + } + } + up_write(&instance->rw_gpio_config_sem); + return 0; +} + +static void encode_gpio_state(struct upisnd_all_gpio_state_t *s, const unsigned long *bits) +{ + u8 *p = (uint8_t *)s->state; + + p[0] = (bits[0]) & 0xff; + p[1] = (bits[0] >> 8) & 0xff; + p[2] = (bits[0] >> 16) & 0xff; + p[3] = (bits[0] >> 24) & 0xff; + p[4] = (bits[1]) & 0xff; +} + +static void decode_gpio_state(unsigned long *bits, const struct upisnd_all_gpio_state_t *s) +{ + const u8 *p = (const uint8_t *)s->state; + + bits[0] = + (p[0]) | + (p[1] << 8) | + (p[2] << 16) | + (p[3] << 24); + bits[1] = + (p[4]); +} + +static int upisnd_gpio_chip_get_multiple(struct gpio_chip *gc, + unsigned long *mask, + unsigned long *bits) +{ + unsigned int i; + int err = 0; + + memset(bits, 0, sizeof(bits[0]) * 2); + + printd("%p %02lx%08lx", gc, mask[1], mask[0]); + + struct upisnd_instance *instance = gpiochip_get_data(gc); + + unsigned long state[2]; + + down_read(&instance->rw_gpio_config_sem); + for_each_set_bit(i, mask, UPISND_NUM_GPIOS) { + upisnd_setup_t setup = instance->gpio.pin_configs[i].setup; + + if (upisnd_setup_get_element_type(setup) != UPISND_ELEMENT_TYPE_GPIO) { + printe("Pin %u is not configured as GPIO!", i); + err = -EINVAL; + } + } + up_read(&instance->rw_gpio_config_sem); + + if (err < 0) + return err; + + if (test_bit(IRQ_FLAG_HANDLING_IRQ_BIT, &instance->gpio.irq_flags)) { + state[0] = instance->gpio.gpio_state[0]; + state[1] = instance->gpio.gpio_state[1]; + } else { + struct upisnd_all_gpio_state_t s; + + down_write(&instance->rw_gpio_config_sem); + + err = upisnd_comm_gpio_get_all(instance, &s); + + if (err < 0) { + up_write(&instance->rw_gpio_config_sem); + return err; + } + + decode_gpio_state(state, &s); + + instance->gpio.gpio_state[0] = state[0]; + instance->gpio.gpio_state[1] = state[1]; + up_write(&instance->rw_gpio_config_sem); + } + + bits[0] = state[0] & mask[0]; + bits[1] = state[1] & mask[1]; + + return err; +} + +static int upisnd_gpio_chip_set_multiple(struct gpio_chip *gc, + unsigned long *mask, + unsigned long *bits) +{ + unsigned long state[2]; + + printd("%p %02lx%08lx %02lx%08lx", gc, mask[1], mask[0], bits[1], bits[0]); + + struct upisnd_instance *instance = gpiochip_get_data(gc); + + down_write(&instance->rw_gpio_config_sem); + + state[0] = (instance->gpio.gpio_state[0] & ~mask[0]) | (bits[0] & mask[0]); + state[1] = (instance->gpio.gpio_state[1] & ~mask[1]) | (bits[1] & mask[1]); + + struct upisnd_all_gpio_state_t s; + + encode_gpio_state(&s, state); + + upisnd_comm_gpio_set_all(instance, &s); + + instance->gpio.gpio_state[0] = state[0]; + instance->gpio.gpio_state[1] = state[1]; + + up_write(&instance->rw_gpio_config_sem); + return 0; +} + +static int upisnd_gpio_chip_dir_in(struct gpio_chip *gc, unsigned int offset) +{ + printd("%p %u", gc, offset); + return upisnd_gpio_chip_set_config(gc, offset, pinconf_to_config_packed + ((enum pin_config_param)UPISND_PINCONF_DIRECTION, + UPISND_PINCONF_DIR_INPUT_PULL_AS_IS_OR_NONE)); +} + +static int upisnd_gpio_chip_dir_out(struct gpio_chip *gc, unsigned int offset, int val) +{ + printd("%p %u %d", gc, offset, val); + return upisnd_gpio_chip_set_config(gc, offset, pinconf_to_config_packed + ((enum pin_config_param)UPISND_PINCONF_DIRECTION, + val ? UPISND_PINCONF_DIR_OUTPUT_HIGH : UPISND_PINCONF_DIR_OUTPUT_LOW)); +} + +static int upisnd_gpio_chip_request(struct gpio_chip *gc, unsigned int offset) +{ + printd("(%p, %u)", gc, offset); + + struct upisnd_instance *instance = gpiochip_get_data(gc); + + int err; + upisnd_setup_t setup = 0; + + down_write(&instance->rw_gpio_config_sem); + + if (upisnd_setup_get_element_type(instance->gpio.pin_configs[offset].setup) == + UPISND_ELEMENT_TYPE_GPIO) { + setup = instance->gpio.pin_configs[offset].setup; + } else { + upisnd_setup_set_element_type(&setup, UPISND_ELEMENT_TYPE_GPIO); + upisnd_setup_set_gpio_dir(&setup, UPISND_PIN_DIR_INPUT); + upisnd_setup_set_gpio_pull(&setup, UPISND_PIN_PULL_NONE); + upisnd_setup_set_pin_id(&setup, offset); + } + + // If the pin is already in use via setup interface. + if (instance->gpio.pin_configs[offset].element) { + // If the pin is not being requested via setup interface, deny the request. + if (!(instance->gpio.pin_configs[offset].flags & + UPISND_PIN_FLAG_CONFIGURING_THROUGH_SYSFS)) { + printe("Pin %u already exported through pisound-micro sysfs!", offset); + err = -EBUSY; + } else { + err = 0; + } + + up_write(&instance->rw_gpio_config_sem); + return err; + } + + err = upisnd_gpio_setup(instance, setup); + + if (err >= 0) { + instance->gpio.pin_configs[offset].element = NULL; + instance->gpio.pin_configs[offset].flags = 0; + instance->gpio.pin_configs[offset].setup = setup; + instance->gpio.pin_configs[offset].gpio_desc = NULL; + } + + up_write(&instance->rw_gpio_config_sem); + + return err; +} + +static void upisnd_gpio_chip_free(struct gpio_chip *gc, unsigned int offset) +{ + printd("(%p, %u)", gc, offset); + + struct upisnd_instance *instance = gpiochip_get_data(gc); + + down_write(&instance->rw_gpio_config_sem); + if (instance->gpio.pin_configs[offset].element || + (instance->gpio.pin_configs[offset].flags & UPISND_PIN_FLAG_CONFIGURING_THROUGH_SYSFS) + ) { + // Unsetup will be taken care of in upisnd_element_cleanup, + // ignore requests that come through gpiochip_free_own_desc. + up_write(&instance->rw_gpio_config_sem); + return; + } + + upisnd_setup_t setup = instance->gpio.pin_configs[offset].setup; + + upisnd_setup_set_element_type(&setup, UPISND_ELEMENT_TYPE_NONE); + upisnd_gpio_setup(instance, setup); + + memset(&instance->gpio.pin_configs[offset], 0, sizeof(struct upisnd_pin_config_t)); + + up_write(&instance->rw_gpio_config_sem); + + printd("Done"); +} + +static int upisnd_gpio_chip_set_config(struct gpio_chip *gc, + unsigned int offset, + unsigned long config) +{ + int p = pinconf_to_config_param(config); + u32 arg = pinconf_to_config_argument(config); + + printd("%p %u %08lx p=%d arg=%u", gc, offset, config, p, arg); + + struct upisnd_instance *instance = gpiochip_get_data(gc); + + down_write(&instance->rw_gpio_config_sem); + if (instance->gpio.pin_configs[offset].flags & UPISND_PIN_FLAG_CONFIGURING_THROUGH_SYSFS) { + // Setup has taken care of configuring the pin already, + // ignore requests that come through gpiochip_request_own_desc. + up_write(&instance->rw_gpio_config_sem); + return 0; + } + + int err = 0; + + upisnd_setup_t setup = instance->gpio.pin_configs[offset].setup; + + if (upisnd_setup_get_element_type(setup) != UPISND_ELEMENT_TYPE_GPIO) { + err = -EINVAL; + goto cleanup; + } + + switch (p) { + case PIN_CONFIG_BIAS_PULL_DOWN: + case PIN_CONFIG_BIAS_PULL_UP: + if (upisnd_setup_get_gpio_dir(setup) != UPISND_PIN_DIR_INPUT) { + err = -EINVAL; + goto cleanup; + } + if (arg != 0) + upisnd_setup_set_gpio_pull(&setup, p == PIN_CONFIG_BIAS_PULL_UP ? + UPISND_PIN_PULL_UP : UPISND_PIN_PULL_DOWN); + else + upisnd_setup_set_gpio_pull(&setup, UPISND_PIN_PULL_NONE); + break; + case PIN_CONFIG_BIAS_DISABLE: + case PIN_CONFIG_BIAS_HIGH_IMPEDANCE: + if (upisnd_setup_get_gpio_dir(setup) != UPISND_PIN_DIR_INPUT) { + err = -EINVAL; + goto cleanup; + } + upisnd_setup_set_gpio_pull(&setup, UPISND_PIN_PULL_NONE); + break; + case UPISND_PINCONF_DIRECTION: + if (arg & UPISND_PINCONF_DIR_MASK) { + // Output. + upisnd_setup_set_gpio_dir(&setup, UPISND_PIN_DIR_OUTPUT); + upisnd_setup_set_gpio_output(&setup, arg == UPISND_PINCONF_DIR_OUTPUT_HIGH); + } else { + // Input. + if (arg == UPISND_PINCONF_DIR_INPUT_PULL_AS_IS_OR_NONE) { + if (upisnd_setup_get_gpio_dir(setup) == UPISND_PIN_DIR_OUTPUT) + upisnd_setup_set_gpio_pull(&setup, UPISND_PIN_PULL_NONE); + } else { + upisnd_setup_set_gpio_pull(&setup, (enum upisnd_pin_pull_e)arg); + } + upisnd_setup_set_gpio_dir(&setup, UPISND_PIN_DIR_INPUT); + } + break; + default: + printe("Not supported param %d", p); + err = -ENOTSUPP; + goto cleanup; + } + + err = upisnd_gpio_setup(instance, setup); + + if (err >= 0) + instance->gpio.pin_configs[offset].setup = setup; + + printd("Done"); + +cleanup: + up_write(&instance->rw_gpio_config_sem); + + return err; +} + +static int upisnd_gpio_irq_set_type(struct irq_data *data, unsigned int type) +{ + printd("%p %u", data, type); + + if (!(type & IRQ_TYPE_EDGE_BOTH)) { + printe("IRQ %d: unsupported type %u\n", data->irq, type); + return -ENOTSUPP; + } + + struct gpio_chip *gc = irq_data_get_irq_chip_data(data); + struct upisnd_instance *instance = gpiochip_get_data(gc); + + unsigned long pin = data->hwirq; + + down_read(&instance->rw_gpio_config_sem); + if (upisnd_setup_get_element_type(instance->gpio.pin_configs[pin].setup) != + UPISND_ELEMENT_TYPE_GPIO || + upisnd_setup_get_gpio_dir(instance->gpio.pin_configs[pin].setup) != + UPISND_PIN_DIR_INPUT) { + printe("Pin %s is not set up as an input!", upisnd_pin_name(pin)); + up_read(&instance->rw_gpio_config_sem); + return -EACCES; + } + up_read(&instance->rw_gpio_config_sem); + + upisnd_irq_config_set_irq_type(&instance->gpio.irq_type_config, pin, type); + set_bit(IRQ_FLAG_TYPES_CHANGED_BIT, &instance->gpio.irq_flags); + + return 0; +} + +static void upisnd_gpio_irq_ack(struct irq_data *data) +{ + printd("%p mask=%08x irq=%u hwirq=%lu", data, data->mask, data->irq, data->hwirq); +} + +static void upisnd_gpio_irq_mask(struct irq_data *data) +{ + printd("%p mask=%08x irq=%u hwirq=%lu", data, data->mask, data->irq, data->hwirq); + + struct gpio_chip *gc = irq_data_get_irq_chip_data(data); + struct upisnd_instance *instance = gpiochip_get_data(gc); + + unsigned long pin = data->hwirq; + + down_read(&instance->rw_gpio_config_sem); + if (upisnd_setup_get_element_type(instance->gpio.pin_configs[pin].setup) != + UPISND_ELEMENT_TYPE_GPIO || + upisnd_setup_get_gpio_dir(instance->gpio.pin_configs[pin].setup) != + UPISND_PIN_DIR_INPUT) { + printe("Pin %s is not set up as an input!", upisnd_pin_name(pin)); + up_read(&instance->rw_gpio_config_sem); + return; + } + up_read(&instance->rw_gpio_config_sem); + + upisnd_irq_mask_config_set(&instance->gpio.irq_mask_config, pin, true); + set_bit(IRQ_FLAG_MASKS_CHANGED_BIT, &instance->gpio.irq_flags); +} + +static void upisnd_gpio_irq_unmask(struct irq_data *data) +{ + printd("%p mask=%08x irq=%u hwirq=%lu", data, data->mask, data->irq, data->hwirq); + + struct gpio_chip *gc = irq_data_get_irq_chip_data(data); + struct upisnd_instance *instance = gpiochip_get_data(gc); + + unsigned long pin = data->hwirq; + + down_read(&instance->rw_gpio_config_sem); + if (upisnd_setup_get_element_type(instance->gpio.pin_configs[pin].setup) != + UPISND_ELEMENT_TYPE_GPIO || + upisnd_setup_get_gpio_dir(instance->gpio.pin_configs[pin].setup) != + UPISND_PIN_DIR_INPUT) { + printe("Pin %s is not set up as an input!", upisnd_pin_name(pin)); + up_read(&instance->rw_gpio_config_sem); + return; + } + up_read(&instance->rw_gpio_config_sem); + + upisnd_irq_mask_config_set(&instance->gpio.irq_mask_config, pin, false); + set_bit(IRQ_FLAG_MASKS_CHANGED_BIT, &instance->gpio.irq_flags); +} + +static void upisnd_gpio_irq_bus_lock(struct irq_data *data) +{ + printd("%p mask=%08x irq=%u hwirq=%lu", data, data->mask, data->irq, data->hwirq); + + struct gpio_chip *gc = irq_data_get_irq_chip_data(data); + struct upisnd_instance *instance = gpiochip_get_data(gc); + + mutex_lock(&instance->gpio.gpio_irq_lock); +} + +static void upisnd_gpio_irq_bus_sync_unlock(struct irq_data *data) +{ + printd("%p mask=%08x irq=%u hwirq=%lu", data, data->mask, data->irq, data->hwirq); + + struct gpio_chip *gc = irq_data_get_irq_chip_data(data); + struct upisnd_instance *instance = gpiochip_get_data(gc); + + int result = 0; + + if (test_bit(IRQ_FLAG_TYPES_CHANGED_BIT, &instance->gpio.irq_flags)) { + printd("Types changed"); + result = upisnd_comm_set_irq_types(instance, &instance->gpio.irq_type_config); + printd("result: %d", result); + + clear_bit(IRQ_FLAG_TYPES_CHANGED_BIT, &instance->gpio.irq_flags); + } + + if (test_bit(IRQ_FLAG_MASKS_CHANGED_BIT, &instance->gpio.irq_flags)) { + printd("Masks changed"); + result = upisnd_comm_set_irq_masks(instance, &instance->gpio.irq_mask_config); + printd("result: %d", result); + + clear_bit(IRQ_FLAG_MASKS_CHANGED_BIT, &instance->gpio.irq_flags); + } + + printd("done"); + mutex_unlock(&instance->gpio.gpio_irq_lock); +} + +static const struct irq_chip upisnd_gpio_irq_chip = { + .name = "pisound-micro-gpio", + .irq_set_type = upisnd_gpio_irq_set_type, + .irq_ack = upisnd_gpio_irq_ack, + .irq_mask = upisnd_gpio_irq_mask, + .irq_unmask = upisnd_gpio_irq_unmask, + .irq_bus_lock = upisnd_gpio_irq_bus_lock, + .irq_bus_sync_unlock = upisnd_gpio_irq_bus_sync_unlock, + .flags = IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_IMMUTABLE, +}; + +static void upisnd_gpio_irq_event_handler(struct work_struct *work) +{ + struct upisnd_instance *instance = container_of(work, + struct upisnd_instance, + gpio.irq_event_handler); + + int n; + struct irq_event_t events[128]; + + set_bit(IRQ_FLAG_HANDLING_IRQ_BIT, &instance->gpio.irq_flags); + + unsigned int offset; + unsigned long masked[2]; + + memset(masked, 0, sizeof(masked)); + u8 i; + + down_write(&instance->rw_gpio_config_sem); + n = kfifo_out(&instance->gpio.irq_event_fifo, events, ARRAY_SIZE(events)); + if (n > 0) { + printd("Handling %u IRQ events:", n); + for (i = 0; i < n; ++i) { + offset = events[i].num; + + if (offset == 0x7f) { + printi("\tAlert event %s", events[i].high ? "on" : "off"); + continue; + } + + printd("\t%d %s %s", offset, upisnd_pin_name(offset), + events[i].high ? "up" : "down"); + + assign_bit(offset, instance->gpio.gpio_state, events[i].high); + + if (upisnd_irq_mask_config_get(&instance->gpio.irq_mask_config, offset)) + set_bit(offset, masked); + } + } + up_write(&instance->rw_gpio_config_sem); + + for (i = 0; i < n; ++i) { + if (!test_bit(i, masked)) { + offset = events[i].num; + int nested_irq = irq_find_mapping(instance->gpio.gpio_chip.irq.domain, + offset); + + if (unlikely(nested_irq <= 0)) { + dev_warn_ratelimited(instance->gpio.gpio_chip.parent, + "unmapped interrupt %d\n", + offset); + continue; + } else { + printd("Before handle_nested_irq"); + handle_nested_irq(nested_irq); + printd("After handle_nested_irq"); + } + } + } + + clear_bit(IRQ_FLAG_HANDLING_IRQ_BIT, &instance->gpio.irq_flags); + + if (!kfifo_is_empty(&instance->gpio.irq_event_fifo)) + queue_work(instance->work_queue, &instance->gpio.irq_event_handler); +} + +void upisnd_gpio_handle_irq_event(struct upisnd_instance *instance, + const struct irq_event_t *events, + unsigned int n) +{ + printd("Pushing %u events", n); + kfifo_in(&instance->gpio.irq_event_fifo, events, n); + if (!work_pending(&instance->gpio.irq_event_handler)) + queue_work(instance->work_queue, &instance->gpio.irq_event_handler); +} + +void upisnd_gpio_reset(struct upisnd_instance *instance) +{ + down_write(&instance->rw_gpio_config_sem); + + memset(&instance->gpio.gpio_state, 0, sizeof(instance->gpio.gpio_state)); + memset(instance->gpio.pin_configs, 0, sizeof(instance->gpio.pin_configs)); + memset(&instance->gpio.irq_mask_config, 0xff, sizeof(instance->gpio.irq_mask_config)); + + up_write(&instance->rw_gpio_config_sem); +} + +int upisnd_gpio_init(struct upisnd_instance *instance) +{ + struct upisnd_gpio *gpio = &instance->gpio; + + mutex_init(&gpio->gpio_irq_lock); + + // Set all IRQs as masked initially. + memset(&gpio->irq_mask_config, 0xff, sizeof(gpio->irq_mask_config)); + + INIT_KFIFO(gpio->irq_event_fifo); + INIT_WORK(&gpio->irq_event_handler, upisnd_gpio_irq_event_handler); + + gpio->gpio_chip = upisnd_gpio_chip; + gpio->gpio_chip.parent = instance->ctrl_dev; + + gpio->gpio_irq = &upisnd_gpio_irq_chip; + + struct gpio_irq_chip *girq = &gpio->gpio_chip.irq; + + gpio_irq_chip_set_chip(girq, gpio->gpio_irq); + girq->threaded = true; + girq->num_parents = 1; + girq->parents = devm_kzalloc(instance->ctrl_dev, sizeof(*girq->parents), GFP_KERNEL); + + if (!girq->parents) + return -ENOMEM; + + *girq->parents = irq_of_parse_and_map(instance->ctrl_dev->of_node, 0); + + printd("Doing devm_gpiochip_add_data"); + int err = devm_gpiochip_add_data(instance->ctrl_dev, &gpio->gpio_chip, instance); + + printd("result: %d, base = %d", err, gpio->gpio_chip.base); + return err; +} + +void upisnd_gpio_uninit(struct upisnd_instance *instance) +{ + kfifo_free(&instance->gpio.irq_event_fifo); +} + +void upisnd_gpio_set(struct upisnd_instance *instance, unsigned int offset, bool value) +{ + upisnd_gpio_chip_set(&instance->gpio.gpio_chip, offset, value); +} + +int upisnd_gpio_get(struct upisnd_instance *instance, unsigned int offset) +{ + return upisnd_gpio_chip_get(&instance->gpio.gpio_chip, offset); +} + +int upisnd_gpio_get_direction(struct upisnd_instance *instance, unsigned int offset) +{ + return upisnd_gpio_chip_get_direction(&instance->gpio.gpio_chip, offset); +} + +int upisnd_gpio_set_irq_type(struct upisnd_instance *instance, + unsigned int offset, + unsigned int irq_type) +{ + printd("(%d, %d)", offset, irq_type); + if (offset >= UPISND_NUM_GPIOS) + return -EINVAL; + + if (!(irq_type & IRQ_TYPE_EDGE_BOTH)) { + printe("IRQ %d: unsupported type %u\n", offset, irq_type); + return -ENOTSUPP; + } + + mutex_lock(&instance->gpio.gpio_irq_lock); + + if (upisnd_setup_get_element_type(instance->gpio.pin_configs[offset].setup) != + UPISND_ELEMENT_TYPE_GPIO || + upisnd_setup_get_gpio_dir(instance->gpio.pin_configs[offset].setup) != + UPISND_PIN_DIR_INPUT) { + printe("Pin %s is not set up as an input!", upisnd_pin_name(offset)); + return -EACCES; + } + upisnd_irq_config_set_irq_type(&instance->gpio.irq_type_config, offset, irq_type); + int result = upisnd_comm_set_irq_types(instance, &instance->gpio.irq_type_config); + (void)result; + + mutex_unlock(&instance->gpio.gpio_irq_lock); + + printd("result: %d", result); + return 0; +} + +int upisnd_gpio_set_subscription(struct upisnd_instance *instance, unsigned int offset, bool on) +{ + printd("(%d, %d)", offset, on); + + if (offset >= UPISND_NUM_GPIOS) + return -EINVAL; + + int result = 0; + int *rc = &instance->gpio.pin_subscription_refcounts[offset]; + int c; + + mutex_lock(&instance->gpio.gpio_irq_lock); + + if (on) + c = (*rc)++; + else + c = --(*rc); + + if (c == 0) { + printd("Count was 0, setting %d sub to %d", offset, on); + result = upisnd_comm_set_subscription(instance, offset, on); + } + + mutex_unlock(&instance->gpio.gpio_irq_lock); + + return result; +} + +enum upisnd_element_type_e upisnd_gpio_get_type(struct upisnd_instance *instance, + unsigned int offset) +{ + if (offset >= UPISND_NUM_GPIOS) + return UPISND_ELEMENT_TYPE_NONE; + + down_read(&instance->rw_gpio_config_sem); + upisnd_setup_t setup = instance->gpio.pin_configs[offset].setup; + + up_read(&instance->rw_gpio_config_sem); + + return upisnd_setup_get_element_type(setup); +} + +/* vim: set ts=8 sw=8 noexpandtab: */ diff --git a/sound/drivers/upisnd/upisnd_gpio.h b/sound/drivers/upisnd/upisnd_gpio.h new file mode 100644 index 000000000000..7ad50b557063 --- /dev/null +++ b/sound/drivers/upisnd/upisnd_gpio.h @@ -0,0 +1,75 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Pisound Micro Linux kernel module. + * Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 of the + * License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef UPISOUND_GPIO_H +#define UPISOUND_GPIO_H + +enum { + UPISND_PIN_FLAG_IS_ENCODER_B = 1 << 0, + UPISND_PIN_FLAG_CONFIGURING_THROUGH_SYSFS = 1 << 1, + UPISND_PIN_FLAG_IRQ_RISING = 1 << 2, + UPISND_PIN_FLAG_IRQ_FALLING = 1 << 3, +}; + +struct upisnd_pin_config_t { + upisnd_setup_t setup; + u32 flags; + struct gpio_desc *gpio_desc; + struct upisnd_element *element; +}; + +struct upisnd_gpio { + struct gpio_chip gpio_chip; + const struct irq_chip *gpio_irq; + // Protects IRQ state and bus. + struct mutex gpio_irq_lock; + unsigned long gpio_state[2]; + unsigned long irq_flags; + struct upisnd_irq_type_config_t irq_type_config; + struct upisnd_irq_mask_config_t irq_mask_config; + struct upisnd_pin_config_t pin_configs[UPISND_NUM_GPIOS]; + int pin_subscription_refcounts[UPISND_NUM_GPIOS]; + DECLARE_KFIFO(irq_event_fifo, struct irq_event_t, 128); + struct work_struct irq_event_handler; +}; + +int upisnd_gpio_init(struct upisnd_instance *instance); +void upisnd_gpio_uninit(struct upisnd_instance *instance); + +void upisnd_gpio_reset(struct upisnd_instance *instance); + +int upisnd_gpio_setup(struct upisnd_instance *instance, upisnd_setup_t setup); + +void upisnd_gpio_handle_irq_event(struct upisnd_instance *instance, + const struct irq_event_t *events, + unsigned int n); + +void upisnd_gpio_set(struct upisnd_instance *instance, unsigned int offset, bool value); +int upisnd_gpio_get(struct upisnd_instance *instance, unsigned int offset); +int upisnd_gpio_get_direction(struct upisnd_instance *instance, unsigned int offset); + +int upisnd_gpio_set_irq_type(struct upisnd_instance *instance, + unsigned int offset, + unsigned int irq_type); + +int upisnd_gpio_set_subscription(struct upisnd_instance *instance, unsigned int offset, bool on); + +enum upisnd_element_type_e upisnd_gpio_get_type(struct upisnd_instance *instance, + unsigned int offset); + +#endif // UPISOUND_GPIO_H + +/* vim: set ts=8 sw=8 noexpandtab: */ diff --git a/sound/drivers/upisnd/upisnd_midi.c b/sound/drivers/upisnd/upisnd_midi.c new file mode 100644 index 000000000000..b7fea8227bf6 --- /dev/null +++ b/sound/drivers/upisnd/upisnd_midi.c @@ -0,0 +1,337 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Pisound Micro Linux kernel module. + * Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 of the + * License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "upisnd_common.h" + +static int upisnd_midi_output_open(struct snd_rawmidi_substream *substream) +{ + struct upisnd_instance *instance = substream->rmidi->private_data; + + mutex_lock(&instance->midi.out_lock); + instance->midi.midi_output = substream; + mutex_unlock(&instance->midi.out_lock); + return 0; +} + +static void upisnd_midi_output_drain(struct snd_rawmidi_substream *substream); + +static int upisnd_midi_output_close(struct snd_rawmidi_substream *substream) +{ + struct upisnd_instance *instance = substream->rmidi->private_data; + + printd("Close: draining output"); + upisnd_midi_output_drain(substream); + printd("Close: setting to null"); + mutex_lock(&instance->midi.out_lock); + instance->midi.midi_output = NULL; + mutex_unlock(&instance->midi.out_lock); + return 0; +} + +static void upisnd_midi_output_trigger(struct snd_rawmidi_substream *substream, int up) +{ + if (up == 0) + return; + + struct upisnd_instance *instance = substream->rmidi->private_data; + + if (!delayed_work_pending(&instance->midi.midi_out_handler)) + queue_delayed_work(instance->work_queue, &instance->midi.midi_out_handler, 0); +} + +static void upisnd_midi_output_drain(struct snd_rawmidi_substream *substream) +{ + struct upisnd_instance *instance = substream->rmidi->private_data; + + printd("Begin draining!"); + + do { + printd("Before flush"); + while (delayed_work_pending(&instance->midi.midi_out_handler)) + flush_delayed_work(&instance->midi.midi_out_handler); + printd("Flushed"); + } while (!snd_rawmidi_transmit_empty(substream)); + + printd("Done!"); +} + +static int upisnd_midi_input_open(struct snd_rawmidi_substream *substream) +{ + struct upisnd_instance *instance = substream->rmidi->private_data; + + mutex_lock(&instance->midi.in_lock); + instance->midi.midi_input = substream; + mutex_unlock(&instance->midi.in_lock); + return 0; +} + +static int upisnd_midi_input_close(struct snd_rawmidi_substream *substream) +{ + struct upisnd_instance *instance = substream->rmidi->private_data; + + mutex_lock(&instance->midi.in_lock); + instance->midi.midi_input = NULL; + mutex_unlock(&instance->midi.in_lock); + return 0; +} + +static void upisnd_midi_input_trigger(struct snd_rawmidi_substream *substream, int up) +{ + if (up == 0) + return; + + struct upisnd_instance *instance = substream->rmidi->private_data; + + if (!work_pending(&instance->midi.midi_in_handler)) + queue_work(instance->work_queue, &instance->midi.midi_in_handler); +} + +static const struct snd_rawmidi_ops upisnd_midi_output_ops = { + .open = upisnd_midi_output_open, + .close = upisnd_midi_output_close, + .trigger = upisnd_midi_output_trigger, + .drain = upisnd_midi_output_drain, +}; + +static const struct snd_rawmidi_ops upisnd_midi_input_ops = { + .open = upisnd_midi_input_open, + .close = upisnd_midi_input_close, + .trigger = upisnd_midi_input_trigger, +}; + +static void upisnd_get_port_info(struct snd_rawmidi *rmidi, + int number, + struct snd_seq_port_info *seq_port_info) +{ + seq_port_info->type = + SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | + SNDRV_SEQ_PORT_TYPE_HARDWARE | + SNDRV_SEQ_PORT_TYPE_PORT; + seq_port_info->midi_voices = 0; +} + +static const struct snd_rawmidi_global_ops upisnd_midi_ops = { + .get_port_info = upisnd_get_port_info, +}; + +static void upisnd_midi_in_handler(struct work_struct *work) +{ + int n, err; + + printd("In handler"); + struct upisnd_instance *instance = container_of(work, + struct upisnd_instance, + midi.midi_in_handler); + + mutex_lock(&instance->midi.in_lock); + if (!instance->midi.midi_input) + goto cleanup; + + u8 data[512]; + + n = kfifo_out_peek(&instance->midi.midi_in_fifo, data, sizeof(data)); + err = snd_rawmidi_receive(instance->midi.midi_input, data, n); + + if (err < 0) + printe("snd_rawmidi_receive failed! (%d)", err); + + if (err > 0) { + printd("Received %d MIDI bytes", err); + instance->midi.rx_cnt += err; + } + + kfifo_skip_count(&instance->midi.midi_in_fifo, err); + + if (!kfifo_is_empty(&instance->midi.midi_in_fifo) && + !work_pending(&instance->midi.midi_in_handler)) { + queue_work(instance->work_queue, &instance->midi.midi_in_handler); + } +cleanup: + mutex_unlock(&instance->midi.in_lock); + printd("Done"); +} + +static void upisnd_midi_out_handler(struct work_struct *work) +{ + printd("Out handler"); + struct upisnd_instance *instance = container_of(work, + struct upisnd_instance, + midi.midi_out_handler.work); + + mutex_lock(&instance->midi.out_lock); + printd("midi_output = %p", instance->midi.midi_output); + if (!instance->midi.midi_output) + goto cleanup; + + enum { MIDI_MILLI_BYTES_PER_JIFFY = 3125000 / HZ }; + enum { MIDI_MAX_OUTPUT_BUFFER_SIZE_IN_MILLIBYTES = 4096000 }; + + unsigned int now = jiffies; + unsigned int millibytes_became_available = (MIDI_MILLI_BYTES_PER_JIFFY) * + (now - instance->midi.last_midi_output_at); + + instance->midi.output_buffer_used_in_millibytes = + instance->midi.output_buffer_used_in_millibytes <= millibytes_became_available ? + 0 : instance->midi.output_buffer_used_in_millibytes - millibytes_became_available; + instance->midi.last_midi_output_at = now; + + unsigned int output_buffer_available = (MIDI_MAX_OUTPUT_BUFFER_SIZE_IN_MILLIBYTES - + instance->midi.output_buffer_used_in_millibytes) / 1000; + + u8 buffer[UPISND_MAX_PACKET_LENGTH - 1]; + int n, batch, err; + + printd("Available: %u", output_buffer_available); + + for (batch = 0; batch < 3; ++batch) { + if (output_buffer_available == 0) + break; + + n = snd_rawmidi_transmit_peek(instance->midi.midi_output, + buffer, + min(output_buffer_available, sizeof(buffer))); + + if (n > 0) { + printd("Peeked: %d (batch %d)", n, batch); + err = upisnd_comm_send_midi(instance, buffer, (unsigned int)n); + if (err < 0) { + printe("Error occurred when sending MIDI data over I2C! (%d)", n); + goto cleanup; + } + snd_rawmidi_transmit_ack(instance->midi.midi_output, n); + + instance->midi.tx_cnt += n; + instance->midi.output_buffer_used_in_millibytes += n * 1000; + output_buffer_available -= n; + } else if (n < 0) { + printe("snd_rawmidi_transmit_peek returned error %d!", n); + goto cleanup; + } else { + break; + } + } + + printd("Checking if empty %p", instance->midi.midi_output); + if (!snd_rawmidi_transmit_empty(instance->midi.midi_output)) { + unsigned int delay = 0; + + if (instance->midi.output_buffer_used_in_millibytes > + MIDI_MAX_OUTPUT_BUFFER_SIZE_IN_MILLIBYTES - 127000) + delay = 127000 / MIDI_MILLI_BYTES_PER_JIFFY; + printd("Queue more work after %u jiffies", delay); + queue_delayed_work(instance->work_queue, &instance->midi.midi_out_handler, delay); + } + +cleanup: + mutex_unlock(&instance->midi.out_lock); + printd("Done"); +} + +static void upisnd_proc_stat_show(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + const unsigned int *d = entry->private_data; + + snd_iprintf(buffer, "%u\n", *d); +} + +int upisnd_midi_init(struct upisnd_instance *instance) +{ + int err; + + mutex_init(&instance->midi.in_lock); + mutex_init(&instance->midi.out_lock); + + err = snd_card_ro_proc_new(instance->sound_card.snd_card, "tx", &instance->midi.tx_cnt, + upisnd_proc_stat_show); + err = snd_card_ro_proc_new(instance->sound_card.snd_card, "rx", &instance->midi.rx_cnt, + upisnd_proc_stat_show); + + err = snd_rawmidi_new(instance->sound_card.snd_card, + "pisoundmicro", 0, 1, 1, + &instance->midi.rawmidi); + + struct snd_rawmidi *rawmidi = instance->midi.rawmidi; + + if (err < 0) { + printe("snd_rawmidi_new failed: %d\n", err); + return err; + } + + strscpy(rawmidi->name, "pisound-micro ", sizeof(rawmidi->name)); + strcat(rawmidi->name, instance->ctrl.serial); + + rawmidi->info_flags = + SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + + rawmidi->ops = &upisnd_midi_ops; + + rawmidi->private_data = instance; + + snd_rawmidi_set_ops(rawmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &upisnd_midi_output_ops); + + snd_rawmidi_set_ops(rawmidi, SNDRV_RAWMIDI_STREAM_INPUT, &upisnd_midi_input_ops); + + INIT_KFIFO(instance->midi.midi_in_fifo); + instance->midi.last_midi_output_at = jiffies; + + INIT_WORK(&instance->midi.midi_in_handler, upisnd_midi_in_handler); + INIT_DELAYED_WORK(&instance->midi.midi_out_handler, upisnd_midi_out_handler); + + return 0; +} + +void upisnd_midi_uninit(struct upisnd_instance *instance) +{ + if (!instance->midi.rawmidi) + return; + + cancel_work_sync(&instance->midi.midi_in_handler); + cancel_delayed_work_sync(&instance->midi.midi_out_handler); + + instance->midi.rawmidi->private_data = NULL; + + instance->midi.rawmidi = NULL; + mutex_lock(&instance->midi.in_lock); + instance->midi.midi_input = NULL; + mutex_unlock(&instance->midi.in_lock); + mutex_lock(&instance->midi.out_lock); + instance->midi.midi_output = NULL; + mutex_unlock(&instance->midi.out_lock); + + kfifo_free(&instance->midi.midi_in_fifo); +} + +void upisnd_handle_midi_data(struct upisnd_instance *instance, const uint8_t *data, unsigned int n) +{ + printd("%p, %u", instance, n); + if (n == 0) + return; + + int i; + + for (i = 0; i < n; ++i) { + kfifo_put(&instance->midi.midi_in_fifo, data[i]); + printd("Received MIDI %02x", data[i]); + } + + if (!work_pending(&instance->midi.midi_in_handler)) + queue_work(instance->work_queue, &instance->midi.midi_in_handler); + printd("Done"); +} + +/* vim: set ts=8 sw=8 noexpandtab: */ diff --git a/sound/drivers/upisnd/upisnd_midi.h b/sound/drivers/upisnd/upisnd_midi.h new file mode 100644 index 000000000000..0a4678e17e07 --- /dev/null +++ b/sound/drivers/upisnd/upisnd_midi.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Pisound Micro Linux kernel module. + * Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 of the + * License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef UPISOUND_MIDI_H +#define UPISOUND_MIDI_H + +struct upisnd_instance; + +struct upisnd_midi { + // in_lock protects the input substream. + struct mutex in_lock; + // out_lock protects the output substream. + struct mutex out_lock; + struct work_struct midi_in_handler; + struct delayed_work midi_out_handler; + + struct snd_rawmidi *rawmidi; + struct snd_rawmidi_substream *midi_input; + struct snd_rawmidi_substream *midi_output; + DECLARE_KFIFO(midi_in_fifo, uint8_t, 4096); + unsigned int last_midi_output_at; + unsigned int output_buffer_used_in_millibytes; + unsigned int tx_cnt; + unsigned int rx_cnt; +}; + +int upisnd_midi_init(struct upisnd_instance *instance); +void upisnd_midi_uninit(struct upisnd_instance *instance); + +void upisnd_handle_midi_data(struct upisnd_instance *instance, const uint8_t *data, unsigned int n); + +#endif // UPISOUND_MIDI_H + +/* vim: set ts=8 sw=8 noexpandtab: */ diff --git a/sound/drivers/upisnd/upisnd_module.c b/sound/drivers/upisnd/upisnd_module.c new file mode 100644 index 000000000000..ef545313c0c2 --- /dev/null +++ b/sound/drivers/upisnd/upisnd_module.c @@ -0,0 +1,273 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Pisound Micro Linux kernel module. + * Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 of the + * License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "upisnd_common.h" + +void upisnd_instance_release(struct kref *kref) +{ + struct upisnd_instance *instance = container_of(kref, struct upisnd_instance, refcount); + + if (instance->codec_dev) { + put_device(instance->codec_dev); + instance->codec_dev = NULL; + } + + if (instance->ctrl_dev) { + instance->ctrl_dev->platform_data = NULL; + put_device(instance->ctrl_dev); + instance->ctrl_dev = NULL; + } + + if (instance->sound_card.dev) { + snd_soc_unregister_card(&instance->sound_card); + memset(&instance->sound_card, 0, sizeof(instance->sound_card)); + } + + if (instance->work_queue) { + flush_workqueue(instance->work_queue); + destroy_workqueue(instance->work_queue); + instance->work_queue = NULL; + } + + printd("Releasing instance %p", instance); + kfree(instance); +} + +static int of_dev_node_match(struct device *dev, const void *data) +{ + return dev->of_node == data; +} + +static int upisnd_probe(struct platform_device *pdev) +{ + printd("Load %p", pdev); + + int err = 0; + struct device_node *node; + struct device_node *i2c_node; + struct device *dev; + + struct upisnd_instance *instance = pdev->dev.platform_data; + + if (!instance) { + instance = kzalloc(sizeof(*instance), GFP_KERNEL); + + if (!instance) { + printe("Failed to allocate instance!"); + err = -ENOMEM; + goto cleanup; + } + kref_init(&instance->refcount); + + pdev->dev.platform_data = instance; + instance->pdev = pdev; + + init_rwsem(&instance->rw_gpio_config_sem); + + instance->work_queue = create_singlethread_workqueue("upisnd_workqueue"); + if (!instance->work_queue) { + printe("Failed creating single thread work queue!"); + err = -ENOMEM; + goto cleanup; + } + } + + node = pdev->dev.of_node; + if (!node) { + printe("Device node not found!"); + err = -ENODEV; + goto cleanup; + } + + i2c_node = of_parse_phandle(node, "ctrl", 0); + if (!i2c_node) { + printe("Failed to read 'ctrl' node!"); + err = -ENODEV; + goto cleanup; + } + + dev = bus_find_device(&i2c_bus_type, NULL, i2c_node, of_dev_node_match); + of_node_put(i2c_node); + + if (!dev) { + printe("Failed to find 'ctrl' device (%pOF)!", i2c_node); + err = -ENODEV; + goto cleanup; + } + + bool got_ref = false; + + if (!dev->platform_data) { + kref_get(&instance->refcount); + dev->platform_data = instance; + got_ref = true; + } + instance->ctrl_dev = dev; + + if (instance->ctrl.serial[0] == '\0') { + printd("Deferring probe until serial is retrieved!"); + return -EPROBE_DEFER; + } + + i2c_node = of_parse_phandle(node, "codec", 0); + + if (!i2c_node) { + printe("Failed to read 'codec' node!"); + err = -ENODEV; + goto cleanup; + } + + dev = bus_find_device(&i2c_bus_type, NULL, i2c_node, of_dev_node_match); + of_node_put(i2c_node); + + if (!dev) { + printe("Failed to find 'codec' device!"); + err = -ENODEV; + goto cleanup; + } + + instance->codec_dev = dev; + + err = upisnd_sound_init(pdev, instance); + if (err != 0) { + if (err != -EPROBE_DEFER) + printe("Failed initializing sound card! (%d)", err); + goto cleanup; + } + +cleanup: + if (err != 0) { + if (err != -EPROBE_DEFER) + printe("Error %d!", err); + + if (instance) { + if (got_ref) { + instance->ctrl_dev->platform_data = NULL; + kref_put(&instance->refcount, &upisnd_instance_release); + } + + if (instance->codec_dev) { + put_device(instance->codec_dev); + instance->codec_dev = NULL; + } + } + } + + return err; +} + +static void upisnd_remove(struct platform_device *pdev) +{ + printd("Unload %p", pdev); + struct upisnd_instance *instance = dev_get_platdata(&pdev->dev); + + kref_put(&instance->refcount, &upisnd_instance_release); + pdev->dev.platform_data = NULL; +} + +static const struct of_device_id upisnd_of_match[] = { + {.compatible = "blokas,pisound-micro" }, + {} +}; + +static const struct of_device_id upisnd_ctrl_of_match[] = { + {.compatible = "blokas,upisnd-ctrl" }, + {} +}; + +static struct platform_driver upisnd_driver = { + .driver = { + .name = "snd-pisound-micro", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(upisnd_of_match), + }, + .probe = upisnd_probe, + .remove = upisnd_remove, +}; + +static const struct i2c_device_id upisnd_ctrl_idtable[] = { + { "blokas,upisnd-ctrl", 0 }, + {} +}; + +static struct i2c_driver upisnd_ctrl_driver = { + .driver = { + .name = "snd-pisound-micro-ctrl", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(upisnd_ctrl_of_match), + }, + .id_table = upisnd_ctrl_idtable, + .probe = upisnd_ctrl_probe, + .remove = upisnd_ctrl_remove, +}; + +static int upisnd_module_init(void) +{ + int err, progress = 0; + + err = upisnd_comm_module_init(); + if (err != 0) + goto cleanup; + + ++progress; + err = platform_driver_register(&upisnd_driver); + if (err != 0) + goto cleanup; + + ++progress; + err = i2c_add_driver(&upisnd_ctrl_driver); + if (err != 0) + goto cleanup; + +cleanup: + if (err) { + printe("Error %d occurred, progress: %d", err, progress); + switch (progress) { + case 2: + i2c_del_driver(&upisnd_ctrl_driver); + fallthrough; + case 1: + platform_driver_unregister(&upisnd_driver); + fallthrough; + case 0: + // No comm uninit. + fallthrough; + default: + break; + } + } + + return err; +} + +static void upisnd_module_exit(void) +{ + i2c_del_driver(&upisnd_ctrl_driver); + platform_driver_unregister(&upisnd_driver); +} + +module_init(upisnd_module_init); +module_exit(upisnd_module_exit); + +MODULE_DEVICE_TABLE(of, upisnd_of_match); +MODULE_DEVICE_TABLE(of, upisnd_ctrl_of_match); +MODULE_DEVICE_TABLE(i2c, upisnd_ctrl_idtable); + +MODULE_AUTHOR("Giedrius Trainavi\xc4\x8dius "); +MODULE_DESCRIPTION("Audio, MIDI & I/O Driver for Pisound Micro, https://blokas.io/"); +MODULE_LICENSE("GPL v2"); + +/* vim: set ts=8 sw=8 noexpandtab: */ diff --git a/sound/drivers/upisnd/upisnd_pins.c b/sound/drivers/upisnd/upisnd_pins.c new file mode 100644 index 000000000000..8e3e9bac6bed --- /dev/null +++ b/sound/drivers/upisnd/upisnd_pins.c @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Pisound Micro Linux kernel module. + * Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 of the + * License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "upisnd_common.h" + +struct upisnd_pin_def_t { + const char *name; + upisnd_pin_capability_mask_t capabilities; +}; + +static const struct upisnd_pin_def_t upisnd_pins[UPISND_PIN_COUNT] = { + { "A27", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY }, + { "A28", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY }, + { "A29", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY }, + { "A30", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY }, + { "A31", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY }, + { "A32", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY }, + { "B03", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER }, + { "B04", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER }, + { "B05", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER }, + { "B06", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER }, + { "B07", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER }, + { "B08", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER }, + { "B09", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER }, + { "B10", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER }, + // Pull down is not available in MCU revisions A, B, C, D, E. F and G are not affected. + { "B11", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER }, + // Pull down is not available in MCU revisions A, B, C, D, E. F and G are not affected. + { "B12", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER }, + { "B13", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER }, + { "B14", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER }, + { "B15", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER }, + { "B16", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER }, + { "B17", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER }, + { "B18", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER }, + { "B23", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN }, + { "B24", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN }, + { "B25", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN }, + { "B26", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN }, + { "B27", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN }, + { "B28", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN }, + { "B29", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN }, + { "B30", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN }, + { "B31", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN }, + { "B32", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN }, + { "B33", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN }, + { "B34", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN }, + { "B37", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY }, + { "B38", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY }, + { "B39", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY }, +}; + +upisnd_pin_t upisnd_name_to_pin(const char *name) +{ + if (!name || strlen(name) != 3 || !isdigit(name[1]) || !isdigit(name[2])) + return UPISND_PIN_INVALID; + + char sanitized[4]; + + switch (*name) { + case 'a': case 'A': + sanitized[0] = 'A'; + break; + case 'b': case 'B': + sanitized[0] = 'B'; + break; + default: + return UPISND_PIN_INVALID; + } + + memcpy(&sanitized[1], &name[1], 2); + sanitized[3] = '\0'; + + int i; + + for (i = 0; i < ARRAY_SIZE(upisnd_pins); ++i) { + if (strcmp(sanitized, upisnd_pins[i].name) == 0) + return i; + } + + return UPISND_PIN_INVALID; +} + +const char *upisnd_pin_name(upisnd_pin_t pin) +{ + if (!upisnd_is_pin_valid(pin)) + return ""; + + return upisnd_pins[pin].name; +} + +int upisnd_check_caps(upisnd_pin_t pin, upisnd_pin_capability_mask_t mask) +{ + if (!upisnd_is_pin_valid(pin)) + return -ENXIO; + + return (upisnd_pins[pin].capabilities & mask) == mask ? 0 : -EINVAL; +} + +/* vim: set ts=8 sw=8 noexpandtab: */ diff --git a/sound/drivers/upisnd/upisnd_pins.h b/sound/drivers/upisnd/upisnd_pins.h new file mode 100644 index 000000000000..5f4b3a5660a3 --- /dev/null +++ b/sound/drivers/upisnd/upisnd_pins.h @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Pisound Micro Linux kernel module. + * Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 of the + * License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef UPISOUND_PINS_H +#define UPISOUND_PINS_H + +enum { UPISND_NUM_GPIOS = 37 }; + +enum upisnd_pin_e { + UPISND_PIN_A27, UPISND_PIN_A28, UPISND_PIN_A29, UPISND_PIN_A30, + UPISND_PIN_A31, UPISND_PIN_A32, UPISND_PIN_B03, UPISND_PIN_B04, + UPISND_PIN_B05, UPISND_PIN_B06, UPISND_PIN_B07, UPISND_PIN_B08, + UPISND_PIN_B09, UPISND_PIN_B10, UPISND_PIN_B11, UPISND_PIN_B12, + UPISND_PIN_B13, UPISND_PIN_B14, UPISND_PIN_B15, UPISND_PIN_B16, + UPISND_PIN_B17, UPISND_PIN_B18, UPISND_PIN_B23, UPISND_PIN_B24, + UPISND_PIN_B25, UPISND_PIN_B26, UPISND_PIN_B27, UPISND_PIN_B28, + UPISND_PIN_B29, UPISND_PIN_B30, UPISND_PIN_B31, UPISND_PIN_B32, + UPISND_PIN_B33, UPISND_PIN_B34, UPISND_PIN_B37, UPISND_PIN_B38, + UPISND_PIN_B39, + + UPISND_PIN_COUNT, + UPISND_PIN_INVALID = UPISND_PIN_COUNT +}; + +typedef u8 upisnd_pin_t; + +static inline bool upisnd_is_pin_valid(upisnd_pin_t pin) +{ + return pin < UPISND_PIN_COUNT; +} + +enum upisnd_pin_capability_flags_e { + UPISND_PIN_CAP_GPIO_DIR_INPUT = 1 << 0, + UPISND_PIN_CAP_GPIO_DIR_OUTPUT = 1 << 1, + UPISND_PIN_CAP_GPIO_PULL_UP = 1 << 2, + UPISND_PIN_CAP_GPIO_PULL_DOWN = 1 << 3, + UPISND_PIN_CAP_GPIO = UPISND_PIN_CAP_GPIO_DIR_INPUT | + UPISND_PIN_CAP_GPIO_DIR_OUTPUT | + UPISND_PIN_CAP_GPIO_PULL_UP | + UPISND_PIN_CAP_GPIO_PULL_DOWN, + UPISND_PIN_CAP_ENCODER = 1 << 4, + UPISND_PIN_CAP_ANALOG_IN = 1 << 5, + UPISND_PIN_CAP_MIDI_ACTIVITY = 1 << 6, +}; + +typedef u8 upisnd_pin_capability_mask_t; + +upisnd_pin_t upisnd_name_to_pin(const char *name); +const char *upisnd_pin_name(upisnd_pin_t pin); +int upisnd_check_caps(upisnd_pin_t pin, upisnd_pin_capability_mask_t mask); + +#endif // UPISOUND_PINS_H + +/* vim: set ts=8 sw=8 noexpandtab: */ diff --git a/sound/drivers/upisnd/upisnd_protocol.h b/sound/drivers/upisnd/upisnd_protocol.h new file mode 100644 index 000000000000..59beaa3e6cbf --- /dev/null +++ b/sound/drivers/upisnd/upisnd_protocol.h @@ -0,0 +1,323 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Pisound Micro Linux kernel module. + * Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 of the + * License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef UPISND_PROTOCOL_H +#define UPISND_PROTOCOL_H + +#ifndef __KERNEL__ +#include +#include + +enum upisnd_irq_type_e { + IRQ_TYPE_NONE = 0, + IRQ_TYPE_EDGE_RISING = 1, + IRQ_TYPE_EDGE_FALLING = 2, + IRQ_TYPE_EDGES_BOTH = 3, +}; +#endif + +enum { UPISND_ON_BIT_MASK = 0x80 }; +enum { UPISND_IRQ_NUM_MASK = 0x7f }; +enum { UPISND_PIN_MASK = 0x3f }; + +enum upisnd_irq_num_e { + UPISND_IRQ_GPIO_START = 0, + // IRQ numbers match GPIO numbers. + UPISND_IRQ_GPIO_END = UPISND_PIN_MASK, + + UPISND_IRQ_VGND_SHORT_ALERT = 0x7f, +}; + +typedef u8 upisnd_pin_t; +typedef u8 upisnd_irq_num_t; +typedef u8 upisnd_msg_id_t; + +typedef u32 upisnd_setup_t; + +enum upisnd_activity_type_e { + UPISND_ACTIVITY_TYPE_MIDI_IN = 0, + UPISND_ACTIVITY_TYPE_MIDI_OUT = 1, +}; + +enum upisnd_pin_pull_e { + UPISND_PIN_PULL_NONE = 0, + UPISND_PIN_PULL_UP = 1, + UPISND_PIN_PULL_DOWN = 2 +}; + +enum upisnd_pin_direction_e { + UPISND_PIN_DIR_INPUT = 0, + UPISND_PIN_DIR_OUTPUT = 1 +}; + +enum upisnd_element_type_e { + UPISND_ELEMENT_TYPE_NONE = 0, + UPISND_ELEMENT_TYPE_ENCODER = 1, + UPISND_ELEMENT_TYPE_ANALOG_IN = 2, + UPISND_ELEMENT_TYPE_GPIO = 3, + UPISND_ELEMENT_TYPE_ACTIVITY = 4, +}; + +#define UPISND_DEFINE_SETUP_FIELD(shift, bits, type, name) \ + static inline type upisnd_setup_get_ ## name(upisnd_setup_t setup) \ + { \ + return (type)(((setup) & (((1 << (bits)) - 1) << (shift))) >> (shift)); \ + } \ + static inline void upisnd_setup_set_ ## name(upisnd_setup_t *setup, type value) \ + { \ + *(setup) = ((*(setup)) & ~(((1 << (bits)) - 1) << (shift))) | \ + (((value) & ((1 << (bits)) - 1)) << (shift)); \ + } + +UPISND_DEFINE_SETUP_FIELD(0, 3, enum upisnd_element_type_e, element_type); +UPISND_DEFINE_SETUP_FIELD(3, 8, upisnd_pin_t, pin_id); +UPISND_DEFINE_SETUP_FIELD(11, 2, enum upisnd_pin_pull_e, gpio_pull); +UPISND_DEFINE_SETUP_FIELD(13, 1, enum upisnd_pin_direction_e, gpio_dir); +UPISND_DEFINE_SETUP_FIELD(12, 1, bool, gpio_output); +UPISND_DEFINE_SETUP_FIELD(13, 8, upisnd_pin_t, encoder_pin_b_id); +UPISND_DEFINE_SETUP_FIELD(21, 2, enum upisnd_pin_pull_e, encoder_pin_b_pull); +UPISND_DEFINE_SETUP_FIELD(11, 2, enum upisnd_activity_type_e, activity_type); + +#undef UPISND_DEFINE_SETUP_FIELD + +struct upisnd_irq_type_config_t { + u8 irq_types[10]; +} __packed; + +static inline void upisnd_irq_config_set_irq_type(struct upisnd_irq_type_config_t *cfg, + upisnd_pin_t pin, + unsigned int type) +{ + u8 *d = &cfg->irq_types[pin >> 2]; + *d = (*d & ~(0x3 << (pin & 0x3))) | ((type & 0x3) << (pin & 0x3)); +} + +static inline unsigned int upisnd_irq_config_get_irq_type(const struct upisnd_irq_type_config_t + *cfg, upisnd_pin_t pin) +{ + return (cfg->irq_types[pin >> 2] >> (pin & 0x3)) & 0x3; +} + +struct upisnd_irq_mask_config_t { + u8 irq_mask[5]; +} __packed; + +static inline void upisnd_irq_mask_config_set(struct upisnd_irq_mask_config_t *cfg, + upisnd_pin_t pin, + bool mask) +{ + u8 *d = &cfg->irq_mask[pin >> 3]; + + if (mask) + *d |= 1 << (pin & 0x7); + else + *d &= ~(1 << (pin & 0x7)); +} + +static inline bool upisnd_irq_mask_config_get(const struct upisnd_irq_mask_config_t *cfg, + upisnd_pin_t pin) +{ + return (cfg->irq_mask[pin >> 3] & (1 << (pin & 0x7))) != 0; +} + +struct upisnd_all_gpio_state_t { + u8 state[5]; +} __packed; + +enum { UPISND_MAX_PACKET_LENGTH = 16 }; +enum { UPISND_MSG_RESPONSE_FLAG = 0x80 }; +enum { UPISND_MSG_ID_MASK = 0x7f }; +enum { UPISND_MSG_ID_INVALID = 0x00 }; + +enum upisnd_cmd_type_e { + UPISND_CMD_MIDI = 0x00, + UPISND_CMD_SETUP = 0x10, + UPISND_CMD_SET_IRQ_TYPES = 0x20, + UPISND_CMD_SET_IRQ_MASKS = 0x30, + UPISND_CMD_SET_GPIO = 0x40, + UPISND_CMD_GET_GPIO = 0x50, + UPISND_CMD_SET_ALL_GPIOS = 0x60, + UPISND_CMD_GET_ALL_GPIOS = 0x70, + UPISND_CMD_RESULT = 0x80, + UPISND_CMD_SET_SUBSCRIPTION = 0x90, + UPISND_CMD_IRQ_EVENT = 0xa0, + UPISND_CMD_GET = 0xb0, + UPISND_CMD_SET = 0xc0, + UPISND_CMD_CONTROL_EVENT = 0xd0, + + UPISND_CMD_INVALID = 0xff +}; + +enum { UPISND_CMD_TYPE_MASK = 0xf0 }; +enum { UPISND_CMD_LENGTH_MASK = 0x0f }; + +enum upisnd_value_id_t { + UPISND_VALUE_INVALID = 0x00, + UPISND_VALUE_VERSION_INFO = 0x01, + UPISND_VALUE_ADC_OFFSET = 0x02, + UPISND_VALUE_ADC_GAIN = 0x03, + UPISND_VALUE_SERIAL_NUMBER = 0x04, + UPISND_VALUE_ELEMENT_VAL_BASE = 0x80, +}; + +struct upisnd_cmd_t { + u8 cmd_and_size; + u8 flags_and_msg_id; +} __packed; + +static inline bool upisnd_cmd_is_response(const struct upisnd_cmd_t *cmd) +{ + return (cmd->flags_and_msg_id & UPISND_MSG_RESPONSE_FLAG) != 0; +} + +struct upisnd_cmd_setup_t { + struct upisnd_cmd_t cmd; + upisnd_setup_t setup; +} __packed; + +struct upisnd_cmd_set_gpio_t { + u8 cmd_and_size; + u8 on_and_pin; +} __packed; + +struct upisnd_cmd_get_gpio_t { + struct upisnd_cmd_t cmd; + u8 pin; +} __packed; + +struct upisnd_cmd_set_all_gpios_t { + u8 cmd_and_size; + struct upisnd_all_gpio_state_t state; +} __packed; + +struct upisnd_cmd_get_all_gpios_t { + struct upisnd_cmd_t cmd; +} __packed; + +struct upisnd_cmd_get_all_gpios_response_t { + struct upisnd_cmd_t cmd; + struct upisnd_all_gpio_state_t state; +} __packed; + +struct upisnd_cmd_set_irq_types_t { + struct upisnd_cmd_t cmd; + struct upisnd_irq_type_config_t config; +} __packed; + +struct upisnd_cmd_set_irq_masks_t { + struct upisnd_cmd_t cmd; + struct upisnd_irq_mask_config_t config; +} __packed; + +struct upisnd_cmd_set_subscription_t { + struct upisnd_cmd_t cmd; + u8 on_and_irq_num; +} __packed; + +struct upisnd_cmd_result_t { + struct upisnd_cmd_t cmd; + s8 result; +} __packed; + +struct upisnd_cmd_get_t { + struct upisnd_cmd_t cmd; + u8 value_id; +} __packed; + +enum { UPISND_CMD_GET_RESPONSE_INT32_SIZE = sizeof(struct upisnd_cmd_get_t) + sizeof(int8_t) + + sizeof(int32_t) }; +enum { UPISND_CMD_GET_RESPONSE_DATA_MIN_SIZE = sizeof(struct upisnd_cmd_get_t) + sizeof(int8_t) }; + +struct upisnd_cmd_get_response_t { + struct upisnd_cmd_get_t cmd_get; + s8 result; + union { + s32 value; + u8 data[12]; + }; +} __packed; + +struct upisnd_cmd_set_t { + struct upisnd_cmd_t cmd; + u8 value_id; + s32 value; +} __packed; + +struct upisnd_cmd_control_event_t { + u8 cmd_and_size; + u16 values[7]; +} __packed; + +static inline enum upisnd_cmd_type_e upisnd_cmd_decode_type(u8 b) +{ + return (enum upisnd_cmd_type_e)(b & UPISND_CMD_TYPE_MASK); +} + +static inline u8 upisnd_cmd_decode_length(u8 b) +{ + return (b & UPISND_CMD_LENGTH_MASK) + 1u; +} + +static inline u8 upisnd_cmd_encode(enum upisnd_cmd_type_e type, u8 size) +{ + return (size <= UPISND_MAX_PACKET_LENGTH && size > 0) ? (type | (size - 1)) + : UPISND_CMD_INVALID; +} + +static inline u8 upisnd_msg_id_encode(upisnd_msg_id_t msg_id, bool response) +{ + return (response ? 0x80 : 0x00) | (msg_id & UPISND_MSG_ID_MASK); +} + +static inline upisnd_msg_id_t upisnd_msg_id_decode_id(u8 b) +{ + return b & UPISND_MSG_ID_MASK; +} + +static inline void upisnd_cmd_prepare(struct upisnd_cmd_t *cmd, + enum upisnd_cmd_type_e type, + u8 size, + upisnd_msg_id_t msg_id, + bool response) +{ + cmd->cmd_and_size = upisnd_cmd_encode(type, size); + cmd->flags_and_msg_id = upisnd_msg_id_encode(msg_id, response); +} + +#define upisnd_cmd_matches(_cmd, type, size) \ + (upisnd_cmd_decode_type((_cmd)->cmd_and_size) == (type) && \ + upisnd_cmd_decode_length((_cmd)->cmd_and_size) >= (size)) + +static inline bool upisnd_cmd_type_has_msg_id(enum upisnd_cmd_type_e type) +{ + switch (type) { + default: + return false; + case UPISND_CMD_SETUP: + case UPISND_CMD_SET_IRQ_TYPES: + case UPISND_CMD_SET_IRQ_MASKS: + case UPISND_CMD_GET_GPIO: + case UPISND_CMD_GET_ALL_GPIOS: + case UPISND_CMD_RESULT: + case UPISND_CMD_GET: + case UPISND_CMD_SET: + return true; + } +} + +#endif // UPISND_PROTOCOL_H + +/* vim: set ts=8 sw=8 noexpandtab: */ diff --git a/sound/drivers/upisnd/upisnd_sound.c b/sound/drivers/upisnd/upisnd_sound.c new file mode 100644 index 000000000000..c3df5e1f1570 --- /dev/null +++ b/sound/drivers/upisnd/upisnd_sound.c @@ -0,0 +1,277 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Pisound Micro Linux kernel module. + * Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 of the + * License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "upisnd_common.h" +#include "upisnd_codec.h" + +static void upisnd_proc_serial_show(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + struct upisnd_instance *instance = entry->private_data; + + snd_iprintf(buffer, "%s\n", instance->ctrl.serial); +} + +static void upisnd_proc_version_show(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + struct upisnd_instance *instance = entry->private_data; + + snd_iprintf(buffer, + "%u.%u.%u\n", + instance->ctrl.version.major, + instance->ctrl.version.minor, + instance->ctrl.version.build); +} + +static void upisnd_proc_hwrev_show(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + struct upisnd_instance *instance = entry->private_data; + + snd_iprintf(buffer, "%u\n", instance->ctrl.version.hwrev); +} + +static int upisnd_card_probe(struct snd_soc_card *card) +{ + struct upisnd_instance *instance = container_of(card, struct upisnd_instance, sound_card); + int err; + + err = upisnd_midi_init(instance); + + if (err < 0) { + printe("Failed to initialize MIDI subsystem! (%d)", err); + return err; + } + + err = snd_card_ro_proc_new(card->snd_card, "serial", instance, upisnd_proc_serial_show); + + if (err < 0) { + printe("Failed to create serial proc entry! (%d)", err); + return err; + } + + err = snd_card_ro_proc_new(card->snd_card, "version", instance, upisnd_proc_version_show); + + if (err < 0) { + printe("Failed to create version proc entry! (%d)", err); + return err; + } + + err = snd_card_ro_proc_new(card->snd_card, "hwrev", instance, upisnd_proc_hwrev_show); + + if (err < 0) { + printe("Failed to create hwrev proc entry! (%d)", err); + return err; + } + + return 0; +} + +static int upisnd_card_remove(struct snd_soc_card *card) +{ + struct upisnd_instance *instance = container_of(card, struct upisnd_instance, sound_card); + + upisnd_midi_uninit(instance); + return 0; +} + +static int upisnd_startup(struct snd_pcm_substream *substream) +{ + printd("startup"); + return 0; +} + +static int upisnd_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) +{ + printd("hw_params"); + + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + int ret = 0; + + struct upisnd_instance *instance = container_of(rtd->card, + struct upisnd_instance, + sound_card); + + if (!(instance->flags & UPISND_FLAG_DUMMY)) + ret = snd_soc_dai_set_bclk_ratio(cpu_dai, 64); + + if (ret < 0) { + printe("Failed setting dai bclk ratio!\n"); + return ret; + } + + return 0; +} + +static int upisnd_dai_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + + return snd_soc_dai_set_tdm_slot(codec_dai, 0, 0, 0, 0); // Enable I2S mode. +} + +static const struct snd_soc_ops upisnd_ops = { + .startup = upisnd_startup, + .hw_params = upisnd_hw_params, +}; + +SND_SOC_DAILINK_DEFS(adau1961, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC("adau1961", "adau-hifi")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static const struct snd_soc_dai_link upisnd_dai_link = { + .name = "pisound-micro", + .stream_name = "pisound-micro PCM", + .dai_fmt = + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBP_CFP, + .ops = &upisnd_ops, + .init = upisnd_dai_init, + SND_SOC_DAILINK_REG(adau1961), +}; + +static const struct snd_soc_card upisnd_sound_card = { + .name = "pisoundmicro", + .owner = THIS_MODULE, + .probe = upisnd_card_probe, + .remove = upisnd_card_remove, +}; + +int upisnd_sound_init(struct platform_device *pdev, struct upisnd_instance *instance) +{ + struct snd_soc_dai_link_component *comp = NULL; + struct device_node *i2s_node = NULL; + + memcpy(&instance->sound_card, &upisnd_sound_card, sizeof(upisnd_sound_card)); + memcpy(&instance->dai_link, &upisnd_dai_link, sizeof(upisnd_dai_link)); + + if (pdev->dev.of_node) { + of_property_read_string(pdev->dev.of_node, "card-name", &instance->sound_card.name); + i2s_node = of_parse_phandle(pdev->dev.of_node, "i2s-controller", 0); + if (!i2s_node) + printi("'i2s-controller' node not specified, will use dummy one instead!"); + } + + comp = devm_kzalloc(&pdev->dev, sizeof(*comp) * 3, GFP_KERNEL); + if (!comp) + return -ENOMEM; + + char *long_name = devm_kzalloc(&pdev->dev, 26, GFP_KERNEL); + + if (!long_name) + return -ENOMEM; + + instance->dai_link.cpus = &comp[0]; + instance->dai_link.codecs = &comp[1]; + instance->dai_link.platforms = &comp[2]; + + instance->dai_link.num_cpus = 1; + instance->dai_link.num_codecs = 1; + instance->dai_link.num_platforms = 1; + + if (i2s_node) { + instance->dai_link.cpus->of_node = i2s_node; + instance->dai_link.platforms->of_node = i2s_node; + } else { + printi("Setting up dummy interface."); + instance->dai_link.cpus->name = "snd-soc-dummy"; + instance->dai_link.platforms->name = "snd-soc-dummy"; + instance->dai_link.cpus->dai_name = "snd-soc-dummy-dai"; + instance->dai_link.platforms->dai_name = "snd-soc-dummy-dai"; + instance->dai_link.dai_fmt = (instance->dai_link.dai_fmt & ~SND_SOC_DAIFMT_CBP_CFP) + | SND_SOC_DAIFMT_CBC_CFC; + instance->flags |= UPISND_FLAG_DUMMY; + } + instance->dai_link.codecs->of_node = instance->codec_dev->of_node; + instance->dai_link.codecs->dai_name = "adau-hifi"; + instance->dai_link.stream_name = instance->ctrl.serial; + + instance->sound_card.dev = &pdev->dev; + + instance->sound_card.dai_link = &instance->dai_link; + instance->sound_card.num_links = 1; + + snprintf(long_name, 26, "Pisound Micro %s", instance->ctrl.serial); + instance->sound_card.long_name = long_name; + printd("About to register card %s", instance->sound_card.long_name); + + int err = snd_soc_register_card(&instance->sound_card); + + if (i2s_node) + of_node_put(i2s_node); + + if (err < 0) { + instance->sound_card.dev = NULL; + if (err != -EPROBE_DEFER) + printe("snd_soc_register_card failed with %d!", err); + return err; + } + + struct snd_soc_dai *dai = snd_soc_card_get_codec_dai(&instance->sound_card, "adau-hifi"); + + if (!dai) { + printe("Failed to get codec dai!"); + instance->sound_card.dev = NULL; + return -ENODEV; + } + + if (adau1961_is_hp_capless(dai->component)) { + err = upisnd_comm_set_subscription(instance, UPISND_IRQ_VGND_SHORT_ALERT, true); + if (err < 0) { + instance->sound_card.dev = NULL; + printe("Failed to subscribe to VGND short alert IRQ! (%d)", err); + return err; + } + } + + return 0; +} + +static void upisnd_sound_handle_irq_event(struct upisnd_instance *instance, + const struct irq_event_t *event) +{ + struct snd_soc_dai *dai = NULL; + + switch (event->num) { + case UPISND_IRQ_VGND_SHORT_ALERT: + dai = snd_soc_card_get_codec_dai(&instance->sound_card, "adau-hifi"); + printe("VGND short alert %s Headphone output!", + event->high ? "ON, muting" : "OFF, restoring last state"); + if (dai) + adau1961_set_vgnd_shorted(dai->component, event->high); + else + printe("Failed to get codec dai!"); + break; + default: + break; + } +} + +void upisnd_sound_handle_irq_events(struct upisnd_instance *instance, + const struct irq_event_t *events, + unsigned int n) +{ + unsigned int i; + + printd("Handling %u sound IRQ events", n); + + for (i = 0; i < n; ++i) + upisnd_sound_handle_irq_event(instance, &events[i]); +} + +/* vim: set ts=8 sw=8 noexpandtab: */ diff --git a/sound/drivers/upisnd/upisnd_sound.h b/sound/drivers/upisnd/upisnd_sound.h new file mode 100644 index 000000000000..100f30733ba2 --- /dev/null +++ b/sound/drivers/upisnd/upisnd_sound.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Pisound Micro Linux kernel module. + * Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 of the + * License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef UPISOUND_SOUND_H +#define UPISOUND_SOUND_H + +int upisnd_sound_init(struct platform_device *pdev, struct upisnd_instance *instance); + +void upisnd_sound_handle_irq_events(struct upisnd_instance *instance, + const struct irq_event_t *events, + unsigned int n); + +#endif // UPISOUND_SOUND_H + +/* vim: set ts=8 sw=8 noexpandtab: */ diff --git a/sound/drivers/upisnd/upisnd_sysfs.c b/sound/drivers/upisnd/upisnd_sysfs.c new file mode 100644 index 000000000000..3feaa1289dba --- /dev/null +++ b/sound/drivers/upisnd/upisnd_sysfs.c @@ -0,0 +1,1826 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Pisound Micro Linux kernel module. + * Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 of the + * License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "upisnd_common.h" + +enum { + // Max lengths are with '\0' included. + MAX_ELEMENT_NAME_LENGTH = 64, + MAX_SETUP_REQUEST_LENGTH = 63 + MAX_ELEMENT_NAME_LENGTH, +}; + +static void upisnd_config_release(struct kobject *kobj); +static void upisnd_element_release(struct kobject *kobj); + +static ssize_t upisnd_attr_show(struct kobject *kobj, struct attribute *attr, char *buf) +{ + printd("hi %s %s", kobject_name(kobj), attr->name); + struct kobj_attribute *attribute = container_of(attr, struct kobj_attribute, attr); + + if (!attribute->show) + return -EIO; + + int err = attribute->show(kobj, attribute, buf); + + return err; +} + +static ssize_t upisnd_attr_store(struct kobject *kobj, + struct attribute *attr, + const char *buf, + size_t len) +{ + struct kobj_attribute *attribute = container_of(attr, struct kobj_attribute, attr); + + if (!attribute->store) + return -EIO; + + int err = attribute->store(kobj, attribute, buf, len); + + return err; +} + +static const struct sysfs_ops upisnd_attr_ops = { + .show = &upisnd_attr_show, + .store = &upisnd_attr_store +}; + +#define to_config(kobj_) container_of(kobj_, struct upisnd_config, kset.kobj) +struct upisnd_config { + struct kset kset; + struct upisnd_instance *instance; + struct kset *elements; +}; + +static int upisnd_element_cleanup(struct kobject *kobj, struct upisnd_instance *instance); + +static void upisnd_config_release(struct kobject *kobj) +{ + printd("hi"); + struct upisnd_config *cfg = to_config(kobj); + + kfree(cfg); +} + +static const struct kobj_type upisnd_config_type = { + .release = &upisnd_config_release, + .sysfs_ops = &upisnd_attr_ops +}; + +static const struct kobj_type upisnd_element_type = { + .release = &upisnd_element_release, + .sysfs_ops = &upisnd_attr_ops +}; + +// Used for handling encoder value overflow. +enum upisnd_value_mode { + UPISND_VALUE_MODE_CLAMP = 0, + UPISND_VALUE_MODE_WRAP, +}; + +#define to_element(kobj_) container_of(kobj_, struct upisnd_element, kobj) +struct upisnd_element { + struct kobject kobj; + // Mapped from upisnd pins to gpio pin numbering. + // gpio_pins[1] is used only for encoder B pin. + // Negative number is used to indicate invalid/unused pin. + int gpio_pins[2]; + int value; + int raw_value; + int input_min; + int input_max; + int value_low; + int value_high; + enum upisnd_value_mode value_mode; +}; + +// Returns true if the value changed. +static bool upisnd_element_update_value(struct upisnd_element *el) +{ + int old_value = el->value; + + int value = el->raw_value; + + switch (el->value_mode) { + case UPISND_VALUE_MODE_CLAMP: + value = min(max(value, el->input_min), el->input_max); + break; + case UPISND_VALUE_MODE_WRAP: + if (el->input_max != el->input_min) { + while (value < el->input_min) + value += el->input_max - el->input_min; + value = (value - el->input_min) % (el->input_max - el->input_min) + + el->input_min; + } else { + value = el->input_min; + } + break; + default: + return false; + } + + el->raw_value = value; + el->value = upisnd_map_value_range(value, el->input_min, el->input_max, el->value_low, + el->value_high); + + return el->value != old_value; +} + +static int upisnd_parse_gpio_dir(enum upisnd_pin_direction_e *dir, char **s, const char *sep) +{ + if (!dir || !s || !sep) + return -EINVAL; + + char *t = strsep(s, sep); + + if (!t || *t == '\0') + return -EINVAL; + + if (strncasecmp(t, "input", 6u) == 0) { + *dir = UPISND_PIN_DIR_INPUT; + return 0; + } else if (strncasecmp(t, "output", 7u) == 0) { + *dir = UPISND_PIN_DIR_OUTPUT; + return 0; + } + + return -EINVAL; +} + +static int upisnd_parse_gpio_input_pull(enum upisnd_pin_pull_e *pull, char **s, const char *sep) +{ + if (!pull || !s || !sep) + return -EINVAL; + + char *t = strsep(s, sep); + + if (!t || *t == '\0') + return -EINVAL; + + if (strncasecmp(t, "pull_up", 8u) == 0) { + *pull = UPISND_PIN_PULL_UP; + return 0; + } else if (strncasecmp(t, "pull_down", 10u) == 0) { + *pull = UPISND_PIN_PULL_DOWN; + return 0; + } else if (strncasecmp(t, "pull_none", 10u) == 0) { + *pull = UPISND_PIN_PULL_NONE; + return 0; + } + + return -EINVAL; +} + +static int upisnd_parse_gpio_output_level(bool *level, char **s, const char *sep) +{ + if (!level || !s || !sep) + return -EINVAL; + + char *t = strsep(s, sep); + + if (!t || *t == '\0') + return -EINVAL; + + return kstrtobool(t, level); +} + +static int upisnd_parse_pin(upisnd_pin_t *pin, char **s, const char *sep) +{ + if (!pin || !s || !sep) + return -EINVAL; + + char *t = strsep(s, sep); + + *pin = upisnd_name_to_pin(t); + + return upisnd_is_pin_valid(*pin) ? 0 : -EINVAL; +} + +static int upisnd_validate_setup(upisnd_setup_t setup) +{ + switch (upisnd_setup_get_element_type(setup)) { + case UPISND_ELEMENT_TYPE_ENCODER: + { + int a = upisnd_check_caps(upisnd_setup_get_pin_id(setup), UPISND_PIN_CAP_ENCODER); + int b = upisnd_check_caps(upisnd_setup_get_encoder_pin_b_id(setup), + UPISND_PIN_CAP_ENCODER); + + if (a < 0 || b < 0) { + printe("Invalid or incapable pins for Encoder (%d, %d)", a, b); + return -EINVAL; + } + return 0; + } + case UPISND_ELEMENT_TYPE_ANALOG_IN: + { + int err = upisnd_check_caps(upisnd_setup_get_pin_id(setup), + UPISND_PIN_CAP_ANALOG_IN); + + if (err < 0) { + printe("Invalid or incapable pin for Analog In (%d)", err); + return err; + } + return 0; + } + case UPISND_ELEMENT_TYPE_GPIO: + { + int err = upisnd_check_caps(upisnd_setup_get_pin_id(setup), UPISND_PIN_CAP_GPIO); + + if (err < 0) { + printe("Invalid or incapable pin for GPIO (%d)", err); + return err; + } + return 0; + } + case UPISND_ELEMENT_TYPE_ACTIVITY: + { + int err; + + switch (upisnd_setup_get_activity_type(setup)) { + case UPISND_ACTIVITY_TYPE_MIDI_IN: + case UPISND_ACTIVITY_TYPE_MIDI_OUT: + err = upisnd_check_caps(upisnd_setup_get_pin_id(setup), + UPISND_PIN_CAP_MIDI_ACTIVITY); + if (err < 0) { + printe("Invalid or incapable pin for MIDI Activity (%d)", err); + return err; + } + break; + default: + printe("Invalid activity type!"); + err = -EINVAL; + break; + } + + return err; + } + break; + case UPISND_ELEMENT_TYPE_NONE: + default: + return -EINVAL; + } +} + +static int upisnd_parse_setup(upisnd_setup_t *setup, const char *buf, size_t len) +{ + if (!setup || !buf || len + 1 >= MAX_SETUP_REQUEST_LENGTH) + return -EINVAL; + + *setup = 0; + upisnd_setup_set_element_type(setup, UPISND_ELEMENT_TYPE_NONE); + + char b[MAX_SETUP_REQUEST_LENGTH]; + + strscpy(b, buf, len + 1); + + static const char *const SEP = "\n\t "; + + char *s = b; + char *token = strsep(&s, SEP); + + if (!token || *token == '\0') + return -EINVAL; + + if (strncasecmp(token, "encoder", 8u) == 0) { + upisnd_setup_set_element_type(setup, UPISND_ELEMENT_TYPE_ENCODER); + } else if (strncasecmp(token, "analog_in", 10u) == 0) { + upisnd_setup_set_element_type(setup, UPISND_ELEMENT_TYPE_ANALOG_IN); + } else if (strncasecmp(token, "activity_", 9u) == 0) { + if (strncasecmp(&token[9], "midi_in", 8u) == 0) + upisnd_setup_set_activity_type(setup, UPISND_ACTIVITY_TYPE_MIDI_IN); + else if (strncasecmp(&token[9], "midi_out", 9u) == 0) + upisnd_setup_set_activity_type(setup, UPISND_ACTIVITY_TYPE_MIDI_OUT); + else + return -EINVAL; + upisnd_setup_set_element_type(setup, UPISND_ELEMENT_TYPE_ACTIVITY); + } else if (strncasecmp(token, "gpio", 5u) == 0) { + upisnd_setup_set_element_type(setup, UPISND_ELEMENT_TYPE_GPIO); + } else { + return -EINVAL; + } + + int err = 0; + + upisnd_pin_t pin; + enum upisnd_pin_direction_e dir; + enum upisnd_pin_pull_e pull; + bool output; + + switch (upisnd_setup_get_element_type(*setup)) { + case UPISND_ELEMENT_TYPE_ANALOG_IN: + case UPISND_ELEMENT_TYPE_ACTIVITY: + case UPISND_ELEMENT_TYPE_GPIO: + case UPISND_ELEMENT_TYPE_ENCODER: + err = upisnd_parse_pin(&pin, &s, SEP); + + if (err != 0) + break; + + upisnd_setup_set_pin_id(setup, pin); + + switch (upisnd_setup_get_element_type(*setup)) { + case UPISND_ELEMENT_TYPE_ENCODER: + err = upisnd_parse_gpio_input_pull(&pull, &s, SEP); + if (err != 0) + break; + upisnd_setup_set_gpio_pull(setup, pull); + + err = upisnd_parse_pin(&pin, &s, SEP); + if (err != 0) + break; + upisnd_setup_set_encoder_pin_b_id(setup, pin); + + err = upisnd_parse_gpio_input_pull(&pull, &s, SEP); + if (err == 0) + upisnd_setup_set_encoder_pin_b_pull(setup, pull); + break; + case UPISND_ELEMENT_TYPE_GPIO: + err = upisnd_parse_gpio_dir(&dir, &s, SEP); + if (err != 0) + break; + + upisnd_setup_set_gpio_dir(setup, dir); + + switch (dir) { + case UPISND_PIN_DIR_INPUT: + err = upisnd_parse_gpio_input_pull(&pull, &s, SEP); + if (err == 0) + upisnd_setup_set_gpio_pull(setup, pull); + break; + case UPISND_PIN_DIR_OUTPUT: + err = upisnd_parse_gpio_output_level(&output, &s, SEP); + if (err == 0) + upisnd_setup_set_gpio_output(setup, output); + break; + default: + return -EINVAL; + } + break; + default: + break; + } + break; + default: + return -EINVAL; + } + + if (err >= 0) + err = upisnd_validate_setup(*setup); + + return err; +} + +static ssize_t upisnd_element_type_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf); +static ssize_t upisnd_element_value_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf); +static ssize_t upisnd_element_value_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t len); +static ssize_t upisnd_element_input_min_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf); +static ssize_t upisnd_element_input_min_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t len); +static ssize_t upisnd_element_input_max_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf); +static ssize_t upisnd_element_input_max_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t len); +static ssize_t upisnd_element_value_low_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf); +static ssize_t upisnd_element_value_low_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t len); +static ssize_t upisnd_element_value_high_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf); +static ssize_t upisnd_element_value_high_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t len); +static ssize_t upisnd_element_value_mode_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf); +static ssize_t upisnd_element_value_mode_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t len); +static ssize_t upisnd_element_direction_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf); +static ssize_t upisnd_element_pin_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf); +static ssize_t upisnd_element_pin_name_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf); +static ssize_t upisnd_element_pin_pull_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf); +static ssize_t upisnd_element_pin_b_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf); +static ssize_t upisnd_element_pin_b_name_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf); +static ssize_t upisnd_element_pin_b_pull_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf); +static ssize_t upisnd_element_activity_type_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf); +static ssize_t upisnd_element_gpio_export(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t len); +static ssize_t upisnd_element_gpio_unexport(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t len); + +static struct kobj_attribute upisnd_element_type_attr = __ATTR(type, 0444, upisnd_element_type_show, + NULL); +static struct kobj_attribute upisnd_element_value_attr = __ATTR(value, 0664, + upisnd_element_value_show, upisnd_element_value_store); +static struct kobj_attribute upisnd_element_input_min_attr = __ATTR(input_min, 0664, + upisnd_element_input_min_show, upisnd_element_input_min_store); +static struct kobj_attribute upisnd_element_input_max_attr = __ATTR(input_max, 0664, + upisnd_element_input_max_show, upisnd_element_input_max_store); +static struct kobj_attribute upisnd_element_value_low_attr = __ATTR(value_low, 0664, + upisnd_element_value_low_show, upisnd_element_value_low_store); +static struct kobj_attribute upisnd_element_value_high_attr = __ATTR(value_high, 0664, + upisnd_element_value_high_show, upisnd_element_value_high_store); +static struct kobj_attribute upisnd_element_value_mode_attr = __ATTR(value_mode, 0664, + upisnd_element_value_mode_show, upisnd_element_value_mode_store); +static struct kobj_attribute upisnd_element_direction_attr = __ATTR(direction, 0444, + upisnd_element_direction_show, NULL); +static struct kobj_attribute upisnd_element_pin_attr = __ATTR(pin, 0444, upisnd_element_pin_show, + NULL); +static struct kobj_attribute upisnd_element_pin_name_attr = __ATTR(pin_name, 0444, + upisnd_element_pin_name_show, NULL); +static struct kobj_attribute upisnd_element_pin_pull_attr = __ATTR(pin_pull, 0444, + upisnd_element_pin_pull_show, NULL); +static struct kobj_attribute upisnd_element_pin_b_attr = __ATTR(pin_b, 0444, + upisnd_element_pin_b_show, NULL); +static struct kobj_attribute upisnd_element_pin_b_name_attr = __ATTR(pin_b_name, 0444, + upisnd_element_pin_b_name_show, NULL); +static struct kobj_attribute upisnd_element_pin_b_pull_attr = __ATTR(pin_b_pull, 0444, + upisnd_element_pin_b_pull_show, NULL); +static struct kobj_attribute upisnd_element_activity_type_attr = __ATTR(activity_type, 0444, + upisnd_element_activity_type_show, NULL); +static struct kobj_attribute upisnd_element_gpio_export_attr = __ATTR(gpio_export, 0220, NULL, + upisnd_element_gpio_export); +static struct kobj_attribute upisnd_element_gpio_unexport_attr = __ATTR(gpio_unexport, 0220, NULL, + upisnd_element_gpio_unexport); + +static struct attribute *upisnd_element_attrs[] = { + &upisnd_element_type_attr.attr, + &upisnd_element_value_attr.attr, + &upisnd_element_input_min_attr.attr, + &upisnd_element_input_max_attr.attr, + &upisnd_element_value_low_attr.attr, + &upisnd_element_value_high_attr.attr, + &upisnd_element_value_mode_attr.attr, + &upisnd_element_direction_attr.attr, + &upisnd_element_pin_attr.attr, + &upisnd_element_pin_name_attr.attr, + &upisnd_element_pin_pull_attr.attr, + &upisnd_element_pin_b_attr.attr, + &upisnd_element_pin_b_name_attr.attr, + &upisnd_element_pin_b_pull_attr.attr, + &upisnd_element_activity_type_attr.attr, + &upisnd_element_gpio_export_attr.attr, + &upisnd_element_gpio_unexport_attr.attr, + NULL +}; + +static umode_t upisnd_element_attr_is_visible(struct kobject *kobj, struct attribute *attr, int idx) +{ + printd("Hi!"); + + struct upisnd_element *element = to_element(kobj); + struct upisnd_config *cfg = to_config(kobj->parent->parent); + struct upisnd_instance *instance = cfg->instance; + + enum upisnd_element_type_e t = upisnd_setup_get_element_type(instance->gpio.pin_configs + [element->gpio_pins[0]].setup); + + switch (t) { + case UPISND_ELEMENT_TYPE_ENCODER: + if (attr == &upisnd_element_value_mode_attr.attr || + attr == &upisnd_element_pin_b_attr.attr || + attr == &upisnd_element_pin_b_name_attr.attr || + attr == &upisnd_element_pin_b_pull_attr.attr) + return attr->mode; + fallthrough; + case UPISND_ELEMENT_TYPE_ANALOG_IN: + if (attr == &upisnd_element_input_min_attr.attr || + attr == &upisnd_element_input_max_attr.attr || + attr == &upisnd_element_value_low_attr.attr || + attr == &upisnd_element_value_high_attr.attr) + return attr->mode; + break; + case UPISND_ELEMENT_TYPE_GPIO: + if (attr == &upisnd_element_gpio_export_attr.attr || + attr == &upisnd_element_gpio_unexport_attr.attr || + attr == &upisnd_element_direction_attr.attr) + return attr->mode; + break; + default: + break; + } + + switch (t) { + case UPISND_ELEMENT_TYPE_ANALOG_IN: + case UPISND_ELEMENT_TYPE_ENCODER: + case UPISND_ELEMENT_TYPE_GPIO: + if (attr == &upisnd_element_value_attr.attr) { + if (t == UPISND_ELEMENT_TYPE_GPIO) { + if (upisnd_setup_get_gpio_dir(instance->gpio.pin_configs + [element->gpio_pins[0]].setup) == UPISND_PIN_DIR_INPUT) + return 0444; + } + return attr->mode; + } else if (attr == &upisnd_element_pin_pull_attr.attr) { + if (t == UPISND_ELEMENT_TYPE_GPIO) { + if (upisnd_setup_get_gpio_dir(instance->gpio.pin_configs + [element->gpio_pins[0]].setup) == UPISND_PIN_DIR_INPUT) + return attr->mode; + } else if (t == UPISND_ELEMENT_TYPE_ENCODER) { + return attr->mode; + } + } + break; + case UPISND_ELEMENT_TYPE_ACTIVITY: + if (attr == &upisnd_element_activity_type_attr.attr) + return attr->mode; + break; + case UPISND_ELEMENT_TYPE_NONE: + default: + break; + } + + if (attr == &upisnd_element_type_attr.attr || + attr == &upisnd_element_pin_attr.attr || + attr == &upisnd_element_pin_name_attr.attr) + return attr->mode; + + return 0; +} + +static struct attribute_group upisnd_element_group = { + .attrs = upisnd_element_attrs, + .is_visible = &upisnd_element_attr_is_visible +}; + +static struct upisnd_element *upisnd_create_element(struct upisnd_config *config, const char *name) +{ + struct upisnd_element *el = kzalloc(sizeof(*el), GFP_KERNEL); + + if (!el) { + printe("Failed allocating upisnd_element!"); + return NULL; + } + + el->kobj.kset = config->elements; + el->gpio_pins[0] = UPISND_PIN_INVALID; + el->gpio_pins[1] = UPISND_PIN_INVALID; + + int err = kobject_init_and_add(&el->kobj, &upisnd_element_type, NULL, "%s", name); + + if (err < 0) { + printe("Failed initializing upisnd_element kobject! (err=%d, name='%s')", + err, name); + goto cleanup; + } + + return el; + +cleanup: + kobject_put(&el->kobj); + kfree(el); + return NULL; +} + +static void upisnd_element_release(struct kobject *kobj) +{ + printd("hi"); + struct upisnd_element *element = to_element(kobj); + + kfree(element); +} + +static int upisnd_setup_get_pins(int pins[2], upisnd_setup_t setup) +{ + pins[0] = UPISND_PIN_INVALID; + pins[1] = UPISND_PIN_INVALID; + + switch (upisnd_setup_get_element_type(setup)) { + case UPISND_ELEMENT_TYPE_ENCODER: + pins[1] = upisnd_setup_get_encoder_pin_b_id(setup); + if (!upisnd_is_pin_valid(pins[1])) + pins[1] = UPISND_PIN_INVALID; + fallthrough; + case UPISND_ELEMENT_TYPE_ANALOG_IN: + case UPISND_ELEMENT_TYPE_GPIO: + case UPISND_ELEMENT_TYPE_NONE: + case UPISND_ELEMENT_TYPE_ACTIVITY: + pins[0] = upisnd_setup_get_pin_id(setup); + if (!upisnd_is_pin_valid(pins[0])) + pins[0] = UPISND_PIN_INVALID; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int upisnd_setup_do(struct upisnd_instance *instance, struct upisnd_element *element, + upisnd_setup_t setup, int pins[2]) +{ + int err; + + element->raw_value = 0; + element->value = 0; + element->input_min = 0; + element->value_low = 0; + element->input_max = upisnd_setup_get_element_type(setup) != + UPISND_ELEMENT_TYPE_ENCODER ? 1023 : 23; + element->value_high = element->input_max; + element->value_mode = UPISND_VALUE_MODE_CLAMP; + + int i; + + for (i = 0; i < 2; ++i) { + if (pins[i] == UPISND_PIN_INVALID) + break; + + int pin = pins[i]; + + printd("Check %d %08x %d %p %p", pin, instance->gpio.pin_configs[pin].setup, + upisnd_setup_get_element_type(instance->gpio.pin_configs[pin].setup), + instance->gpio.pin_configs[pin].element, element); + + if (upisnd_setup_get_element_type(instance->gpio.pin_configs[pin].setup) != + UPISND_ELEMENT_TYPE_NONE && instance->gpio.pin_configs[pin].element != + element) { + printe("Pin %s already used via %s%s!", + upisnd_pin_name(pin), + instance->gpio.pin_configs[pin].element ? "sysfs " : "gpio", + instance->gpio.pin_configs[pin].element ? + instance->gpio.pin_configs[pin].element->kobj.name : ""); + return -EBUSY; + } + } + + // Unsetup previous config if necessary. + for (i = 0; i < 2; ++i) { + if (element->gpio_pins[i] != UPISND_PIN_INVALID && + element->gpio_pins[i] != pins[i]) { + upisnd_setup_t s = instance->gpio.pin_configs[element->gpio_pins[i]].setup; + + upisnd_setup_set_element_type(&s, UPISND_ELEMENT_TYPE_NONE); + upisnd_gpio_setup(instance, s); + instance->gpio.pin_configs[element->gpio_pins[i]].setup = s; + instance->gpio.pin_subscription_refcounts[element->gpio_pins[i]] = 0; + } + } + + err = upisnd_gpio_setup(instance, setup); + + if (err < 0) + return err; + + instance->gpio.pin_configs[pins[0]].element = element; + instance->gpio.pin_configs[pins[0]].flags = 0; + instance->gpio.pin_configs[pins[0]].setup = setup; + instance->gpio.pin_configs[pins[0]].gpio_desc = NULL; + + element->gpio_pins[0] = pins[0]; + element->gpio_pins[1] = pins[1]; + + switch (upisnd_setup_get_element_type(setup)) { + case UPISND_ELEMENT_TYPE_ENCODER: + upisnd_gpio_set_subscription(instance, pins[0], true); + upisnd_gpio_set_subscription(instance, pins[1], true); + instance->gpio.pin_configs[pins[1]].element = element; + instance->gpio.pin_configs[pins[1]].flags = UPISND_PIN_FLAG_IS_ENCODER_B; + instance->gpio.pin_configs[pins[1]].setup = setup; + instance->gpio.pin_configs[pins[1]].gpio_desc = NULL; + break; + case UPISND_ELEMENT_TYPE_GPIO: + if (upisnd_setup_get_gpio_dir(setup) != UPISND_PIN_DIR_INPUT) + break; + fallthrough; + default: + upisnd_gpio_set_subscription(instance, pins[0], true); + break; + } + + return 0; +} + +static ssize_t upisnd_setup(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, + size_t length) +{ + if (length + 1 >= MAX_SETUP_REQUEST_LENGTH) { + printe("Request exceeds maximum length!"); + return -EINVAL; + } + + int err; + + char b[MAX_SETUP_REQUEST_LENGTH]; + + strscpy(b, buf, length + 1); + + char *s = b; + char *name = strsep(&s, " /\n\t"); + + if (!name || *name == '\0' || !s || *s == '\0') + return -EINVAL; + + upisnd_setup_t setup; + + err = upisnd_parse_setup(&setup, s, length - (s - name)); + if (err < 0) { + printe("Failed parsing setup request! (%d)", err); + return err; + } + + int pins[2]; + + upisnd_setup_get_pins(pins, setup); + + struct upisnd_config *cfg = to_config(kobj); + + struct upisnd_instance *instance = cfg->instance; + + down_write(&instance->rw_gpio_config_sem); + + struct kobject *existing = kset_find_obj(cfg->elements, name); + + if (existing) { + bool setup_matches = false; + struct upisnd_element *el = to_element(existing); + + if (upisnd_is_pin_valid(el->gpio_pins[0])) + setup_matches = instance->gpio.pin_configs[el->gpio_pins[0]].setup == setup; + + kobject_put(existing); + up_write(&instance->rw_gpio_config_sem); + + if (setup_matches) { + printd("%s already existed and requested setup matched, returning success.", + name); + return length; + } + + printe("%s is already setup! (%d)", name, -EEXIST); + return -EEXIST; + } + + struct upisnd_element *element = upisnd_create_element(cfg, name); + + if (!element) { + up_write(&instance->rw_gpio_config_sem); + return -ENOMEM; + } + + err = upisnd_setup_do(instance, element, setup, pins); + + if (err >= 0) { + err = sysfs_create_group(&element->kobj, &upisnd_element_group); + if (err < 0) + printe + ("Failed creating pisound-micro element attributes! (err=%d, name ='%s')", + err, name); + } + + up_write(&instance->rw_gpio_config_sem); + + if (err < 0) { + kobject_put(&element->kobj); + return err; + } + + kobject_uevent(&element->kobj, KOBJ_ADD); + return length; +} + +static ssize_t upisnd_unsetup(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, + size_t length) +{ + if (length + 1 >= MAX_ELEMENT_NAME_LENGTH) { + printe("Element name exceeds maximum length!"); + return -EINVAL; + } + + char b[MAX_ELEMENT_NAME_LENGTH]; + + strscpy(b, buf, length + 1); + + char *s = b; + char *name = strsep(&s, " /\n\t"); + + if (!name || *name == '\0') + return -EINVAL; + + struct upisnd_config *cfg = to_config(kobj); + + down_write(&cfg->instance->rw_gpio_config_sem); + + struct kobject *existing = kset_find_obj(cfg->elements, name); + + if (!existing) { + printe("%s not found!", name); + up_write(&cfg->instance->rw_gpio_config_sem); + return -ENOENT; + } + + upisnd_element_cleanup(existing, cfg->instance); + + printd("Found %p", existing); + kobject_put(existing); // kset_find_obj increments the + kobject_put(existing); // refcount, so we have to put twice. + + up_write(&cfg->instance->rw_gpio_config_sem); + + return length; +} + +static ssize_t upisnd_adc_gain_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ + struct upisnd_config *cfg = to_config(kobj); + + s32 adc_gain; + int err = upisnd_comm_get_value(cfg->instance, UPISND_VALUE_ADC_GAIN, &adc_gain); + + if (err < 0) { + printe("Failed getting ADC gain value! (%d)", err); + return err; + } + + return sprintf(buf, "%d\n", adc_gain); +} + +static ssize_t upisnd_adc_gain_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t length) +{ + struct upisnd_config *cfg = to_config(kobj); + + int adc_gain; + int err = kstrtoint(buf, 10, &adc_gain); + + if (err < 0) { + printe("Failed parsing ADC gain! (%d)", err); + return err; + } + + err = upisnd_comm_set_value(cfg->instance, UPISND_VALUE_ADC_GAIN, adc_gain); + if (err < 0) { + printe("Failed setting ADC gain value! (%d)", err); + return err; + } + + return length; +} + +static ssize_t upisnd_adc_offset_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ + struct upisnd_config *cfg = to_config(kobj); + + s32 adc_offset; + int err = upisnd_comm_get_value(cfg->instance, UPISND_VALUE_ADC_OFFSET, &adc_offset); + + if (err < 0) { + printe("Failed getting ADC offset value! (%d)", err); + return err; + } + + return sprintf(buf, "%d\n", adc_offset); +} + +static ssize_t upisnd_adc_offset_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t length) +{ + struct upisnd_config *cfg = to_config(kobj); + + int adc_offset; + int err = kstrtoint(buf, 10, &adc_offset); + + if (err < 0) { + printe("Failed parsing ADC offset! (%d)", err); + return err; + } + + err = upisnd_comm_set_value(cfg->instance, UPISND_VALUE_ADC_OFFSET, adc_offset); + if (err < 0) { + printe("Failed setting ADC offset value! (%d)", err); + return err; + } + + return length; +} + +static struct kobj_attribute upisnd_setup_attr = __ATTR(setup, 0220, NULL, upisnd_setup); +static struct kobj_attribute upisnd_unsetup_attr = __ATTR(unsetup, 0220, NULL, upisnd_unsetup); +static struct kobj_attribute upisnd_adc_gain_attr = __ATTR(adc_gain, 0664, upisnd_adc_gain_show, + upisnd_adc_gain_store); +static struct kobj_attribute upisnd_adc_offset_attr = __ATTR(adc_offset, 0664, + upisnd_adc_offset_show, + upisnd_adc_offset_store); + +static struct attribute *upisnd_root_attrs[] = { + &upisnd_setup_attr.attr, + &upisnd_unsetup_attr.attr, + &upisnd_adc_gain_attr.attr, + &upisnd_adc_offset_attr.attr, + NULL +}; + +static umode_t upisnd_root_is_visible(struct kobject *kobj, struct attribute *attr, int idx) +{ + struct upisnd_config *cfg = to_config(kobj); + struct upisnd_instance *instance = cfg->instance; + + if (!(instance->flags & UPISND_FLAG_ADC_CALIBRATION)) { + if (attr == &upisnd_adc_gain_attr.attr || attr == &upisnd_adc_offset_attr.attr) + return 0; + } + + return attr->mode; +} + +static const struct attribute_group upisnd_root_group = { + .attrs = upisnd_root_attrs, + .is_visible = &upisnd_root_is_visible +}; + +static struct upisnd_config *upisnd_create_config(struct upisnd_instance *instance, + const char *name) +{ + struct upisnd_config *cfg = kzalloc(sizeof(*cfg), GFP_KERNEL); + + if (!cfg) { + printe("Failed allocating upisnd_config!"); + return NULL; + } + + printd("cfg=%p", cfg); + + cfg->instance = instance; + + int err = kobject_set_name(&cfg->kset.kobj, name); + + if (err < 0) { + printe("Failed setting config kset name! (%d)", err); + goto cleanup; + } + + cfg->kset.kobj.ktype = &upisnd_config_type; + err = kset_register(&cfg->kset); + + if (err < 0) { + printe("Failed registering config kset! (%d)", err); + goto cleanup; + } + + cfg->kset.kobj.kset = &cfg->kset; + + cfg->elements = kset_create_and_add("elements", NULL, &cfg->kset.kobj); + if (!cfg->elements) { + printe("Failed creating elements kset!"); + goto cleanup; + } + + err = sysfs_create_group(&cfg->kset.kobj, &upisnd_root_group); + if (err < 0) { + printe("Failed creating pisound-micro attributes! (%d)", err); + goto cleanup; + } + + err = kobject_uevent(&cfg->kset.kobj, KOBJ_ADD); + printd("kobject_uevent(&cfg->kobj, KOBJ_ADD) = %d", err); + + return cfg; + +cleanup: + if (cfg->elements) { + kset_unregister(cfg->elements); + cfg->elements = NULL; + } + + kset_unregister(&cfg->kset); + return NULL; +} + +static ssize_t upisnd_element_gpio_export(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, + size_t len) +{ + if (!kobj || !kobj->parent || !kobj->parent->parent) + return -EINVAL; + + struct upisnd_element *element = to_element(kobj); + struct upisnd_config *cfg = to_config(kobj->parent->parent); + struct upisnd_instance *instance = cfg->instance; + + struct gpio_desc *desc = NULL; + + int pin = element->gpio_pins[0]; + + if (pin == UPISND_PIN_INVALID) { + printe("Element %s is not set up yet!", kobj->name); + return -EINVAL; + } + + down_write(&instance->rw_gpio_config_sem); + if (upisnd_setup_get_element_type(instance->gpio.pin_configs[pin].setup) != + UPISND_ELEMENT_TYPE_GPIO) { + printe("Element %s is not set up as a GPIO!", kobj->name); + up_write(&instance->rw_gpio_config_sem); + return -EINVAL; + } + + instance->gpio.pin_configs[pin].flags |= UPISND_PIN_FLAG_CONFIGURING_THROUGH_SYSFS; + up_write(&instance->rw_gpio_config_sem); + + int err = 0; + + printd("Requesting own %d", pin); + desc = gpiochip_request_own_desc(&instance->gpio.gpio_chip, pin, kobj->name, 0, 0); + if (IS_ERR(desc)) { + printe("Failed requesting own GPIO desc for pin %d! (%ld)", + upisnd_setup_get_pin_id(instance->gpio.pin_configs[pin].setup), + PTR_ERR(desc)); + err = PTR_ERR(desc); + desc = NULL; + } + + if (err >= 0) { + err = gpiod_export(desc, true); + + if (err < 0) { + printe("Failed exporting GPIO via sysfs for pin %d! (%d)", + upisnd_setup_get_pin_id(instance->gpio.pin_configs[pin].setup), err); + gpiochip_free_own_desc(desc); + desc = NULL; + } + } + + printd("Result: %p %d", desc, err); + + down_write(&instance->rw_gpio_config_sem); + instance->gpio.pin_configs[pin].flags = 0; + instance->gpio.pin_configs[pin].gpio_desc = desc; + up_write(&instance->rw_gpio_config_sem); + + return err >= 0 ? len : err; +} + +static ssize_t upisnd_element_gpio_unexport(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t len) +{ + if (!kobj || !kobj->parent || !kobj->parent->parent) + return -EINVAL; + + struct upisnd_element *element = to_element(kobj); + struct upisnd_config *cfg = to_config(kobj->parent->parent); + struct upisnd_instance *instance = cfg->instance; + + int pin = element->gpio_pins[0]; + + if (pin == UPISND_PIN_INVALID) { + printe("Element %s is not set up yet!", kobj->name); + return -EINVAL; + } + + down_write(&instance->rw_gpio_config_sem); + struct gpio_desc *desc = instance->gpio.pin_configs[pin].gpio_desc; + + if (!desc) { + up_write(&instance->rw_gpio_config_sem); + printe("Element %s is not exported via gpio sysfs!", kobj->name); + return -EINVAL; + } + + instance->gpio.pin_configs[pin].flags |= UPISND_PIN_FLAG_CONFIGURING_THROUGH_SYSFS; + up_write(&instance->rw_gpio_config_sem); + + printd("Freeing own %d", pin); + gpiochip_free_own_desc(desc); + + down_write(&instance->rw_gpio_config_sem); + instance->gpio.pin_configs[pin].flags &= ~UPISND_PIN_FLAG_CONFIGURING_THROUGH_SYSFS; + instance->gpio.pin_configs[pin].gpio_desc = NULL; + up_write(&instance->rw_gpio_config_sem); + + return len; +} + +static int upisnd_element_cleanup(struct kobject *kobj, struct upisnd_instance *instance) +{ + printd("(%p)", kobj); + + struct upisnd_element *element = to_element(kobj); + + if (element->gpio_pins[0] == UPISND_PIN_INVALID) + return 0; + + upisnd_setup_t setup = instance->gpio.pin_configs[element->gpio_pins[0]].setup; + + if (upisnd_setup_get_element_type(setup) == UPISND_ELEMENT_TYPE_NONE) { + printe("Pin %d is assigned, but the '%s' element is not set up", + element->gpio_pins[0], kobj->name); + return 0; + } + + if (instance->gpio.pin_configs[element->gpio_pins[0]].gpio_desc) { + printd("Freeing own desc"); + struct gpio_desc *desc = instance->gpio.pin_configs[element->gpio_pins[0]] + .gpio_desc; + + instance->gpio.pin_configs[element->gpio_pins[0]].flags |= + UPISND_PIN_FLAG_CONFIGURING_THROUGH_SYSFS; + up_write(&instance->rw_gpio_config_sem); + gpiochip_free_own_desc(desc); + down_write(&instance->rw_gpio_config_sem); + instance->gpio.pin_configs[element->gpio_pins[0]].flags &= + ~UPISND_PIN_FLAG_CONFIGURING_THROUGH_SYSFS; + printd("Freed"); + } + + upisnd_setup_set_element_type(&setup, UPISND_ELEMENT_TYPE_NONE); + int err = upisnd_gpio_setup(instance, setup); + + int i; + + for (i = 0; i < ARRAY_SIZE(element->gpio_pins); ++i) { + if (element->gpio_pins[i] == UPISND_PIN_INVALID) + break; + + memset(&instance->gpio.pin_configs[element->gpio_pins[i]], 0, + sizeof(struct upisnd_pin_config_t)); + instance->gpio.pin_subscription_refcounts[element->gpio_pins[i]] = 0; + + element->gpio_pins[i] = UPISND_PIN_INVALID; + } + + return err; +} + +static ssize_t upisnd_element_type_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + const char *type = NULL; + struct upisnd_element *element = to_element(kobj); + struct upisnd_config *cfg = to_config(kobj->parent->parent); + struct upisnd_instance *instance = cfg->instance; + + enum upisnd_element_type_e t = UPISND_ELEMENT_TYPE_NONE; + + if (element->gpio_pins[0] != UPISND_PIN_INVALID) { + t = upisnd_setup_get_element_type(instance->gpio.pin_configs[element->gpio_pins[0]] + .setup); + } + + switch (t) { + case UPISND_ELEMENT_TYPE_ENCODER: + type = "encoder"; + break; + case UPISND_ELEMENT_TYPE_ANALOG_IN: + type = "analog_in"; + break; + case UPISND_ELEMENT_TYPE_GPIO: + type = "gpio"; + break; + case UPISND_ELEMENT_TYPE_ACTIVITY: + type = "activity"; + break; + case UPISND_ELEMENT_TYPE_NONE: + type = "none"; + break; + default: + type = "unknown"; + break; + } + + return sprintf(buf, "%s\n", type); +} + +static ssize_t upisnd_element_value_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + struct upisnd_element *element = to_element(kobj); + struct upisnd_config *cfg = to_config(kobj->parent->parent); + struct upisnd_instance *instance = cfg->instance; + + enum upisnd_element_type_e t = UPISND_ELEMENT_TYPE_NONE; + int pin = UPISND_PIN_INVALID; + + if (element->gpio_pins[0] != UPISND_PIN_INVALID) { + t = upisnd_setup_get_element_type(instance->gpio.pin_configs[element->gpio_pins[0]] + .setup); + pin = element->gpio_pins[0]; + } + + switch (t) { + case UPISND_ELEMENT_TYPE_ANALOG_IN: + case UPISND_ELEMENT_TYPE_ENCODER: + return sprintf(buf, "%d\n", element->value); + case UPISND_ELEMENT_TYPE_GPIO: + return sprintf(buf, "%d\n", upisnd_gpio_get(instance, pin)); + case UPISND_ELEMENT_TYPE_ACTIVITY: + case UPISND_ELEMENT_TYPE_NONE: + default: + return 0; + } +} + +static ssize_t upisnd_element_value_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t len) +{ + if (!kobj || !kobj->parent || !kobj->parent->parent) + return -EINVAL; + + struct upisnd_element *element = to_element(kobj); + struct upisnd_config *cfg = to_config(kobj->parent->parent); + struct upisnd_instance *instance = cfg->instance; + + printd("%p %p", kobj, cfg); + + enum upisnd_element_type_e t = UPISND_ELEMENT_TYPE_NONE; + int pin = UPISND_PIN_INVALID; + + if (element->gpio_pins[0] != UPISND_PIN_INVALID) { + t = upisnd_setup_get_element_type(instance->gpio.pin_configs[element->gpio_pins[0]] + .setup); + pin = element->gpio_pins[0]; + } + + int value, err; + bool b; + + bool changed = false; + + switch (t) { + case UPISND_ELEMENT_TYPE_NONE: + return -EINVAL; + case UPISND_ELEMENT_TYPE_GPIO: + err = kstrtobool(buf, &b); + if (err < 0) { + printe("Failed parsing the provided boolean! (%d)", err); + return err; + } + changed = b != upisnd_gpio_get(instance, pin); + upisnd_gpio_set(instance, pin, b); + break; + case UPISND_ELEMENT_TYPE_ENCODER: + case UPISND_ELEMENT_TYPE_ANALOG_IN: + err = kstrtoint(buf, 0, &value); + if (err < 0) { + printe("Failed parsing the provided integer! (%d)", err); + return err; + } + down_write(&instance->rw_gpio_config_sem); + element->raw_value = upisnd_unmap_value_range(value, element->input_min, + element->input_max, + element->value_low, + element->value_high); + changed = upisnd_element_update_value(element); + up_write(&instance->rw_gpio_config_sem); + break; + case UPISND_ELEMENT_TYPE_ACTIVITY: + return -EINVAL; + } + + if (changed) + sysfs_notify(kobj, NULL, "value"); + + return len; +} + +static ssize_t upisnd_element_input_min_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + struct upisnd_element *element = to_element(kobj); + + return sprintf(buf, "%d\n", element->input_min); +} + +static ssize_t upisnd_element_store_value_int(struct upisnd_element *element, int *value, + const char *buf, size_t len) +{ + int v; + int err = kstrtoint(buf, 0, &v); + + if (err < 0) { + printe("Failed parsing the provided integer! (%d)", err); + return err; + } + + struct upisnd_config *cfg = to_config(element->kobj.parent->parent); + struct upisnd_instance *instance = cfg->instance; + + down_write(&instance->rw_gpio_config_sem); + *value = v; + bool changed = upisnd_element_update_value(element); + + up_write(&instance->rw_gpio_config_sem); + + if (changed) + sysfs_notify(&element->kobj, NULL, "value"); + + return len; +} + +static ssize_t upisnd_element_input_min_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t len) +{ + if (!kobj || !kobj->parent || !kobj->parent->parent) + return -EINVAL; + + struct upisnd_element *element = to_element(kobj); + int result = upisnd_element_store_value_int(element, &element->input_min, buf, len); + + if (result >= 0) { + if (element->input_min > element->input_max) { + int t = element->input_min; + + element->input_min = element->input_max; + element->input_max = t; + } + } + return result; +} + +static ssize_t upisnd_element_input_max_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + if (!kobj || !kobj->parent || !kobj->parent->parent) + return -EINVAL; + + struct upisnd_element *element = to_element(kobj); + + return sprintf(buf, "%d\n", element->input_max); +} + +static ssize_t upisnd_element_input_max_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t len) +{ + if (!kobj || !kobj->parent || !kobj->parent->parent) + return -EINVAL; + + struct upisnd_element *element = to_element(kobj); + int result = upisnd_element_store_value_int(element, &element->input_max, buf, len); + + if (result >= 0) { + if (element->input_min > element->input_max) { + int t = element->input_min; + + element->input_min = element->input_max; + element->input_max = t; + } + } + return result; +} + +static ssize_t upisnd_element_value_low_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + if (!kobj || !kobj->parent || !kobj->parent->parent) + return -EINVAL; + + struct upisnd_element *element = to_element(kobj); + + return sprintf(buf, "%d\n", element->value_low); +} + +static ssize_t upisnd_element_value_low_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t len) +{ + if (!kobj || !kobj->parent || !kobj->parent->parent) + return -EINVAL; + + struct upisnd_element *element = to_element(kobj); + + return upisnd_element_store_value_int(element, &element->value_low, buf, len); +} + +static ssize_t upisnd_element_value_high_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + if (!kobj || !kobj->parent || !kobj->parent->parent) + return -EINVAL; + + struct upisnd_element *element = to_element(kobj); + + return sprintf(buf, "%d\n", element->value_high); +} + +static ssize_t upisnd_element_value_high_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t len) +{ + if (!kobj || !kobj->parent || !kobj->parent->parent) + return -EINVAL; + + struct upisnd_element *element = to_element(kobj); + + return upisnd_element_store_value_int(element, &element->value_high, buf, len); +} + +static ssize_t upisnd_element_value_mode_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + if (!kobj || !kobj->parent || !kobj->parent->parent) + return -EINVAL; + + struct upisnd_element *element = to_element(kobj); + + const char *s; + + switch (element->value_mode) { + case UPISND_VALUE_MODE_CLAMP: + s = "clamp"; + break; + case UPISND_VALUE_MODE_WRAP: + s = "wrap"; + break; + default: + return -EINVAL; + } + + return sprintf(buf, "%s\n", s); +} + +static ssize_t upisnd_element_value_mode_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t len) +{ + if (!kobj || !kobj->parent || !kobj->parent->parent) + return -EINVAL; + + static const char *const SEP = "\n\t "; + + char b[8]; + + strscpy(b, buf, min(len + 1, sizeof(b))); + + char *s = b; + char *token = strsep(&s, SEP); + + if (!token || *token == '\0') + return -EINVAL; + + enum upisnd_value_mode mode; + + if (strncasecmp(token, "clamp", 6u) == 0) + mode = UPISND_VALUE_MODE_CLAMP; + else if (strncasecmp(token, "wrap", 5u) == 0) + mode = UPISND_VALUE_MODE_WRAP; + else + return -EINVAL; + + struct upisnd_element *element = to_element(kobj); + struct upisnd_config *cfg = to_config(element->kobj.parent->parent); + struct upisnd_instance *instance = cfg->instance; + + down_write(&instance->rw_gpio_config_sem); + element->value_mode = mode; + bool changed = upisnd_element_update_value(element); + + up_write(&instance->rw_gpio_config_sem); + + if (changed) + sysfs_notify(&element->kobj, NULL, "value"); + + return len; +} + +static ssize_t upisnd_element_direction_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + struct upisnd_element *element = to_element(kobj); + struct upisnd_config *cfg = to_config(kobj->parent->parent); + struct upisnd_instance *instance = cfg->instance; + + enum upisnd_element_type_e t = UPISND_ELEMENT_TYPE_NONE; + int pin = UPISND_PIN_INVALID; + + if (element->gpio_pins[0] != UPISND_PIN_INVALID) { + t = upisnd_setup_get_element_type(instance->gpio.pin_configs[element->gpio_pins[0]] + .setup); + pin = element->gpio_pins[0]; + } + + switch (t) { + case UPISND_ELEMENT_TYPE_ENCODER: + return sprintf(buf, "in\n"); + case UPISND_ELEMENT_TYPE_GPIO: + return sprintf(buf, upisnd_gpio_get_direction(instance, pin) == 0 ? + "out\n" : "in\n"); + case UPISND_ELEMENT_TYPE_ACTIVITY: + return sprintf(buf, "out\n"); + case UPISND_ELEMENT_TYPE_NONE: + default: + return 0; + } +} + +static ssize_t upisnd_element_pin_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ + struct upisnd_element *element = to_element(kobj); + + if (element->gpio_pins[0] != UPISND_PIN_INVALID) + return sprintf(buf, "%d\n", element->gpio_pins[0]); + + return -EINVAL; +} + +static ssize_t upisnd_element_pin_name_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + struct upisnd_element *element = to_element(kobj); + + if (element->gpio_pins[0] != UPISND_PIN_INVALID) + return sprintf(buf, "%s\n", upisnd_pin_name(element->gpio_pins[0])); + + return -EINVAL; +} + +static const char *upisnd_pin_pull_to_string(enum upisnd_pin_pull_e pull) +{ + switch (pull) { + case UPISND_PIN_PULL_NONE: + return "pull_none"; + case UPISND_PIN_PULL_DOWN: + return "pull_down"; + case UPISND_PIN_PULL_UP: + return "pull_up"; + default: + return NULL; + } +} + +static ssize_t upisnd_element_pin_pull_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + struct upisnd_element *element = to_element(kobj); + + struct upisnd_config *cfg = to_config(kobj->parent->parent); + struct upisnd_instance *instance = cfg->instance; + + enum upisnd_element_type_e t = UPISND_ELEMENT_TYPE_NONE; + + if (element->gpio_pins[0] != UPISND_PIN_INVALID) + t = upisnd_setup_get_element_type(instance->gpio.pin_configs[element->gpio_pins[0]] + .setup); + + if (t == UPISND_ELEMENT_TYPE_GPIO) { + if (upisnd_setup_get_gpio_dir(instance->gpio.pin_configs[element->gpio_pins[0]] + .setup) != UPISND_PIN_DIR_INPUT) + return -EINVAL; + } else if (t != UPISND_ELEMENT_TYPE_ENCODER) { + return -EINVAL; + } + + enum upisnd_pin_pull_e pull = upisnd_setup_get_gpio_pull( + instance->gpio.pin_configs[element->gpio_pins[0]].setup); + + switch (pull) { + case UPISND_PIN_PULL_NONE: + case UPISND_PIN_PULL_DOWN: + case UPISND_PIN_PULL_UP: + return sprintf(buf, "%s\n", upisnd_pin_pull_to_string(pull)); + default: + return 0; + } +} + +static ssize_t upisnd_element_pin_b_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + struct upisnd_element *element = to_element(kobj); + + if (element->gpio_pins[1] != UPISND_PIN_INVALID) + return sprintf(buf, "%d\n", element->gpio_pins[1]); + + return -EINVAL; +} + +static ssize_t upisnd_element_pin_b_name_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + struct upisnd_element *element = to_element(kobj); + + if (element->gpio_pins[1] != UPISND_PIN_INVALID) + return sprintf(buf, "%s\n", upisnd_pin_name(element->gpio_pins[1])); + + return -EINVAL; +} + +static ssize_t upisnd_element_pin_b_pull_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + struct upisnd_element *element = to_element(kobj); + + struct upisnd_config *cfg = to_config(kobj->parent->parent); + struct upisnd_instance *instance = cfg->instance; + + enum upisnd_element_type_e t = UPISND_ELEMENT_TYPE_NONE; + + if (element->gpio_pins[0] != UPISND_PIN_INVALID) + t = upisnd_setup_get_element_type(instance->gpio.pin_configs[element->gpio_pins[0]] + .setup); + + if (t != UPISND_ELEMENT_TYPE_ENCODER) + return -EINVAL; + + enum upisnd_pin_pull_e pull = upisnd_setup_get_encoder_pin_b_pull( + instance->gpio.pin_configs[element->gpio_pins[0]].setup); + + switch (pull) { + case UPISND_PIN_PULL_NONE: + case UPISND_PIN_PULL_DOWN: + case UPISND_PIN_PULL_UP: + return sprintf(buf, "%s\n", upisnd_pin_pull_to_string(pull)); + default: + return 0; + } +} + +static ssize_t upisnd_element_activity_type_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + struct upisnd_element *element = to_element(kobj); + + struct upisnd_config *cfg = to_config(kobj->parent->parent); + struct upisnd_instance *instance = cfg->instance; + + enum upisnd_element_type_e t = UPISND_ELEMENT_TYPE_NONE; + + if (element->gpio_pins[0] != UPISND_PIN_INVALID) + t = upisnd_setup_get_element_type(instance->gpio.pin_configs[element->gpio_pins[0]] + .setup); + + if (t != UPISND_ELEMENT_TYPE_ACTIVITY) + return -EINVAL; + + switch (upisnd_setup_get_activity_type(instance->gpio.pin_configs[element->gpio_pins[0]] + .setup)) { + case UPISND_ACTIVITY_TYPE_MIDI_IN: + return sprintf(buf, "midi_in\n"); + case UPISND_ACTIVITY_TYPE_MIDI_OUT: + return sprintf(buf, "midi_out\n"); + default: + return 0; + } +} + +static void upisnd_sysfs_ctrl_event_handler(struct work_struct *work); + +int upisnd_sysfs_init(struct upisnd_instance *instance, const char *name) +{ + instance->config = upisnd_create_config(instance, name ? name : "pisound-micro"); + if (!instance->config) + return -ENOMEM; + + INIT_KFIFO(instance->ctrl_event_fifo); + INIT_WORK(&instance->ctrl_event_handler, upisnd_sysfs_ctrl_event_handler); + + return 0; +} + +void upisnd_sysfs_uninit(struct upisnd_instance *instance) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(instance->gpio.pin_configs); ++i) { + if (instance->gpio.pin_configs[i].gpio_desc) { + gpiochip_free_own_desc(instance->gpio.pin_configs[i].gpio_desc); + instance->gpio.pin_configs[i].gpio_desc = NULL; + } + } + + kfifo_free(&instance->ctrl_event_fifo); + + if (instance->config) { + struct kobject *k, *t; + + if (instance->config->elements) { + list_for_each_entry_safe(k, t, &instance->config->elements->list, entry) { + printd("Putting element %p", k); + kobject_put(k); + } + + printd("Unregistering elements set"); + kset_unregister(instance->config->elements); + instance->config->elements = NULL; + } + + instance->config->kset.kobj.kset = NULL; + kset_unregister(&instance->config->kset); + instance->config = NULL; + } +} + +static void upisnd_sysfs_ctrl_event_handler(struct work_struct *work) +{ + printd("ctrl event handler"); + struct upisnd_instance *instance = container_of(work, struct upisnd_instance, + ctrl_event_handler); + struct kobject *objs[UPISND_NUM_GPIOS]; + + memset(objs, 0, sizeof(objs)); + + unsigned int i, j = 0; + int n; + struct control_event_t events[32]; + + down_write(&instance->rw_gpio_config_sem); + while ((n = kfifo_out(&instance->ctrl_event_fifo, events, 32)) > 0) { + printd("Got %d events", n); + for (i = 0; i < n; ++i) { + upisnd_pin_t pin = events[i].pin; + + if (!upisnd_is_pin_valid(pin)) { + printe("Received invalid pin (%d), ignoring", pin); + continue; + } + + struct upisnd_element *element = instance->gpio.pin_configs[pin].element; + + if (element) { + switch (upisnd_setup_get_element_type(instance->gpio.pin_configs + [pin].setup)) { + case UPISND_ELEMENT_TYPE_ENCODER: + element->raw_value += (int16_t)events[i].raw_value; + printd("Encoder %s, old raw: %d, new raw: %d", + element->kobj.name, + element->raw_value - (int16_t)events[i].raw_value, + element->raw_value); + break; + case UPISND_ELEMENT_TYPE_ANALOG_IN: + element->raw_value = (uint16_t)(events[i].raw_value + & 0x03ff); + printd("Analog in %s, raw: %d", element->kobj.name, + element->raw_value); + break; + case UPISND_ELEMENT_TYPE_GPIO: + if (upisnd_setup_get_gpio_dir(instance->gpio + .pin_configs[pin].setup) == UPISND_PIN_DIR_INPUT) { + element->raw_value = (uint16_t)events[i].raw_value; + printd("Button %s, raw: %d", element->kobj.name, + element->raw_value); + break; + } + break; + default: + printe + ("Got control event for non gpio, enc or analog element %s!" + , element->kobj.name); + continue; + } + + objs[pin] = &element->kobj; + } + } + } + for (i = 0; i < ARRAY_SIZE(objs); ++i) { + if (objs[i] && upisnd_element_update_value(to_element(objs[i]))) + objs[j++] = kobject_get(objs[i]); + } + up_write(&instance->rw_gpio_config_sem); + + for (i = 0; i < j; ++i) { + printd("Notify %d %p", i, objs[i]); + sysfs_notify(objs[i], NULL, "value"); + kobject_put(objs[i]); + } + + if (!kfifo_is_empty(&instance->ctrl_event_fifo) && + !work_pending(&instance->ctrl_event_handler)) + queue_work(instance->work_queue, &instance->ctrl_event_handler); +} + +void upisnd_sysfs_handle_irq_event(struct upisnd_instance *instance, + const struct irq_event_t *events, unsigned int n) +{ + printd("Converting and pushing %u events", n); + struct control_event_t cev; + unsigned int i; + + for (i = 0; i < n; ++i) { + cev.pin = events[i].num; + cev.raw_value = events[i].high ? 1 : 0; + kfifo_put(&instance->ctrl_event_fifo, cev); + } + if (!work_pending(&instance->ctrl_event_handler)) + queue_work(instance->work_queue, &instance->ctrl_event_handler); +} + +void upisnd_sysfs_handle_control_event(struct upisnd_instance *instance, + const struct control_event_t *events, unsigned int n) +{ + printd("Pushing %u events", n); + kfifo_in(&instance->ctrl_event_fifo, events, n); + if (!work_pending(&instance->ctrl_event_handler)) + queue_work(instance->work_queue, &instance->ctrl_event_handler); +} + +/* vim: set ts=8 sw=8 noexpandtab: */ diff --git a/sound/drivers/upisnd/upisnd_sysfs.h b/sound/drivers/upisnd/upisnd_sysfs.h new file mode 100644 index 000000000000..72a5666c0dc6 --- /dev/null +++ b/sound/drivers/upisnd/upisnd_sysfs.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Pisound Micro Linux kernel module. + * Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 of the + * License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef UPISOUND_SYSFS_H +#define UPISOUND_SYSFS_H + +int upisnd_sysfs_init(struct upisnd_instance *instance, const char *name); +void upisnd_sysfs_uninit(struct upisnd_instance *instance); + +void upisnd_sysfs_handle_irq_event(struct upisnd_instance *instance, + const struct irq_event_t *events, + unsigned int n); +void upisnd_sysfs_handle_control_event(struct upisnd_instance *instance, + const struct control_event_t *events, + unsigned int n); + +#endif // UPISOUND_SYSFS_H + +/* vim: set ts=8 sw=8 noexpandtab: */ diff --git a/sound/drivers/upisnd/upisnd_utils.c b/sound/drivers/upisnd/upisnd_utils.c new file mode 100644 index 000000000000..bf3346512d74 --- /dev/null +++ b/sound/drivers/upisnd/upisnd_utils.c @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Pisound Micro Linux kernel module. + * Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 of the + * License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "upisnd_common.h" + +int upisnd_map_value_range(int v, int input_min, int input_max, int output_min, int output_max) +{ + int x = (v - input_min) * (output_max - output_min); + + return x / (input_max - input_min) + output_min; +} + +int upisnd_unmap_value_range(int v, int input_min, int input_max, int output_min, int output_max) +{ + int x = (v - output_min) * (input_max - input_min); + + return x / (output_max - output_min) + input_min; +} + +/* vim: set ts=8 sw=8 noexpandtab: */ diff --git a/sound/drivers/upisnd/upisnd_utils.h b/sound/drivers/upisnd/upisnd_utils.h new file mode 100644 index 000000000000..e4b41f52fad0 --- /dev/null +++ b/sound/drivers/upisnd/upisnd_utils.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Pisound Micro Linux kernel module. + * Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 of the + * License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef UPISOUND_UTILS_H +#define UPISOUND_UTILS_H + +int upisnd_map_value_range(int v, int input_min, int input_max, int output_min, int output_max); +int upisnd_unmap_value_range(int v, int input_min, int input_max, int output_min, int output_max); + +#endif // UPISOUND_UTILS_H + +/* vim: set ts=8 sw=8 noexpandtab: */ diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index ce74818bd715..c2521d6d8342 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -123,6 +123,7 @@ source "sound/soc/meson/Kconfig" source "sound/soc/mxs/Kconfig" source "sound/soc/pxa/Kconfig" source "sound/soc/qcom/Kconfig" +source "sound/soc/raspberrypi/Kconfig" source "sound/soc/renesas/Kconfig" source "sound/soc/rockchip/Kconfig" source "sound/soc/samsung/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 462322c38aa4..c99ebbcc6652 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -65,6 +65,7 @@ obj-$(CONFIG_SND_SOC) += mxs/ obj-$(CONFIG_SND_SOC) += kirkwood/ obj-$(CONFIG_SND_SOC) += pxa/ obj-$(CONFIG_SND_SOC) += qcom/ +obj-$(CONFIG_SND_SOC) += raspberrypi/ obj-$(CONFIG_SND_SOC) += renesas/ obj-$(CONFIG_SND_SOC) += rockchip/ obj-$(CONFIG_SND_SOC) += samsung/ diff --git a/sound/soc/bcm/Kconfig b/sound/soc/bcm/Kconfig index de4e8a0daf1c..07532624467a 100644 --- a/sound/soc/bcm/Kconfig +++ b/sound/soc/bcm/Kconfig @@ -29,4 +29,288 @@ config SND_BCM63XX_I2S_WHISTLER If you don't know what to do here, say N +config SND_BCM2708_SOC_CHIPDIP_DAC + tristate "Support for the ChipDip DAC" + help + Say Y or M if you want to add support for the ChipDip DAC soundcard + +config SND_BCM2708_SOC_GOOGLEVOICEHAT_SOUNDCARD + tristate "Support for Google voiceHAT soundcard" + select SND_SOC_VOICEHAT + select SND_RPI_SIMPLE_SOUNDCARD + help + Say Y or M if you want to add support for voiceHAT soundcard. + +config SND_BCM2708_SOC_HIFIBERRY_ADC + tristate "Support for HifiBerry ADC" + select SND_SOC_PCM186X_I2C + select SND_RPI_HIFIBERRY_ADC + help + Say Y or M if you want to add support for HifiBerry ADC. + Use this module for HiFiBerry's ADC-only sound cards + +config SND_BCM2708_SOC_HIFIBERRY_ADC8X + tristate "Support for HifiBerry ADC8X" + select SND_RPI_SIMPLE_SOUNDCARD + help + Say Y or M if you want to add support for HifiBerry ADC8X. + Note: ADC8X only works on PI5 + +config SND_BCM2708_SOC_HIFIBERRY_DAC + tristate "Support for HifiBerry DAC and DAC8X" + select SND_SOC_PCM5102A + select SND_RPI_SIMPLE_SOUNDCARD + help + Say Y or M if you want to add support for HifiBerry DAC and DAC8X. + Note: DAC8X only works on PI5 + +config SND_BCM2708_SOC_HIFIBERRY_DACPLUS + tristate "Support for HifiBerry DAC+" + select SND_SOC_PCM512x + select SND_SOC_TPA6130A2 + select COMMON_CLK_HIFIBERRY_DACPRO + help + Say Y or M if you want to add support for HifiBerry DAC+. + +config SND_BCM2708_SOC_HIFIBERRY_DACPLUSHD + tristate "Support for HifiBerry DAC+ HD" + select SND_SOC_PCM179X_I2C + select COMMON_CLK_HIFIBERRY_DACPLUSHD + help + Say Y or M if you want to add support for HifiBerry DAC+ HD. + +config SND_BCM2708_SOC_HIFIBERRY_DACPLUSADC + tristate "Support for HifiBerry DAC+ADC" + select SND_SOC_PCM512x_I2C + select SND_SOC_DMIC + select COMMON_CLK_HIFIBERRY_DACPRO + help + Say Y or M if you want to add support for HifiBerry DAC+ADC. + +config SND_BCM2708_SOC_HIFIBERRY_DACPLUSADCPRO + tristate "Support for HifiBerry DAC+ADC PRO" + select SND_SOC_PCM512x_I2C + select SND_SOC_PCM186X_I2C + select SND_SOC_TPA6130A2 + select COMMON_CLK_HIFIBERRY_DACPRO + help + Say Y or M if you want to add support for HifiBerry DAC+ADC PRO. + +config SND_BCM2708_SOC_HIFIBERRY_DACPLUSDSP + tristate "Support for HifiBerry DAC+DSP" + select SND_RPI_SIMPLE_SOUNDCARD + help + Say Y or M if you want to add support for HifiBerry DSP-DAC. + +config SND_BCM2708_SOC_HIFIBERRY_DIGI + tristate "Support for HifiBerry Digi" + select SND_SOC_WM8804 + help + Say Y or M if you want to add support for HifiBerry Digi S/PDIF output board. + +config SND_BCM2708_SOC_HIFIBERRY_AMP + tristate "Support for the HifiBerry Amp" + select SND_SOC_TAS5713 + select SND_RPI_SIMPLE_SOUNDCARD + help + Say Y or M if you want to add support for the HifiBerry Amp amplifier board. + +config SND_BCM2708_SOC_PIFI_40 + tristate "Support for the PiFi-40 amp" + select SND_SOC_TAS571X + select SND_PIFI_40 + help + Say Y or M if you want to add support for the PiFi40 amp board + +config SND_BCM2708_SOC_RPI_CIRRUS + tristate "Support for Cirrus Logic Audio Card" + select SND_SOC_WM5102 + select SND_SOC_WM8804 + help + Say Y or M if you want to add support for the Wolfson and + Cirrus Logic audio cards. + +config SND_BCM2708_SOC_RPI_DAC + tristate "Support for RPi-DAC" + select SND_SOC_PCM1794A + select SND_RPI_SIMPLE_SOUNDCARD + help + Say Y or M if you want to add support for RPi-DAC. + +config SND_BCM2708_SOC_RPI_PROTO + tristate "Support for Rpi-PROTO" + select SND_SOC_WM8731_I2C + help + Say Y or M if you want to add support for Audio Codec Board PROTO (WM8731). + +config SND_BCM2708_SOC_JUSTBOOM_BOTH + tristate "Support for simultaneous JustBoom Digi and JustBoom DAC" + select SND_SOC_WM8804 + select SND_SOC_PCM512x + help + Say Y or M if you want to add support for simultaneous + JustBoom Digi and JustBoom DAC. + + This is not the right choice if you only have one but both of + these cards. + +config SND_BCM2708_SOC_JUSTBOOM_DAC + tristate "Support for JustBoom DAC" + select SND_SOC_PCM512x + help + Say Y or M if you want to add support for JustBoom DAC. + +config SND_BCM2708_SOC_JUSTBOOM_DIGI + tristate "Support for JustBoom Digi" + select SND_SOC_WM8804 + select SND_RPI_WM8804_SOUNDCARD + help + Say Y or M if you want to add support for JustBoom Digi. + +config SND_BCM2708_SOC_IQAUDIO_CODEC + tristate "Support for IQaudIO-CODEC" + select SND_SOC_DA7213 + help + Say Y or M if you want to add support for IQaudIO-CODEC. + +config SND_BCM2708_SOC_IQAUDIO_DAC + tristate "Support for IQaudIO-DAC" + select SND_SOC_PCM512x_I2C + help + Say Y or M if you want to add support for IQaudIO-DAC. + +config SND_BCM2708_SOC_IQAUDIO_DIGI + tristate "Support for IQAudIO Digi" + select SND_SOC_WM8804 + select SND_RPI_WM8804_SOUNDCARD + help + Say Y or M if you want to add support for IQAudIO Digital IO board. + +config SND_BCM2708_SOC_I_SABRE_Q2M + tristate "Support for Audiophonics I-Sabre Q2M DAC" + select SND_SOC_I_SABRE_CODEC + help + Say Y or M if you want to add support for Audiophonics I-SABRE Q2M DAC + +config SND_BCM2708_SOC_ADAU1977_ADC + tristate "Support for ADAU1977 ADC" + select SND_SOC_ADAU1977_I2C + select SND_RPI_SIMPLE_SOUNDCARD + help + Say Y or M if you want to add support for ADAU1977 ADC. + +config SND_AUDIOINJECTOR_PI_SOUNDCARD + tristate "Support for audioinjector.net Pi add on soundcard" + select SND_SOC_WM8731_I2C + help + Say Y or M if you want to add support for audioinjector.net Pi Hat + +config SND_AUDIOINJECTOR_OCTO_SOUNDCARD + tristate "Support for audioinjector.net Octo channel (Hat) soundcard" + select SND_SOC_CS42XX8_I2C + help + Say Y or M if you want to add support for audioinjector.net octo add on + +config SND_AUDIOINJECTOR_ISOLATED_SOUNDCARD + tristate "Support for audioinjector.net isolated DAC and ADC soundcard" + select SND_SOC_CS4271_I2C + help + Say Y or M if you want to add support for audioinjector.net isolated soundcard + +config SND_AUDIOSENSE_PI + tristate "Support for AudioSense Add-On Soundcard" + select SND_SOC_TLV320AIC32X4_I2C + help + Say Y or M if you want to add support for tlv320aic32x4 add-on + +config SND_DIGIDAC1_SOUNDCARD + tristate "Support for Red Rocks Audio DigiDAC1" + select SND_SOC_WM8804 + select SND_SOC_WM8741 + help + Say Y or M if you want to add support for Red Rocks Audio DigiDAC1 board. + +config SND_BCM2708_SOC_DIONAUDIO_LOCO + tristate "Support for Dion Audio LOCO DAC-AMP" + select SND_SOC_PCM5102a + help + Say Y or M if you want to add support for Dion Audio LOCO. + +config SND_BCM2708_SOC_DIONAUDIO_LOCO_V2 + tristate "Support for Dion Audio LOCO-V2 DAC-AMP" + select SND_SOC_PCM5122 + help + Say Y or M if you want to add support for Dion Audio LOCO-V2. + +config SND_BCM2708_SOC_ALLO_PIANO_DAC + tristate "Support for Allo Piano DAC" + select SND_SOC_PCM512x_I2C + help + Say Y or M if you want to add support for Allo Piano DAC. + +config SND_BCM2708_SOC_ALLO_PIANO_DAC_PLUS + tristate "Support for Allo Piano DAC Plus" + select SND_SOC_PCM512x_I2C + help + Say Y or M if you want to add support for Allo Piano DAC Plus. + +config SND_BCM2708_SOC_ALLO_BOSS_DAC + tristate "Support for Allo Boss DAC" + select SND_SOC_PCM512x_I2C + select COMMON_CLK_HIFIBERRY_DACPRO + help + Say Y or M if you want to add support for Allo Boss DAC. + +config SND_BCM2708_SOC_ALLO_BOSS2_DAC + tristate "Support for Allo Boss2 DAC" + depends on I2C + select REGMAP_I2C + select SND_AUDIO_GRAPH_CARD + help + Say Y or M if you want to add support for Allo Boss2 DAC. + +config SND_BCM2708_SOC_ALLO_DIGIONE + tristate "Support for Allo DigiOne" + select SND_SOC_WM8804 + select SND_RPI_WM8804_SOUNDCARD + help + Say Y or M if you want to add support for Allo DigiOne. + +config SND_BCM2708_SOC_ALLO_KATANA_DAC + tristate "Support for Allo Katana DAC" + depends on I2C + select REGMAP_I2C + select SND_AUDIO_GRAPH_CARD + help + Say Y or M if you want to add support for Allo Katana DAC. + +config SND_BCM2708_SOC_FE_PI_AUDIO + tristate "Support for Fe-Pi-Audio" + select SND_SOC_SGTL5000 + help + Say Y or M if you want to add support for Fe-Pi-Audio. + +config SND_PISOUND + tristate "Support for Blokas Labs pisound" + select SND_RAWMIDI + help + Say Y or M if you want to add support for Blokas Labs pisound. + +config SND_RPI_SIMPLE_SOUNDCARD + tristate "Support for Raspberry Pi simple soundcards" + help + Say Y or M if you want to add support Raspbery Pi simple soundcards + +config SND_RPI_WM8804_SOUNDCARD + tristate "Support for Raspberry Pi generic WM8804 soundcards" + help + Say Y or M if you want to add support for the Raspberry Pi + generic driver for WM8804 based soundcards. + +config SND_DACBERRY400 + tristate "Support for DACBERRY400 Soundcard" + select SND_SOC_TLV320AIC3X_I2C + help + Say Y or M if you want to add support for tlv320aic3x add-on + endmenu diff --git a/sound/soc/bcm/Makefile b/sound/soc/bcm/Makefile index 0c1325a97b70..15c3c4a742c6 100644 --- a/sound/soc/bcm/Makefile +++ b/sound/soc/bcm/Makefile @@ -12,4 +12,75 @@ obj-$(CONFIG_SND_SOC_CYGNUS) += snd-soc-cygnus.o # BCM63XX Platform Support snd-soc-63xx-y := bcm63xx-i2s-whistler.o bcm63xx-pcm-whistler.o -obj-$(CONFIG_SND_BCM63XX_I2S_WHISTLER) += snd-soc-63xx.o \ No newline at end of file +obj-$(CONFIG_SND_BCM63XX_I2S_WHISTLER) += snd-soc-63xx.o + +# Google voiceHAT custom codec support +snd-soc-googlevoicehat-codec-objs := googlevoicehat-codec.o + +# BCM2708 Machine Support +snd-soc-hifiberry-adc-objs := hifiberry_adc.o +snd-soc-hifiberry-dacplus-objs := hifiberry_dacplus.o +snd-soc-hifiberry-dacplushd-objs := hifiberry_dacplushd.o +snd-soc-hifiberry-dacplusadc-objs := hifiberry_dacplusadc.o +snd-soc-hifiberry-dacplusadcpro-objs := hifiberry_dacplusadcpro.o +snd-soc-hifiberry-dacplusdsp-objs := hifiberry_dacplusdsp.o +snd-soc-justboom-both-objs := justboom-both.o +snd-soc-justboom-dac-objs := justboom-dac.o +snd-soc-rpi-cirrus-objs := rpi-cirrus.o +snd-soc-rpi-proto-objs := rpi-proto.o +snd-soc-iqaudio-codec-objs := iqaudio-codec.o +snd-soc-iqaudio-dac-objs := iqaudio-dac.o + snd-soc-i-sabre-q2m-objs := i-sabre-q2m.o +snd-soc-audioinjector-pi-soundcard-objs := audioinjector-pi-soundcard.o +snd-soc-audioinjector-octo-soundcard-objs := audioinjector-octo-soundcard.o +snd-soc-audioinjector-isolated-soundcard-objs := audioinjector-isolated-soundcard.o +snd-soc-audiosense-pi-objs := audiosense-pi.o +snd-soc-digidac1-soundcard-objs := digidac1-soundcard.o +snd-soc-dionaudio-loco-objs := dionaudio_loco.o +snd-soc-dionaudio-loco-v2-objs := dionaudio_loco-v2.o +snd-soc-allo-boss-dac-objs := allo-boss-dac.o +snd-soc-allo-boss2-dac-objs := allo-boss2-dac.o +snd-soc-allo-piano-dac-objs := allo-piano-dac.o +snd-soc-allo-piano-dac-plus-objs := allo-piano-dac-plus.o +snd-soc-allo-katana-codec-objs := allo-katana-codec.o +snd-soc-pisound-objs := pisound.o +snd-soc-fe-pi-audio-objs := fe-pi-audio.o +snd-soc-rpi-simple-soundcard-objs := rpi-simple-soundcard.o +snd-soc-rpi-wm8804-soundcard-objs := rpi-wm8804-soundcard.o +snd-soc-pifi-40-objs := pifi-40.o +snd-soc-chipdip-dac-objs := chipdip-dac.o +snd-soc-dacberry400-objs := dacberry400.o + +obj-$(CONFIG_SND_BCM2708_SOC_GOOGLEVOICEHAT_SOUNDCARD) += snd-soc-googlevoicehat-codec.o +obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_ADC) += snd-soc-hifiberry-adc.o +obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUS) += snd-soc-hifiberry-dacplus.o +obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSHD) += snd-soc-hifiberry-dacplushd.o +obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSADC) += snd-soc-hifiberry-dacplusadc.o +obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSADCPRO) += snd-soc-hifiberry-dacplusadcpro.o +obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSDSP) += snd-soc-hifiberry-dacplusdsp.o +obj-$(CONFIG_SND_BCM2708_SOC_JUSTBOOM_BOTH) += snd-soc-justboom-both.o +obj-$(CONFIG_SND_BCM2708_SOC_JUSTBOOM_DAC) += snd-soc-justboom-dac.o +obj-$(CONFIG_SND_BCM2708_SOC_RPI_CIRRUS) += snd-soc-rpi-cirrus.o +obj-$(CONFIG_SND_BCM2708_SOC_RPI_PROTO) += snd-soc-rpi-proto.o +obj-$(CONFIG_SND_BCM2708_SOC_IQAUDIO_CODEC) += snd-soc-iqaudio-codec.o +obj-$(CONFIG_SND_BCM2708_SOC_IQAUDIO_DAC) += snd-soc-iqaudio-dac.o +obj-$(CONFIG_SND_BCM2708_SOC_I_SABRE_Q2M) += snd-soc-i-sabre-q2m.o +obj-$(CONFIG_SND_AUDIOINJECTOR_PI_SOUNDCARD) += snd-soc-audioinjector-pi-soundcard.o +obj-$(CONFIG_SND_AUDIOINJECTOR_OCTO_SOUNDCARD) += snd-soc-audioinjector-octo-soundcard.o +obj-$(CONFIG_SND_AUDIOINJECTOR_ISOLATED_SOUNDCARD) += snd-soc-audioinjector-isolated-soundcard.o +obj-$(CONFIG_SND_AUDIOSENSE_PI) += snd-soc-audiosense-pi.o +obj-$(CONFIG_SND_DIGIDAC1_SOUNDCARD) += snd-soc-digidac1-soundcard.o +obj-$(CONFIG_SND_BCM2708_SOC_DIONAUDIO_LOCO) += snd-soc-dionaudio-loco.o +obj-$(CONFIG_SND_BCM2708_SOC_DIONAUDIO_LOCO_V2) += snd-soc-dionaudio-loco-v2.o +obj-$(CONFIG_SND_BCM2708_SOC_ALLO_BOSS_DAC) += snd-soc-allo-boss-dac.o +obj-$(CONFIG_SND_BCM2708_SOC_ALLO_BOSS2_DAC) += snd-soc-allo-boss2-dac.o +obj-$(CONFIG_SND_BCM2708_SOC_ALLO_PIANO_DAC) += snd-soc-allo-piano-dac.o +obj-$(CONFIG_SND_BCM2708_SOC_ALLO_PIANO_DAC_PLUS) += snd-soc-allo-piano-dac-plus.o +obj-$(CONFIG_SND_BCM2708_SOC_ALLO_KATANA_DAC) += snd-soc-allo-katana-codec.o +obj-$(CONFIG_SND_PISOUND) += snd-soc-pisound.o +obj-$(CONFIG_SND_BCM2708_SOC_FE_PI_AUDIO) += snd-soc-fe-pi-audio.o +obj-$(CONFIG_SND_RPI_SIMPLE_SOUNDCARD) += snd-soc-rpi-simple-soundcard.o +obj-$(CONFIG_SND_RPI_WM8804_SOUNDCARD) += snd-soc-rpi-wm8804-soundcard.o +obj-$(CONFIG_SND_BCM2708_SOC_PIFI_40) += snd-soc-pifi-40.o +obj-$(CONFIG_SND_BCM2708_SOC_CHIPDIP_DAC) += snd-soc-chipdip-dac.o +obj-$(CONFIG_SND_DACBERRY400) += snd-soc-dacberry400.o diff --git a/sound/soc/bcm/allo-boss-dac.c b/sound/soc/bcm/allo-boss-dac.c new file mode 100644 index 000000000000..342a03e6a7f8 --- /dev/null +++ b/sound/soc/bcm/allo-boss-dac.c @@ -0,0 +1,470 @@ +/* + * ALSA ASoC Machine Driver for Allo Boss DAC + * + * Author: Baswaraj K + * Copyright 2017 + * based on code by Daniel Matuschek, + * Stuart MacLean + * based on code by Florian Meier + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "../codecs/pcm512x.h" + +#define ALLO_BOSS_NOCLOCK 0 +#define ALLO_BOSS_CLK44EN 1 +#define ALLO_BOSS_CLK48EN 2 + +struct pcm512x_priv { + struct regmap *regmap; + struct clk *sclk; +}; + +static struct gpio_desc *mute_gpio; + +/* Clock rate of CLK44EN attached to GPIO6 pin */ +#define CLK_44EN_RATE 45158400UL +/* Clock rate of CLK48EN attached to GPIO3 pin */ +#define CLK_48EN_RATE 49152000UL + +static bool slave; +static bool snd_soc_allo_boss_master; +static bool digital_gain_0db_limit = true; + +static void snd_allo_boss_select_clk(struct snd_soc_component *component, + int clk_id) +{ + switch (clk_id) { + case ALLO_BOSS_NOCLOCK: + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x00); + break; + case ALLO_BOSS_CLK44EN: + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x20); + break; + case ALLO_BOSS_CLK48EN: + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x04); + break; + } +} + +static void snd_allo_boss_clk_gpio(struct snd_soc_component *component) +{ + snd_soc_component_update_bits(component, PCM512x_GPIO_EN, 0x24, 0x24); + snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_3, 0x0f, 0x02); + snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_6, 0x0f, 0x02); +} + +static bool snd_allo_boss_is_sclk(struct snd_soc_component *component) +{ + unsigned int sck; + + sck = snd_soc_component_read(component, PCM512x_RATE_DET_4); + return (!(sck & 0x40)); +} + +static bool snd_allo_boss_is_sclk_sleep( + struct snd_soc_component *component) +{ + msleep(2); + return snd_allo_boss_is_sclk(component); +} + +static bool snd_allo_boss_is_master_card(struct snd_soc_component *component) +{ + bool isClk44EN, isClk48En, isNoClk; + + snd_allo_boss_clk_gpio(component); + + snd_allo_boss_select_clk(component, ALLO_BOSS_CLK44EN); + isClk44EN = snd_allo_boss_is_sclk_sleep(component); + + snd_allo_boss_select_clk(component, ALLO_BOSS_NOCLOCK); + isNoClk = snd_allo_boss_is_sclk_sleep(component); + + snd_allo_boss_select_clk(component, ALLO_BOSS_CLK48EN); + isClk48En = snd_allo_boss_is_sclk_sleep(component); + + return (isClk44EN && isClk48En && !isNoClk); +} + +static int snd_allo_boss_clk_for_rate(int sample_rate) +{ + int type; + + switch (sample_rate) { + case 11025: + case 22050: + case 44100: + case 88200: + case 176400: + case 352800: + type = ALLO_BOSS_CLK44EN; + break; + default: + type = ALLO_BOSS_CLK48EN; + break; + } + return type; +} + +static void snd_allo_boss_set_sclk(struct snd_soc_component *component, + int sample_rate) +{ + struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component); + + if (!IS_ERR(pcm512x->sclk)) { + int ctype; + + ctype = snd_allo_boss_clk_for_rate(sample_rate); + clk_set_rate(pcm512x->sclk, (ctype == ALLO_BOSS_CLK44EN) + ? CLK_44EN_RATE : CLK_48EN_RATE); + snd_allo_boss_select_clk(component, ctype); + } +} + +static int snd_allo_boss_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + struct pcm512x_priv *priv = snd_soc_component_get_drvdata(component); + + if (slave) + snd_soc_allo_boss_master = false; + else + snd_soc_allo_boss_master = + snd_allo_boss_is_master_card(component); + + if (snd_soc_allo_boss_master) { + struct snd_soc_dai_link *dai = rtd->dai_link; + + dai->name = "BossDAC"; + dai->stream_name = "Boss DAC HiFi [Master]"; + dai->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBP_CFP; + + snd_soc_component_update_bits(component, PCM512x_BCLK_LRCLK_CFG, 0x31, 0x11); + snd_soc_component_update_bits(component, PCM512x_MASTER_MODE, 0x03, 0x03); + snd_soc_component_update_bits(component, PCM512x_MASTER_CLKDIV_2, 0x7f, 63); + /* + * Default sclk to CLK_48EN_RATE, otherwise codec + * pcm512x_dai_startup_master method could call + * snd_pcm_hw_constraint_ratnums using CLK_44EN/64 + * which will mask 384k sample rate. + */ + if (!IS_ERR(priv->sclk)) + clk_set_rate(priv->sclk, CLK_48EN_RATE); + } else { + priv->sclk = ERR_PTR(-ENOENT); + } + + snd_soc_component_update_bits(component, PCM512x_GPIO_EN, 0x08, 0x08); + snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_4, 0x0f, 0x02); + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x08); + + if (digital_gain_0db_limit) { + int ret; + struct snd_soc_card *card = rtd->card; + + ret = snd_soc_limit_volume(card, "Digital Playback Volume", + 207); + if (ret < 0) + dev_warn(card->dev, "Failed to set volume limit: %d\n", + ret); + } + + return 0; +} + +static int snd_allo_boss_update_rate_den( + struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component); + struct snd_ratnum *rats_no_pll; + unsigned int num = 0, den = 0; + int err; + + rats_no_pll = devm_kzalloc(rtd->dev, sizeof(*rats_no_pll), GFP_KERNEL); + if (!rats_no_pll) + return -ENOMEM; + + rats_no_pll->num = clk_get_rate(pcm512x->sclk) / 64; + rats_no_pll->den_min = 1; + rats_no_pll->den_max = 128; + rats_no_pll->den_step = 1; + + err = snd_interval_ratnum(hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE), 1, rats_no_pll, &num, &den); + if (err >= 0 && den) { + params->rate_num = num; + params->rate_den = den; + } + + devm_kfree(rtd->dev, rats_no_pll); + return 0; +} + +static void snd_allo_boss_gpio_mute(struct snd_soc_card *card) +{ + if (mute_gpio) + gpiod_set_value_cansleep(mute_gpio, 1); +} + +static void snd_allo_boss_gpio_unmute(struct snd_soc_card *card) +{ + if (mute_gpio) + gpiod_set_value_cansleep(mute_gpio, 0); +} + +static int snd_allo_boss_set_bias_level(struct snd_soc_card *card, + struct snd_soc_dapm_context *dapm, enum snd_soc_bias_level level) +{ + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_dai *codec_dai; + + rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]); + codec_dai = snd_soc_rtd_to_codec(rtd, 0); + + if (dapm->dev != codec_dai->dev) + return 0; + + switch (level) { + case SND_SOC_BIAS_PREPARE: + if (dapm->bias_level != SND_SOC_BIAS_STANDBY) + break; + /* UNMUTE DAC */ + snd_allo_boss_gpio_unmute(card); + break; + + case SND_SOC_BIAS_STANDBY: + if (dapm->bias_level != SND_SOC_BIAS_PREPARE) + break; + /* MUTE DAC */ + snd_allo_boss_gpio_mute(card); + break; + + default: + break; + } + + return 0; +} + +static int snd_allo_boss_hw_params( + struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) +{ + int ret = 0; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + int channels = params_channels(params); + int width = snd_pcm_format_width(params_format(params)); + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + struct snd_soc_card *card = rtd->card; + + /* Using powers of 2 allows for an integer clock divisor */ + width = width <= 16 ? 16 : 32; + + /* Mute before changing sample rate */ + snd_allo_boss_gpio_mute(card); + + if (snd_soc_allo_boss_master) { + snd_allo_boss_set_sclk(component, params_rate(params)); + + ret = snd_allo_boss_update_rate_den(substream, params); + if (ret) + goto error; + } + + ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_cpu(rtd, 0), channels * width); + + if (ret) + goto error; + + ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_codec(rtd, 0), channels * width); + + if (ret) + goto error; + + /* Unmute after setting parameters or having an error */ +error: + snd_allo_boss_gpio_unmute(card); + + return ret; +} + +static int snd_allo_boss_startup( + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + struct snd_soc_card *card = rtd->card; + + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x08); + snd_allo_boss_gpio_mute(card); + + if (snd_soc_allo_boss_master) { + struct pcm512x_priv *priv = snd_soc_component_get_drvdata(component); + /* + * Default sclk to CLK_48EN_RATE, otherwise codec + * pcm512x_dai_startup_master method could call + * snd_pcm_hw_constraint_ratnums using CLK_44EN/64 + * which will mask 384k sample rate. + */ + if (!IS_ERR(priv->sclk)) + clk_set_rate(priv->sclk, CLK_48EN_RATE); + } + + return 0; +} + +static void snd_allo_boss_shutdown( + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x00); +} + +static int snd_allo_boss_prepare( + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + + snd_allo_boss_gpio_unmute(card); + return 0; +} +/* machine stream operations */ +static struct snd_soc_ops snd_allo_boss_ops = { + .hw_params = snd_allo_boss_hw_params, + .startup = snd_allo_boss_startup, + .shutdown = snd_allo_boss_shutdown, + .prepare = snd_allo_boss_prepare, +}; + +SND_SOC_DAILINK_DEFS(allo_boss, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), + DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004d", "pcm512x-hifi")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0"))); + +static struct snd_soc_dai_link snd_allo_boss_dai[] = { +{ + .name = "Boss DAC", + .stream_name = "Boss DAC HiFi", + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBC_CFC, + .ops = &snd_allo_boss_ops, + .init = snd_allo_boss_init, + SND_SOC_DAILINK_REG(allo_boss), +}, +}; + +/* audio machine driver */ +static struct snd_soc_card snd_allo_boss = { + .name = "BossDAC", + .owner = THIS_MODULE, + .dai_link = snd_allo_boss_dai, + .num_links = ARRAY_SIZE(snd_allo_boss_dai), +}; + +static int snd_allo_boss_probe(struct platform_device *pdev) +{ + int ret = 0; + + snd_allo_boss.dev = &pdev->dev; + + if (pdev->dev.of_node) { + struct device_node *i2s_node; + struct snd_soc_dai_link *dai; + + dai = &snd_allo_boss_dai[0]; + i2s_node = of_parse_phandle(pdev->dev.of_node, + "i2s-controller", 0); + + if (i2s_node) { + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + } + + digital_gain_0db_limit = !of_property_read_bool( + pdev->dev.of_node, "allo,24db_digital_gain"); + slave = of_property_read_bool(pdev->dev.of_node, + "allo,slave"); + + mute_gpio = devm_gpiod_get_optional(&pdev->dev, "mute", + GPIOD_OUT_LOW); + if (IS_ERR(mute_gpio)) { + ret = PTR_ERR(mute_gpio); + dev_err(&pdev->dev, + "failed to get mute gpio: %d\n", ret); + return ret; + } + + if (mute_gpio) + snd_allo_boss.set_bias_level = + snd_allo_boss_set_bias_level; + + ret = snd_soc_register_card(&snd_allo_boss); + if (ret) { + dev_err(&pdev->dev, + "snd_soc_register_card() failed: %d\n", ret); + return ret; + } + + if (mute_gpio) + snd_allo_boss_gpio_mute(&snd_allo_boss); + + return 0; + } + + return -EINVAL; +} + +static void snd_allo_boss_remove(struct platform_device *pdev) +{ + snd_allo_boss_gpio_mute(&snd_allo_boss); + snd_soc_unregister_card(&snd_allo_boss); +} + +static const struct of_device_id snd_allo_boss_of_match[] = { + { .compatible = "allo,boss-dac", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, snd_allo_boss_of_match); + +static struct platform_driver snd_allo_boss_driver = { + .driver = { + .name = "snd-allo-boss-dac", + .owner = THIS_MODULE, + .of_match_table = snd_allo_boss_of_match, + }, + .probe = snd_allo_boss_probe, + .remove = snd_allo_boss_remove, +}; + +module_platform_driver(snd_allo_boss_driver); + +MODULE_AUTHOR("Baswaraj K "); +MODULE_DESCRIPTION("ALSA ASoC Machine Driver for Allo Boss DAC"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/allo-boss2-dac.c b/sound/soc/bcm/allo-boss2-dac.c new file mode 100644 index 000000000000..7d0232678c9c --- /dev/null +++ b/sound/soc/bcm/allo-boss2-dac.c @@ -0,0 +1,1130 @@ +/* + * Driver for the ALLO KATANA CODEC + * + * Author: Jaikumar + * Copyright 2018 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../codecs/cs43130.h" + +#include +#include +#define DEBUG + +#define CS43130_DSD_EN_MASK 0x10 +#define CS43130_PDN_DONE_INT_MASK 0x00 + +static struct gpio_desc *snd_allo_clk44gpio; +static struct gpio_desc *snd_allo_clk48gpio; + +struct cs43130_priv { + struct snd_soc_component *component; + struct regmap *regmap; + struct regulator_bulk_data supplies[CS43130_NUM_SUPPLIES]; + struct gpio_desc *reset_gpio; + unsigned int dev_id; /* codec device ID */ + int xtal_ibias; + /* shared by both DAIs */ + struct mutex clk_mutex; + int clk_req; + bool pll_bypass; + struct completion xtal_rdy; + struct completion pll_rdy; + unsigned int mclk; + unsigned int mclk_int; + int mclk_int_src; + + /* DAI specific */ + struct cs43130_dai dais[CS43130_DAI_ID_MAX]; + + /* HP load specific */ + bool dc_meas; + bool ac_meas; + bool hpload_done; + struct completion hpload_evt; + unsigned int hpload_stat; + u16 hpload_dc[2]; + u16 dc_threshold[CS43130_DC_THRESHOLD]; + u16 ac_freq[CS43130_AC_FREQ]; + u16 hpload_ac[CS43130_AC_FREQ][2]; + struct workqueue_struct *wq; + struct work_struct work; + struct snd_soc_jack jack; +}; + +static const struct reg_default cs43130_reg_defaults[] = { + {CS43130_SYS_CLK_CTL_1, 0x06}, + {CS43130_SP_SRATE, 0x01}, + {CS43130_SP_BITSIZE, 0x05}, + {CS43130_PAD_INT_CFG, 0x03}, + {CS43130_PWDN_CTL, 0xFE}, + {CS43130_CRYSTAL_SET, 0x04}, + {CS43130_PLL_SET_1, 0x00}, + {CS43130_PLL_SET_2, 0x00}, + {CS43130_PLL_SET_3, 0x00}, + {CS43130_PLL_SET_4, 0x00}, + {CS43130_PLL_SET_5, 0x40}, + {CS43130_PLL_SET_6, 0x10}, + {CS43130_PLL_SET_7, 0x80}, + {CS43130_PLL_SET_8, 0x03}, + {CS43130_PLL_SET_9, 0x02}, + {CS43130_PLL_SET_10, 0x02}, + {CS43130_CLKOUT_CTL, 0x00}, + {CS43130_ASP_NUM_1, 0x01}, + {CS43130_ASP_NUM_2, 0x00}, + {CS43130_ASP_DEN_1, 0x08}, + {CS43130_ASP_DEN_2, 0x00}, + {CS43130_ASP_LRCK_HI_TIME_1, 0x1F}, + {CS43130_ASP_LRCK_HI_TIME_2, 0x00}, + {CS43130_ASP_LRCK_PERIOD_1, 0x3F}, + {CS43130_ASP_LRCK_PERIOD_2, 0x00}, + {CS43130_ASP_CLOCK_CONF, 0x0C}, + {CS43130_ASP_FRAME_CONF, 0x0A}, + {CS43130_XSP_NUM_1, 0x01}, + {CS43130_XSP_NUM_2, 0x00}, + {CS43130_XSP_DEN_1, 0x02}, + {CS43130_XSP_DEN_2, 0x00}, + {CS43130_XSP_LRCK_HI_TIME_1, 0x1F}, + {CS43130_XSP_LRCK_HI_TIME_2, 0x00}, + {CS43130_XSP_LRCK_PERIOD_1, 0x3F}, + {CS43130_XSP_LRCK_PERIOD_2, 0x00}, + {CS43130_XSP_CLOCK_CONF, 0x0C}, + {CS43130_XSP_FRAME_CONF, 0x0A}, + {CS43130_ASP_CH_1_LOC, 0x00}, + {CS43130_ASP_CH_2_LOC, 0x00}, + {CS43130_ASP_CH_1_SZ_EN, 0x06}, + {CS43130_ASP_CH_2_SZ_EN, 0x0E}, + {CS43130_XSP_CH_1_LOC, 0x00}, + {CS43130_XSP_CH_2_LOC, 0x00}, + {CS43130_XSP_CH_1_SZ_EN, 0x06}, + {CS43130_XSP_CH_2_SZ_EN, 0x0E}, + {CS43130_DSD_VOL_B, 0x78}, + {CS43130_DSD_VOL_A, 0x78}, + {CS43130_DSD_PATH_CTL_1, 0xA8}, + {CS43130_DSD_INT_CFG, 0x00}, + {CS43130_DSD_PATH_CTL_2, 0x02}, + {CS43130_DSD_PCM_MIX_CTL, 0x00}, + {CS43130_DSD_PATH_CTL_3, 0x40}, + {CS43130_HP_OUT_CTL_1, 0x30}, + {CS43130_PCM_FILT_OPT, 0x02}, + {CS43130_PCM_VOL_B, 0x78}, + {CS43130_PCM_VOL_A, 0x78}, + {CS43130_PCM_PATH_CTL_1, 0xA8}, + {CS43130_PCM_PATH_CTL_2, 0x00}, + {CS43130_CLASS_H_CTL, 0x1E}, + {CS43130_HP_DETECT, 0x04}, + {CS43130_HP_LOAD_1, 0x00}, + {CS43130_HP_MEAS_LOAD_1, 0x00}, + {CS43130_HP_MEAS_LOAD_2, 0x00}, + {CS43130_INT_MASK_1, 0xFF}, + {CS43130_INT_MASK_2, 0xFF}, + {CS43130_INT_MASK_3, 0xFF}, + {CS43130_INT_MASK_4, 0xFF}, + {CS43130_INT_MASK_5, 0xFF}, +}; +static bool cs43130_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS43130_INT_STATUS_1 ... CS43130_INT_STATUS_5: + case CS43130_HP_DC_STAT_1 ... CS43130_HP_DC_STAT_2: + case CS43130_HP_AC_STAT_1 ... CS43130_HP_AC_STAT_2: + return true; + default: + return false; + } +} + +static const char * const pcm_spd_texts[] = { + "Fast", + "Slow", +}; + +static SOC_ENUM_SINGLE_DECL(pcm_spd_enum, CS43130_PCM_FILT_OPT, 7, + pcm_spd_texts); + +static const SNDRV_CTL_TLVD_DECLARE_DB_MINMAX(master_tlv, -12750, 0); + +static const struct snd_kcontrol_new cs43130_controls[] = { + SOC_DOUBLE_R_TLV("Master Playback Volume", CS43130_PCM_VOL_B, + CS43130_PCM_VOL_A, 0, 255, 1, master_tlv), + SOC_DOUBLE("Master Playback Switch", CS43130_PCM_PATH_CTL_1, + 0, 1, 1, 1), + SOC_DOUBLE_R_TLV("Digital Playback Volume", CS43130_DSD_VOL_B, + CS43130_DSD_VOL_A, 0, 255, 1, master_tlv), + SOC_DOUBLE("Digital Playback Switch", CS43130_DSD_PATH_CTL_1, + 0, 1, 1, 1), + SOC_SINGLE("HV_Enable", CS43130_HP_OUT_CTL_1, 0, 1, 0), + SOC_ENUM("PCM Filter Speed", pcm_spd_enum), + SOC_SINGLE("PCM Phase Compensation", CS43130_PCM_FILT_OPT, 6, 1, 0), + SOC_SINGLE("PCM Nonoversample Emulate", CS43130_PCM_FILT_OPT, 5, 1, 0), + SOC_SINGLE("PCM High-pass Filter", CS43130_PCM_FILT_OPT, 1, 1, 0), + SOC_SINGLE("PCM De-emphasis Filter", CS43130_PCM_FILT_OPT, 0, 1, 0), +}; + +static bool cs43130_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS43130_DEVID_AB ... CS43130_SYS_CLK_CTL_1: + case CS43130_SP_SRATE ... CS43130_PAD_INT_CFG: + case CS43130_PWDN_CTL: + case CS43130_CRYSTAL_SET: + case CS43130_PLL_SET_1 ... CS43130_PLL_SET_5: + case CS43130_PLL_SET_6: + case CS43130_PLL_SET_7: + case CS43130_PLL_SET_8: + case CS43130_PLL_SET_9: + case CS43130_PLL_SET_10: + case CS43130_CLKOUT_CTL: + case CS43130_ASP_NUM_1 ... CS43130_ASP_FRAME_CONF: + case CS43130_XSP_NUM_1 ... CS43130_XSP_FRAME_CONF: + case CS43130_ASP_CH_1_LOC: + case CS43130_ASP_CH_2_LOC: + case CS43130_ASP_CH_1_SZ_EN: + case CS43130_ASP_CH_2_SZ_EN: + case CS43130_XSP_CH_1_LOC: + case CS43130_XSP_CH_2_LOC: + case CS43130_XSP_CH_1_SZ_EN: + case CS43130_XSP_CH_2_SZ_EN: + case CS43130_DSD_VOL_B ... CS43130_DSD_PATH_CTL_3: + case CS43130_HP_OUT_CTL_1: + case CS43130_PCM_FILT_OPT ... CS43130_PCM_PATH_CTL_2: + case CS43130_CLASS_H_CTL: + case CS43130_HP_DETECT: + case CS43130_HP_STATUS: + case CS43130_HP_LOAD_1: + case CS43130_HP_MEAS_LOAD_1: + case CS43130_HP_MEAS_LOAD_2: + case CS43130_HP_DC_STAT_1: + case CS43130_HP_DC_STAT_2: + case CS43130_HP_AC_STAT_1: + case CS43130_HP_AC_STAT_2: + case CS43130_HP_LOAD_STAT: + case CS43130_INT_STATUS_1 ... CS43130_INT_STATUS_5: + case CS43130_INT_MASK_1 ... CS43130_INT_MASK_5: + return true; + default: + return false; + } +} +static bool cs43130_precious_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS43130_INT_STATUS_1 ... CS43130_INT_STATUS_5: + return true; + default: + return false; + } +} +static int cs43130_pcm_pdn(struct snd_soc_component *component) +{ + struct cs43130_priv *cs43130 = + snd_soc_component_get_drvdata(component); + int ret; + unsigned int reg, pdn_int; + + regmap_write(cs43130->regmap, CS43130_DSD_PATH_CTL_2, 0x02); + regmap_update_bits(cs43130->regmap, CS43130_INT_MASK_1, + CS43130_PDN_DONE_INT_MASK, 0); + regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL, + CS43130_PDN_HP_MASK, 1 << CS43130_PDN_HP_SHIFT); + usleep_range(10, 50); + ret = regmap_read(cs43130->regmap, CS43130_INT_STATUS_1, ®); + pdn_int = reg & 0xFE; + regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL, + CS43130_PDN_ASP_MASK, 1 << CS43130_PDN_ASP_SHIFT); + return 0; + +} +static int cs43130_pwr_up_asp_dac(struct snd_soc_component *component) +{ + struct cs43130_priv *cs43130 = + snd_soc_component_get_drvdata(component); + + regmap_update_bits(cs43130->regmap, CS43130_PAD_INT_CFG, + CS43130_ASP_3ST_MASK, 0); + regmap_write(cs43130->regmap, CS43130_DXD1, 0x99); + regmap_write(cs43130->regmap, CS43130_DXD13, 0x20); + regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL, + CS43130_PDN_ASP_MASK, 0); + regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL, + CS43130_PDN_HP_MASK, 0); + usleep_range(10000, 12000); + regmap_write(cs43130->regmap, CS43130_DXD1, 0x00); + regmap_write(cs43130->regmap, CS43130_DXD13, 0x00); + return 0; +} +static int cs43130_change_clksrc(struct snd_soc_component *component, + enum cs43130_mclk_src_sel src) +{ + int ret; + struct cs43130_priv *cs43130 = + snd_soc_component_get_drvdata(component); + int mclk_int_decoded; + + if (src == cs43130->mclk_int_src) { + /* clk source has not changed */ + return 0; + } + switch (cs43130->mclk_int) { + case CS43130_MCLK_22M: + mclk_int_decoded = CS43130_MCLK_22P5; + break; + case CS43130_MCLK_24M: + mclk_int_decoded = CS43130_MCLK_24P5; + break; + default: + dev_err(component->dev, "Invalid MCLK INT freq: %u\n", + cs43130->mclk_int); + return -EINVAL; + } + + switch (src) { + case CS43130_MCLK_SRC_EXT: + cs43130->pll_bypass = true; + cs43130->mclk_int_src = CS43130_MCLK_SRC_EXT; + if (cs43130->xtal_ibias == CS43130_XTAL_UNUSED) { + regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL, + CS43130_PDN_XTAL_MASK, + 1 << CS43130_PDN_XTAL_SHIFT); + } else { + reinit_completion(&cs43130->xtal_rdy); + regmap_update_bits(cs43130->regmap, CS43130_INT_MASK_1, + CS43130_XTAL_RDY_INT_MASK, 0); + regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL, + CS43130_PDN_XTAL_MASK, 0); + ret = wait_for_completion_timeout(&cs43130->xtal_rdy, + msecs_to_jiffies(100)); + regmap_update_bits(cs43130->regmap, CS43130_INT_MASK_1, + CS43130_XTAL_RDY_INT_MASK, + 1 << CS43130_XTAL_RDY_INT_SHIFT); + if (ret == 0) { + dev_err(component->dev, "Timeout waiting for XTAL_READY interrupt\n"); + return -ETIMEDOUT; + } + } + regmap_update_bits(cs43130->regmap, CS43130_SYS_CLK_CTL_1, + CS43130_MCLK_SRC_SEL_MASK, + src << CS43130_MCLK_SRC_SEL_SHIFT); + regmap_update_bits(cs43130->regmap, CS43130_SYS_CLK_CTL_1, + CS43130_MCLK_INT_MASK, + mclk_int_decoded << CS43130_MCLK_INT_SHIFT); + usleep_range(150, 200); + regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL, + CS43130_PDN_PLL_MASK, + 1 << CS43130_PDN_PLL_SHIFT); + break; + case CS43130_MCLK_SRC_RCO: + cs43130->mclk_int_src = CS43130_MCLK_SRC_RCO; + + regmap_update_bits(cs43130->regmap, CS43130_SYS_CLK_CTL_1, + CS43130_MCLK_SRC_SEL_MASK, + src << CS43130_MCLK_SRC_SEL_SHIFT); + regmap_update_bits(cs43130->regmap, CS43130_SYS_CLK_CTL_1, + CS43130_MCLK_INT_MASK, + CS43130_MCLK_22P5 << CS43130_MCLK_INT_SHIFT); + usleep_range(150, 200); + regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL, + CS43130_PDN_XTAL_MASK, + 1 << CS43130_PDN_XTAL_SHIFT); + regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL, + CS43130_PDN_PLL_MASK, + 1 << CS43130_PDN_PLL_SHIFT); + break; + default: + dev_err(component->dev, "Invalid MCLK source value\n"); + return -EINVAL; + } + + return 0; +} +static const struct cs43130_bitwidth_map cs43130_bitwidth_table[] = { + {8, CS43130_SP_BIT_SIZE_8, CS43130_CH_BIT_SIZE_8}, + {16, CS43130_SP_BIT_SIZE_16, CS43130_CH_BIT_SIZE_16}, + {24, CS43130_SP_BIT_SIZE_24, CS43130_CH_BIT_SIZE_24}, + {32, CS43130_SP_BIT_SIZE_32, CS43130_CH_BIT_SIZE_32}, +}; + +static const struct cs43130_bitwidth_map *cs43130_get_bitwidth_table( + unsigned int bitwidth) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cs43130_bitwidth_table); i++) { + if (cs43130_bitwidth_table[i].bitwidth == bitwidth) + return &cs43130_bitwidth_table[i]; + } + + return NULL; +} +static int cs43130_set_bitwidth(int dai_id, unsigned int bitwidth_dai, + struct regmap *regmap) +{ + const struct cs43130_bitwidth_map *bw_map; + + bw_map = cs43130_get_bitwidth_table(bitwidth_dai); + if (!bw_map) + return -EINVAL; + + switch (dai_id) { + case CS43130_ASP_PCM_DAI: + case CS43130_ASP_DOP_DAI: + regmap_update_bits(regmap, CS43130_ASP_CH_1_SZ_EN, + CS43130_CH_BITSIZE_MASK, bw_map->ch_bit); + regmap_update_bits(regmap, CS43130_ASP_CH_2_SZ_EN, + CS43130_CH_BITSIZE_MASK, bw_map->ch_bit); + regmap_update_bits(regmap, CS43130_SP_BITSIZE, + CS43130_ASP_BITSIZE_MASK, bw_map->sp_bit); + break; + case CS43130_XSP_DOP_DAI: + regmap_update_bits(regmap, CS43130_XSP_CH_1_SZ_EN, + CS43130_CH_BITSIZE_MASK, bw_map->ch_bit); + regmap_update_bits(regmap, CS43130_XSP_CH_2_SZ_EN, + CS43130_CH_BITSIZE_MASK, bw_map->ch_bit); + regmap_update_bits(regmap, CS43130_SP_BITSIZE, + CS43130_XSP_BITSIZE_MASK, bw_map->sp_bit << + CS43130_XSP_BITSIZE_SHIFT); + break; + default: + return -EINVAL; + } + + return 0; +} +static const struct cs43130_rate_map cs43130_rate_table[] = { + {32000, CS43130_ASP_SPRATE_32K}, + {44100, CS43130_ASP_SPRATE_44_1K}, + {48000, CS43130_ASP_SPRATE_48K}, + {88200, CS43130_ASP_SPRATE_88_2K}, + {96000, CS43130_ASP_SPRATE_96K}, + {176400, CS43130_ASP_SPRATE_176_4K}, + {192000, CS43130_ASP_SPRATE_192K}, + {352800, CS43130_ASP_SPRATE_352_8K}, + {384000, CS43130_ASP_SPRATE_384K}, +}; + +static const struct cs43130_rate_map *cs43130_get_rate_table(int fs) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cs43130_rate_table); i++) { + if (cs43130_rate_table[i].fs == fs) + return &cs43130_rate_table[i]; + } + + return NULL; +} + +static const struct cs43130_clk_gen *cs43130_get_clk_gen(int mclk_int, int fs, + const struct cs43130_clk_gen *clk_gen_table, int len_clk_gen_table) +{ + int i; + + for (i = 0; i < len_clk_gen_table; i++) { + if (clk_gen_table[i].mclk_int == mclk_int && + clk_gen_table[i].fs == fs) + return &clk_gen_table[i]; + } + return NULL; +} + +static int cs43130_set_sp_fmt(int dai_id, unsigned int bitwidth_sclk, + struct snd_pcm_hw_params *params, + struct cs43130_priv *cs43130) +{ + u16 frm_size; + u16 hi_size; + u8 frm_delay; + u8 frm_phase; + u8 frm_data; + u8 sclk_edge; + u8 lrck_edge; + u8 clk_data; + u8 loc_ch1; + u8 loc_ch2; + u8 dai_mode_val; + const struct cs43130_clk_gen *clk_gen; + + switch (cs43130->dais[dai_id].dai_format) { + case SND_SOC_DAIFMT_I2S: + hi_size = bitwidth_sclk; + frm_delay = 2; + frm_phase = 0; + break; + case SND_SOC_DAIFMT_LEFT_J: + hi_size = bitwidth_sclk; + frm_delay = 2; + frm_phase = 1; + break; + case SND_SOC_DAIFMT_DSP_A: + hi_size = 1; + frm_delay = 2; + frm_phase = 1; + break; + case SND_SOC_DAIFMT_DSP_B: + hi_size = 1; + frm_delay = 0; + frm_phase = 1; + break; + default: + return -EINVAL; + } + switch (cs43130->dais[dai_id].dai_mode) { + case SND_SOC_DAIFMT_CBC_CFC: + dai_mode_val = 0; + break; + case SND_SOC_DAIFMT_CBP_CFP: + dai_mode_val = 1; + break; + default: + return -EINVAL; + } + + frm_size = bitwidth_sclk * params_channels(params); + sclk_edge = 1; + lrck_edge = 0; + loc_ch1 = 0; + loc_ch2 = bitwidth_sclk * (params_channels(params) - 1); + + frm_data = frm_delay & CS43130_SP_FSD_MASK; + frm_data |= (frm_phase << CS43130_SP_STP_SHIFT) & CS43130_SP_STP_MASK; + + clk_data = lrck_edge & CS43130_SP_LCPOL_IN_MASK; + clk_data |= (lrck_edge << CS43130_SP_LCPOL_OUT_SHIFT) & + CS43130_SP_LCPOL_OUT_MASK; + clk_data |= (sclk_edge << CS43130_SP_SCPOL_IN_SHIFT) & + CS43130_SP_SCPOL_IN_MASK; + clk_data |= (sclk_edge << CS43130_SP_SCPOL_OUT_SHIFT) & + CS43130_SP_SCPOL_OUT_MASK; + clk_data |= (dai_mode_val << CS43130_SP_MODE_SHIFT) & + CS43130_SP_MODE_MASK; + switch (dai_id) { + case CS43130_ASP_PCM_DAI: + case CS43130_ASP_DOP_DAI: + regmap_update_bits(cs43130->regmap, CS43130_ASP_LRCK_PERIOD_1, + CS43130_SP_LCPR_DATA_MASK, (frm_size - 1) >> + CS43130_SP_LCPR_LSB_DATA_SHIFT); + regmap_update_bits(cs43130->regmap, CS43130_ASP_LRCK_PERIOD_2, + CS43130_SP_LCPR_DATA_MASK, (frm_size - 1) >> + CS43130_SP_LCPR_MSB_DATA_SHIFT); + regmap_update_bits(cs43130->regmap, CS43130_ASP_LRCK_HI_TIME_1, + CS43130_SP_LCHI_DATA_MASK, (hi_size - 1) >> + CS43130_SP_LCHI_LSB_DATA_SHIFT); + regmap_update_bits(cs43130->regmap, CS43130_ASP_LRCK_HI_TIME_2, + CS43130_SP_LCHI_DATA_MASK, (hi_size - 1) >> + CS43130_SP_LCHI_MSB_DATA_SHIFT); + regmap_write(cs43130->regmap, CS43130_ASP_FRAME_CONF, frm_data); + regmap_write(cs43130->regmap, CS43130_ASP_CH_1_LOC, loc_ch1); + regmap_write(cs43130->regmap, CS43130_ASP_CH_2_LOC, loc_ch2); + regmap_update_bits(cs43130->regmap, CS43130_ASP_CH_1_SZ_EN, + CS43130_CH_EN_MASK, 1 << CS43130_CH_EN_SHIFT); + regmap_update_bits(cs43130->regmap, CS43130_ASP_CH_2_SZ_EN, + CS43130_CH_EN_MASK, 1 << CS43130_CH_EN_SHIFT); + regmap_write(cs43130->regmap, CS43130_ASP_CLOCK_CONF, clk_data); + break; + case CS43130_XSP_DOP_DAI: + regmap_update_bits(cs43130->regmap, CS43130_XSP_LRCK_PERIOD_1, + CS43130_SP_LCPR_DATA_MASK, (frm_size - 1) >> + CS43130_SP_LCPR_LSB_DATA_SHIFT); + regmap_update_bits(cs43130->regmap, CS43130_XSP_LRCK_PERIOD_2, + CS43130_SP_LCPR_DATA_MASK, (frm_size - 1) >> + CS43130_SP_LCPR_MSB_DATA_SHIFT); + regmap_update_bits(cs43130->regmap, CS43130_XSP_LRCK_HI_TIME_1, + CS43130_SP_LCHI_DATA_MASK, (hi_size - 1) >> + CS43130_SP_LCHI_LSB_DATA_SHIFT); + regmap_update_bits(cs43130->regmap, CS43130_XSP_LRCK_HI_TIME_2, + CS43130_SP_LCHI_DATA_MASK, (hi_size - 1) >> + CS43130_SP_LCHI_MSB_DATA_SHIFT); + regmap_write(cs43130->regmap, CS43130_XSP_FRAME_CONF, frm_data); + regmap_write(cs43130->regmap, CS43130_XSP_CH_1_LOC, loc_ch1); + regmap_write(cs43130->regmap, CS43130_XSP_CH_2_LOC, loc_ch2); + regmap_update_bits(cs43130->regmap, CS43130_XSP_CH_1_SZ_EN, + CS43130_CH_EN_MASK, 1 << CS43130_CH_EN_SHIFT); + regmap_update_bits(cs43130->regmap, CS43130_XSP_CH_2_SZ_EN, + CS43130_CH_EN_MASK, 1 << CS43130_CH_EN_SHIFT); + regmap_write(cs43130->regmap, CS43130_XSP_CLOCK_CONF, clk_data); + break; + default: + return -EINVAL; + } + switch (frm_size) { + case 16: + clk_gen = cs43130_get_clk_gen(cs43130->mclk_int, + params_rate(params), + cs43130_16_clk_gen, + ARRAY_SIZE(cs43130_16_clk_gen)); + break; + case 32: + clk_gen = cs43130_get_clk_gen(cs43130->mclk_int, + params_rate(params), + cs43130_32_clk_gen, + ARRAY_SIZE(cs43130_32_clk_gen)); + break; + case 48: + clk_gen = cs43130_get_clk_gen(cs43130->mclk_int, + params_rate(params), + cs43130_48_clk_gen, + ARRAY_SIZE(cs43130_48_clk_gen)); + break; + case 64: + clk_gen = cs43130_get_clk_gen(cs43130->mclk_int, + params_rate(params), + cs43130_64_clk_gen, + ARRAY_SIZE(cs43130_64_clk_gen)); + break; + default: + return -EINVAL; + } + if (!clk_gen) + return -EINVAL; + switch (dai_id) { + case CS43130_ASP_PCM_DAI: + case CS43130_ASP_DOP_DAI: + regmap_write(cs43130->regmap, CS43130_ASP_DEN_1, + (clk_gen->v.denominator & CS43130_SP_M_LSB_DATA_MASK) >> + CS43130_SP_M_LSB_DATA_SHIFT); + regmap_write(cs43130->regmap, CS43130_ASP_DEN_2, + (clk_gen->v.denominator & CS43130_SP_M_MSB_DATA_MASK) >> + CS43130_SP_M_MSB_DATA_SHIFT); + regmap_write(cs43130->regmap, CS43130_ASP_NUM_1, + (clk_gen->v.numerator & CS43130_SP_N_LSB_DATA_MASK) >> + CS43130_SP_N_LSB_DATA_SHIFT); + regmap_write(cs43130->regmap, CS43130_ASP_NUM_2, + (clk_gen->v.numerator & CS43130_SP_N_MSB_DATA_MASK) >> + CS43130_SP_N_MSB_DATA_SHIFT); + break; + case CS43130_XSP_DOP_DAI: + regmap_write(cs43130->regmap, CS43130_XSP_DEN_1, + (clk_gen->v.denominator & CS43130_SP_M_LSB_DATA_MASK) >> + CS43130_SP_M_LSB_DATA_SHIFT); + regmap_write(cs43130->regmap, CS43130_XSP_DEN_2, + (clk_gen->v.denominator & CS43130_SP_M_MSB_DATA_MASK) >> + CS43130_SP_M_MSB_DATA_SHIFT); + regmap_write(cs43130->regmap, CS43130_XSP_NUM_1, + (clk_gen->v.numerator & CS43130_SP_N_LSB_DATA_MASK) >> + CS43130_SP_N_LSB_DATA_SHIFT); + regmap_write(cs43130->regmap, CS43130_XSP_NUM_2, + (clk_gen->v.numerator & CS43130_SP_N_MSB_DATA_MASK) >> + CS43130_SP_N_MSB_DATA_SHIFT); + break; + default: + return -EINVAL; + } + return 0; +} + +static int cs43130_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct cs43130_priv *cs43130 = + snd_soc_component_get_drvdata(component); + const struct cs43130_rate_map *rate_map; + unsigned int sclk = cs43130->dais[dai->id].sclk; + unsigned int bitwidth_sclk; + unsigned int bitwidth_dai = (unsigned int)(params_width(params)); + unsigned int dop_rate = (unsigned int)(params_rate(params)); + unsigned int required_clk, ret; + u8 dsd_speed; + + cs43130->pll_bypass = true; + cs43130_pcm_pdn(component); + mutex_lock(&cs43130->clk_mutex); + if (!cs43130->clk_req) { + /* no DAI is currently using clk */ + if (!(CS43130_MCLK_22M % params_rate(params))) { + required_clk = CS43130_MCLK_22M; + cs43130->mclk_int = CS43130_MCLK_22M; + gpiod_set_value_cansleep(snd_allo_clk44gpio, 1); + gpiod_set_value_cansleep(snd_allo_clk48gpio, 0); + usleep_range(13500, 14000); + } else { + required_clk = CS43130_MCLK_24M; + cs43130->mclk_int = CS43130_MCLK_24M; + gpiod_set_value_cansleep(snd_allo_clk48gpio, 1); + gpiod_set_value_cansleep(snd_allo_clk44gpio, 0); + usleep_range(13500, 14000); + } + if (cs43130->pll_bypass) + cs43130_change_clksrc(component, CS43130_MCLK_SRC_EXT); + else + cs43130_change_clksrc(component, CS43130_MCLK_SRC_PLL); + } + + cs43130->clk_req++; + mutex_unlock(&cs43130->clk_mutex); + + switch (dai->id) { + case CS43130_ASP_DOP_DAI: + case CS43130_XSP_DOP_DAI: + /* DoP bitwidth is always 24-bit */ + bitwidth_dai = 24; + sclk = params_rate(params) * bitwidth_dai * + params_channels(params); + + switch (params_rate(params)) { + case 176400: + dsd_speed = 0; + break; + case 352800: + dsd_speed = 1; + break; + default: + dev_err(component->dev, "Rate(%u) not supported\n", + params_rate(params)); + return -EINVAL; + } + + regmap_update_bits(cs43130->regmap, CS43130_DSD_PATH_CTL_2, + CS43130_DSD_SPEED_MASK, + dsd_speed << CS43130_DSD_SPEED_SHIFT); + break; + case CS43130_ASP_PCM_DAI: + rate_map = cs43130_get_rate_table(params_rate(params)); + if (!rate_map) + return -EINVAL; + + regmap_write(cs43130->regmap, CS43130_SP_SRATE, rate_map->val); + if ((dop_rate == 176400) && (bitwidth_dai == 24)) { + dsd_speed = 0; + regmap_update_bits(cs43130->regmap, + CS43130_DSD_PATH_CTL_2, + CS43130_DSD_SPEED_MASK, + dsd_speed << CS43130_DSD_SPEED_SHIFT); + regmap_update_bits(cs43130->regmap, + CS43130_DSD_PATH_CTL_2, + CS43130_DSD_SRC_MASK, + CS43130_DSD_SRC_ASP << + CS43130_DSD_SRC_SHIFT); + regmap_update_bits(cs43130->regmap, + CS43130_DSD_PATH_CTL_2, + CS43130_DSD_EN_MASK, 0x01 << + CS43130_DSD_EN_SHIFT); + } + break; + default: + dev_err(component->dev, "Invalid DAI (%d)\n", dai->id); + return -EINVAL; + } + + switch (dai->id) { + case CS43130_ASP_DOP_DAI: + regmap_update_bits(cs43130->regmap, CS43130_DSD_PATH_CTL_2, + CS43130_DSD_SRC_MASK, CS43130_DSD_SRC_ASP << + CS43130_DSD_SRC_SHIFT); + regmap_update_bits(cs43130->regmap, CS43130_DSD_PATH_CTL_2, + CS43130_DSD_EN_MASK, 0x01 << + CS43130_DSD_EN_SHIFT); + break; + case CS43130_XSP_DOP_DAI: + regmap_update_bits(cs43130->regmap, CS43130_DSD_PATH_CTL_2, + CS43130_DSD_SRC_MASK, CS43130_DSD_SRC_XSP << + CS43130_DSD_SRC_SHIFT); + break; + } + if (!sclk && cs43130->dais[dai->id].dai_mode == + SND_SOC_DAIFMT_CBP_CFP) { + /* Calculate SCLK in master mode if unassigned */ + sclk = params_rate(params) * bitwidth_dai * + params_channels(params); + } + if (!sclk) { + /* at this point, SCLK must be set */ + dev_err(component->dev, "SCLK freq is not set\n"); + return -EINVAL; + } + + bitwidth_sclk = (sclk / params_rate(params)) / params_channels(params); + if (bitwidth_sclk < bitwidth_dai) { + dev_err(component->dev, "Format not supported: SCLK freq is too low\n"); + return -EINVAL; + } + + dev_dbg(component->dev, + "sclk = %u, fs = %d, bitwidth_dai = %u\n", + sclk, params_rate(params), bitwidth_dai); + + dev_dbg(component->dev, + "bitwidth_sclk = %u, num_ch = %u\n", + bitwidth_sclk, params_channels(params)); + + cs43130_set_bitwidth(dai->id, bitwidth_dai, cs43130->regmap); + cs43130_set_sp_fmt(dai->id, bitwidth_sclk, params, cs43130); + ret = cs43130_pwr_up_asp_dac(component); + return 0; +} + +static int cs43130_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct cs43130_priv *cs43130 = + snd_soc_component_get_drvdata(component); + + mutex_lock(&cs43130->clk_mutex); + cs43130->clk_req--; + if (!cs43130->clk_req) { + /* no DAI is currently using clk */ + cs43130_change_clksrc(component, CS43130_MCLK_SRC_RCO); + cs43130_pcm_pdn(component); + } + mutex_unlock(&cs43130->clk_mutex); + + return 0; +} + +static const unsigned int cs43130_asp_src_rates[] = { + 32000, 44100, 48000, 88200, 96000, 176400, 192000 +}; + +static const struct snd_pcm_hw_constraint_list cs43130_asp_constraints = { + .count = ARRAY_SIZE(cs43130_asp_src_rates), + .list = cs43130_asp_src_rates, +}; + +static int cs43130_pcm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &cs43130_asp_constraints); +} + +static int cs43130_pcm_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct cs43130_priv *cs43130 = + snd_soc_component_get_drvdata(component); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBC_CFC: + cs43130->dais[codec_dai->id].dai_mode = SND_SOC_DAIFMT_CBC_CFC; + break; + case SND_SOC_DAIFMT_CBP_CFP: + cs43130->dais[codec_dai->id].dai_mode = SND_SOC_DAIFMT_CBP_CFP; + break; + default: + dev_err(component->dev, "unsupported mode\n"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + cs43130->dais[codec_dai->id].dai_format = SND_SOC_DAIFMT_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + cs43130->dais[codec_dai->id].dai_format = SND_SOC_DAIFMT_LEFT_J; + break; + default: + dev_err(component->dev, + "unsupported audio format\n"); + return -EINVAL; + } + + dev_dbg(component->dev, "dai_id = %d, dai_mode = %u, dai_format = %u\n", + codec_dai->id, + cs43130->dais[codec_dai->id].dai_mode, + cs43130->dais[codec_dai->id].dai_format); + + return 0; +} + +static int cs43130_set_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct cs43130_priv *cs43130 = + snd_soc_component_get_drvdata(component); + + cs43130->dais[codec_dai->id].sclk = freq; + dev_dbg(component->dev, "dai_id = %d, sclk = %u\n", codec_dai->id, + cs43130->dais[codec_dai->id].sclk); + + return 0; +} + +static int cs43130_component_set_sysclk(struct snd_soc_component *component, + int clk_id, int source, + unsigned int freq, int dir) +{ + struct cs43130_priv *cs43130 = + snd_soc_component_get_drvdata(component); + + dev_dbg(component->dev, "clk_id = %d, source = %d, freq = %d, dir = %d\n", + clk_id, source, freq, dir); + + switch (freq) { + case CS43130_MCLK_22M: + case CS43130_MCLK_24M: + cs43130->mclk = freq; + break; + default: + dev_err(component->dev, "Invalid MCLK INT freq: %u\n", freq); + return -EINVAL; + } + + if (source == CS43130_MCLK_SRC_EXT) { + cs43130->pll_bypass = true; + } else { + dev_err(component->dev, "Invalid MCLK source\n"); + return -EINVAL; + } + + return 0; +} +static u16 const cs43130_ac_freq[CS43130_AC_FREQ] = { + 24, + 43, + 93, + 200, + 431, + 928, + 2000, + 4309, + 9283, + 20000, +}; +static const struct snd_soc_dai_ops cs43130_dai_ops = { + .startup = cs43130_pcm_startup, + .hw_params = cs43130_hw_params, + .hw_free = cs43130_hw_free, + .set_sysclk = cs43130_set_sysclk, + .set_fmt = cs43130_pcm_set_fmt, +}; + +static struct snd_soc_dai_driver cs43130_codec_dai = { + .name = "allo-cs43130", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 44100, + .rate_max = 192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE + + }, + .ops = &cs43130_dai_ops, +}; + +static struct snd_soc_component_driver cs43130_component_driver = { + .idle_bias_on = true, + .controls = cs43130_controls, + .num_controls = ARRAY_SIZE(cs43130_controls), + .set_sysclk = cs43130_component_set_sysclk, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, +}; + +static const struct regmap_config cs43130_regmap = { + .reg_bits = 24, + .pad_bits = 8, + .val_bits = 8, + + .max_register = CS43130_LASTREG, + .reg_defaults = cs43130_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(cs43130_reg_defaults), + .readable_reg = cs43130_readable_register, + .precious_reg = cs43130_precious_register, + .volatile_reg = cs43130_volatile_register, + .cache_type = REGCACHE_RBTREE, + /* needed for regcache_sync */ + .use_single_read = true, + .use_single_write = true, +}; + +static u16 const cs43130_dc_threshold[CS43130_DC_THRESHOLD] = { + 50, + 120, +}; + +static int cs43130_handle_device_data(struct i2c_client *i2c_client, + struct cs43130_priv *cs43130) +{ + struct device_node *np = i2c_client->dev.of_node; + unsigned int val; + int i; + + if (of_property_read_u32(np, "cirrus,xtal-ibias", &val) < 0) { + /* Crystal is unused. System clock is used for external MCLK */ + cs43130->xtal_ibias = CS43130_XTAL_UNUSED; + return 0; + } + + switch (val) { + case 1: + cs43130->xtal_ibias = CS43130_XTAL_IBIAS_7_5UA; + break; + case 2: + cs43130->xtal_ibias = CS43130_XTAL_IBIAS_12_5UA; + break; + case 3: + cs43130->xtal_ibias = CS43130_XTAL_IBIAS_15UA; + break; + default: + dev_err(&i2c_client->dev, + "Invalid cirrus,xtal-ibias value: %d\n", val); + return -EINVAL; + } + + cs43130->dc_meas = of_property_read_bool(np, "cirrus,dc-measure"); + cs43130->ac_meas = of_property_read_bool(np, "cirrus,ac-measure"); + + if (of_property_read_u16_array(np, "cirrus,ac-freq", cs43130->ac_freq, + CS43130_AC_FREQ) < 0) { + for (i = 0; i < CS43130_AC_FREQ; i++) + cs43130->ac_freq[i] = cs43130_ac_freq[i]; + } + + if (of_property_read_u16_array(np, "cirrus,dc-threshold", + cs43130->dc_threshold, + CS43130_DC_THRESHOLD) < 0) { + for (i = 0; i < CS43130_DC_THRESHOLD; i++) + cs43130->dc_threshold[i] = cs43130_dc_threshold[i]; + } + + return 0; +} + + +static int allo_cs43130_component_probe(struct i2c_client *i2c) +{ + struct regmap *regmap; + struct regmap_config config = cs43130_regmap; + struct device *dev = &i2c->dev; + struct cs43130_priv *cs43130; + unsigned int devid = 0; + unsigned int reg; + int ret; + + regmap = devm_regmap_init_i2c(i2c, &config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + cs43130 = devm_kzalloc(dev, sizeof(struct cs43130_priv), + GFP_KERNEL); + if (!cs43130) + return -ENOMEM; + + dev_set_drvdata(dev, cs43130); + cs43130->regmap = regmap; + + if (i2c->dev.of_node) { + ret = cs43130_handle_device_data(i2c, cs43130); + if (ret != 0) + return ret; + } + usleep_range(2000, 2050); + + ret = regmap_read(cs43130->regmap, CS43130_DEVID_AB, ®); + devid = (reg & 0xFF) << 12; + ret = regmap_read(cs43130->regmap, CS43130_DEVID_CD, ®); + devid |= (reg & 0xFF) << 4; + ret = regmap_read(cs43130->regmap, CS43130_DEVID_E, ®); + devid |= (reg & 0xF0) >> 4; + if (devid != CS43198_CHIP_ID) { + dev_err(dev, "Failed to read Chip or wrong Chip id: %d\n", ret); + return ret; + } + + cs43130->mclk_int_src = CS43130_MCLK_SRC_RCO; + msleep(20); + + ret = snd_soc_register_component(dev, &cs43130_component_driver, + &cs43130_codec_dai, 1); + if (ret != 0) { + dev_err(dev, "failed to register codec: %d\n", ret); + return ret; + } + regmap_update_bits(cs43130->regmap, CS43130_PAD_INT_CFG, + CS43130_ASP_3ST_MASK, 0); + regmap_update_bits(cs43130->regmap, CS43130_PAD_INT_CFG, + CS43130_XSP_3ST_MASK, 1); + regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL, + CS43130_PDN_HP_MASK, 1 << CS43130_PDN_HP_SHIFT); + msleep(20); + regmap_write(cs43130->regmap, CS43130_CLASS_H_CTL, 0x06); + snd_allo_clk44gpio = devm_gpiod_get(dev, "clock44", GPIOD_OUT_HIGH); + if (IS_ERR(snd_allo_clk44gpio)) + dev_err(dev, "devm_gpiod_get() failed\n"); + + snd_allo_clk48gpio = devm_gpiod_get(dev, "clock48", GPIOD_OUT_LOW); + if (IS_ERR(snd_allo_clk48gpio)) + dev_err(dev, "devm_gpiod_get() failed\n"); + + return 0; +} + +static void allo_cs43130_component_remove(struct i2c_client *i2c) +{ + snd_soc_unregister_component(&i2c->dev); +} + +static const struct i2c_device_id allo_cs43130_component_id[] = { + { "allo-cs43198", }, + { } +}; +MODULE_DEVICE_TABLE(i2c, allo_cs43130_component_id); + +static const struct of_device_id allo_cs43130_codec_of_match[] = { + { .compatible = "allo,allo-cs43198", }, + { } +}; +MODULE_DEVICE_TABLE(of, allo_cs43130_codec_of_match); + +static struct i2c_driver allo_cs43130_component_driver = { + .probe = allo_cs43130_component_probe, + .remove = allo_cs43130_component_remove, + .id_table = allo_cs43130_component_id, + .driver = { + .name = "allo-cs43198", + .of_match_table = allo_cs43130_codec_of_match, + }, +}; + +module_i2c_driver(allo_cs43130_component_driver); + +MODULE_DESCRIPTION("ASoC Allo Boss2 Codec Driver"); +MODULE_AUTHOR("Sudeepkumar "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/allo-katana-codec.c b/sound/soc/bcm/allo-katana-codec.c new file mode 100644 index 000000000000..4578e8d6501b --- /dev/null +++ b/sound/soc/bcm/allo-katana-codec.c @@ -0,0 +1,386 @@ +/* + * Driver for the ALLO KATANA CODEC + * + * Author: Jaikumar + * Copyright 2018 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define KATANA_CODEC_CHIP_ID 0x30 +#define KATANA_CODEC_VIRT_BASE 0x100 +#define KATANA_CODEC_PAGE 0 + +#define KATANA_CODEC_CHIP_ID_REG (KATANA_CODEC_VIRT_BASE + 0) +#define KATANA_CODEC_RESET (KATANA_CODEC_VIRT_BASE + 1) +#define KATANA_CODEC_VOLUME_1 (KATANA_CODEC_VIRT_BASE + 2) +#define KATANA_CODEC_VOLUME_2 (KATANA_CODEC_VIRT_BASE + 3) +#define KATANA_CODEC_MUTE (KATANA_CODEC_VIRT_BASE + 4) +#define KATANA_CODEC_DSP_PROGRAM (KATANA_CODEC_VIRT_BASE + 5) +#define KATANA_CODEC_DEEMPHASIS (KATANA_CODEC_VIRT_BASE + 6) +#define KATANA_CODEC_DOP (KATANA_CODEC_VIRT_BASE + 7) +#define KATANA_CODEC_FORMAT (KATANA_CODEC_VIRT_BASE + 8) +#define KATANA_CODEC_COMMAND (KATANA_CODEC_VIRT_BASE + 9) +#define KATANA_CODEC_MUTE_STREAM (KATANA_CODEC_VIRT_BASE + 10) + +#define KATANA_CODEC_MAX_REGISTER (KATANA_CODEC_VIRT_BASE + 10) + +#define KATANA_CODEC_FMT 0xff +#define KATANA_CODEC_CHAN_MONO 0x00 +#define KATANA_CODEC_CHAN_STEREO 0x80 +#define KATANA_CODEC_ALEN_16 0x10 +#define KATANA_CODEC_ALEN_24 0x20 +#define KATANA_CODEC_ALEN_32 0x30 +#define KATANA_CODEC_RATE_11025 0x01 +#define KATANA_CODEC_RATE_22050 0x02 +#define KATANA_CODEC_RATE_32000 0x03 +#define KATANA_CODEC_RATE_44100 0x04 +#define KATANA_CODEC_RATE_48000 0x05 +#define KATANA_CODEC_RATE_88200 0x06 +#define KATANA_CODEC_RATE_96000 0x07 +#define KATANA_CODEC_RATE_176400 0x08 +#define KATANA_CODEC_RATE_192000 0x09 +#define KATANA_CODEC_RATE_352800 0x0a +#define KATANA_CODEC_RATE_384000 0x0b + + +struct katana_codec_priv { + struct regmap *regmap; + int fmt; +}; + +static const struct reg_default katana_codec_reg_defaults[] = { + { KATANA_CODEC_RESET, 0x00 }, + { KATANA_CODEC_VOLUME_1, 0xF0 }, + { KATANA_CODEC_VOLUME_2, 0xF0 }, + { KATANA_CODEC_MUTE, 0x00 }, + { KATANA_CODEC_DSP_PROGRAM, 0x04 }, + { KATANA_CODEC_DEEMPHASIS, 0x00 }, + { KATANA_CODEC_DOP, 0x00 }, + { KATANA_CODEC_FORMAT, 0xb4 }, +}; + +static const char * const katana_codec_dsp_program_texts[] = { + "Linear Phase Fast Roll-off Filter", + "Linear Phase Slow Roll-off Filter", + "Minimum Phase Fast Roll-off Filter", + "Minimum Phase Slow Roll-off Filter", + "Apodizing Fast Roll-off Filter", + "Corrected Minimum Phase Fast Roll-off Filter", + "Brick Wall Filter", +}; + +static const unsigned int katana_codec_dsp_program_values[] = { + 0, + 1, + 2, + 3, + 4, + 6, + 7, +}; + +static SOC_VALUE_ENUM_SINGLE_DECL(katana_codec_dsp_program, + KATANA_CODEC_DSP_PROGRAM, 0, 0x07, + katana_codec_dsp_program_texts, + katana_codec_dsp_program_values); + +static const char * const katana_codec_deemphasis_texts[] = { + "Bypass", + "32kHz", + "44.1kHz", + "48kHz", +}; + +static const unsigned int katana_codec_deemphasis_values[] = { + 0, + 1, + 2, + 3, +}; + +static SOC_VALUE_ENUM_SINGLE_DECL(katana_codec_deemphasis, + KATANA_CODEC_DEEMPHASIS, 0, 0x03, + katana_codec_deemphasis_texts, + katana_codec_deemphasis_values); + +static const SNDRV_CTL_TLVD_DECLARE_DB_MINMAX(master_tlv, -12750, 0); + +static const struct snd_kcontrol_new katana_codec_controls[] = { + SOC_DOUBLE_R_TLV("Master Playback Volume", KATANA_CODEC_VOLUME_1, + KATANA_CODEC_VOLUME_2, 0, 255, 1, master_tlv), + SOC_DOUBLE("Master Playback Switch", KATANA_CODEC_MUTE, 0, 0, 1, 1), + SOC_ENUM("DSP Program Route", katana_codec_dsp_program), + SOC_ENUM("Deemphasis Route", katana_codec_deemphasis), + SOC_SINGLE("DoP Playback Switch", KATANA_CODEC_DOP, 0, 1, 1) +}; + +static bool katana_codec_readable_register(struct device *dev, + unsigned int reg) +{ + switch (reg) { + case KATANA_CODEC_CHIP_ID_REG: + return true; + default: + return reg < 0xff; + } +} + +static int katana_codec_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct katana_codec_priv *katana_codec = + snd_soc_component_get_drvdata(component); + int fmt = 0; + int ret; + + dev_dbg(component->card->dev, "hw_params %u Hz, %u channels, %u bits\n", + params_rate(params), + params_channels(params), + params_width(params)); + + switch (katana_codec->fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBP_CFP: // master + if (params_channels(params) == 2) + fmt = KATANA_CODEC_CHAN_STEREO; + else + fmt = KATANA_CODEC_CHAN_MONO; + + switch (params_width(params)) { + case 16: + fmt |= KATANA_CODEC_ALEN_16; + break; + case 24: + fmt |= KATANA_CODEC_ALEN_24; + break; + case 32: + fmt |= KATANA_CODEC_ALEN_32; + break; + default: + dev_err(component->card->dev, "Bad frame size: %d\n", + params_width(params)); + return -EINVAL; + } + + switch (params_rate(params)) { + case 44100: + fmt |= KATANA_CODEC_RATE_44100; + break; + case 48000: + fmt |= KATANA_CODEC_RATE_48000; + break; + case 88200: + fmt |= KATANA_CODEC_RATE_88200; + break; + case 96000: + fmt |= KATANA_CODEC_RATE_96000; + break; + case 176400: + fmt |= KATANA_CODEC_RATE_176400; + break; + case 192000: + fmt |= KATANA_CODEC_RATE_192000; + break; + case 352800: + fmt |= KATANA_CODEC_RATE_352800; + break; + case 384000: + fmt |= KATANA_CODEC_RATE_384000; + break; + default: + dev_err(component->card->dev, "Bad sample rate: %d\n", + params_rate(params)); + return -EINVAL; + } + + ret = regmap_write(katana_codec->regmap, KATANA_CODEC_FORMAT, + fmt); + if (ret != 0) { + dev_err(component->card->dev, "Failed to set format: %d\n", ret); + return ret; + } + break; + + case SND_SOC_DAIFMT_CBC_CFC: + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int katana_codec_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + struct katana_codec_priv *katana_codec = + snd_soc_component_get_drvdata(component); + + katana_codec->fmt = fmt; + + return 0; +} + +static int katana_codec_dai_mute_stream(struct snd_soc_dai *dai, int mute, + int stream) +{ + struct snd_soc_component *component = dai->component; + struct katana_codec_priv *katana_codec = + snd_soc_component_get_drvdata(component); + int ret = 0; + + ret = regmap_write(katana_codec->regmap, KATANA_CODEC_MUTE_STREAM, + mute); + if (ret != 0) { + dev_err(component->card->dev, "Failed to set mute: %d\n", ret); + return ret; + } + return ret; +} + +static const struct snd_soc_dai_ops katana_codec_dai_ops = { + .mute_stream = katana_codec_dai_mute_stream, + .hw_params = katana_codec_hw_params, + .set_fmt = katana_codec_set_fmt, +}; + +static struct snd_soc_dai_driver katana_codec_dai = { + .name = "allo-katana-codec", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 44100, + .rate_max = 384000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE + }, + .ops = &katana_codec_dai_ops, +}; + +static struct snd_soc_component_driver katana_codec_component_driver = { + .idle_bias_on = true, + + .controls = katana_codec_controls, + .num_controls = ARRAY_SIZE(katana_codec_controls), +}; + +static const struct regmap_range_cfg katana_codec_range = { + .name = "Pages", .range_min = KATANA_CODEC_VIRT_BASE, + .range_max = KATANA_CODEC_MAX_REGISTER, + .selector_reg = KATANA_CODEC_PAGE, + .selector_mask = 0xff, + .window_start = 0, .window_len = 0x100, +}; + +const struct regmap_config katana_codec_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .ranges = &katana_codec_range, + .num_ranges = 1, + + .max_register = KATANA_CODEC_MAX_REGISTER, + .readable_reg = katana_codec_readable_register, + .reg_defaults = katana_codec_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(katana_codec_reg_defaults), + .cache_type = REGCACHE_RBTREE, +}; + +static int allo_katana_component_probe(struct i2c_client *i2c) +{ + struct regmap *regmap; + struct regmap_config config = katana_codec_regmap; + struct device *dev = &i2c->dev; + struct katana_codec_priv *katana_codec; + unsigned int chip_id = 0; + int ret; + + regmap = devm_regmap_init_i2c(i2c, &config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + katana_codec = devm_kzalloc(dev, sizeof(struct katana_codec_priv), + GFP_KERNEL); + if (!katana_codec) + return -ENOMEM; + + dev_set_drvdata(dev, katana_codec); + katana_codec->regmap = regmap; + + ret = regmap_read(regmap, KATANA_CODEC_CHIP_ID_REG, &chip_id); + if ((ret != 0) || (chip_id != KATANA_CODEC_CHIP_ID)) { + dev_err(dev, "Failed to read Chip or wrong Chip id: %d\n", ret); + return ret; + } + regmap_update_bits(regmap, KATANA_CODEC_RESET, 0x01, 0x01); + msleep(10); + + ret = snd_soc_register_component(dev, &katana_codec_component_driver, + &katana_codec_dai, 1); + if (ret != 0) { + dev_err(dev, "failed to register codec: %d\n", ret); + return ret; + } + + return 0; +} + +static void allo_katana_component_remove(struct i2c_client *i2c) +{ + snd_soc_unregister_component(&i2c->dev); +} + +static const struct i2c_device_id allo_katana_component_id[] = { + { "allo-katana-codec", }, + { } +}; +MODULE_DEVICE_TABLE(i2c, allo_katana_component_id); + +static const struct of_device_id allo_katana_codec_of_match[] = { + { .compatible = "allo,allo-katana-codec", }, + { } +}; +MODULE_DEVICE_TABLE(of, allo_katana_codec_of_match); + +static struct i2c_driver allo_katana_component_driver = { + .probe = allo_katana_component_probe, + .remove = allo_katana_component_remove, + .id_table = allo_katana_component_id, + .driver = { + .name = "allo-katana-codec", + .of_match_table = allo_katana_codec_of_match, + }, +}; + +module_i2c_driver(allo_katana_component_driver); + +MODULE_DESCRIPTION("ASoC Allo Katana Codec Driver"); +MODULE_AUTHOR("Jaikumar "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/allo-piano-dac-plus.c b/sound/soc/bcm/allo-piano-dac-plus.c new file mode 100644 index 000000000000..187eff7a9aa1 --- /dev/null +++ b/sound/soc/bcm/allo-piano-dac-plus.c @@ -0,0 +1,1026 @@ +/* + * ALSA ASoC Machine Driver for Allo Piano DAC Plus Subwoofer + * + * Author: Baswaraj K + * Copyright 2020 + * based on code by David Knell + * based on code by Florian Meier + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../codecs/pcm512x.h" + +#define P_DAC_LEFT_MUTE 0x10 +#define P_DAC_RIGHT_MUTE 0x01 +#define P_DAC_MUTE 0x11 +#define P_DAC_UNMUTE 0x00 +#define P_MUTE 1 +#define P_UNMUTE 0 + +struct dsp_code { + char i2c_addr; + char offset; + char val; +}; + +struct glb_pool { + struct mutex lock; + unsigned int dual_mode; + unsigned int set_lowpass; + unsigned int set_mode; + unsigned int set_rate; + unsigned int dsp_page_number; +}; + +static bool digital_gain_0db_limit = true; +bool glb_mclk; + +static struct gpio_desc *mute_gpio[2]; + +static const char * const allo_piano_mode_texts[] = { + "None", + "2.0", + "2.1", + "2.2", +}; + +static SOC_ENUM_SINGLE_DECL(allo_piano_mode_enum, + 0, 0, allo_piano_mode_texts); + +static const char * const allo_piano_dual_mode_texts[] = { + "None", + "Dual-Mono", + "Dual-Stereo", +}; + +static SOC_ENUM_SINGLE_DECL(allo_piano_dual_mode_enum, + 0, 0, allo_piano_dual_mode_texts); + +static const char * const allo_piano_dsp_low_pass_texts[] = { + "60", + "70", + "80", + "90", + "100", + "110", + "120", + "130", + "140", + "150", + "160", + "170", + "180", + "190", + "200", +}; + +static SOC_ENUM_SINGLE_DECL(allo_piano_enum, + 0, 0, allo_piano_dsp_low_pass_texts); + +static int __snd_allo_piano_dsp_program(struct snd_soc_pcm_runtime *rtd, + unsigned int mode, unsigned int rate, unsigned int lowpass) +{ + const struct firmware *fw; + struct snd_soc_card *card = rtd->card; + struct glb_pool *glb_ptr = card->drvdata; + char firmware_name[60]; + int ret = 0, dac = 0; + + if (rate <= 46000) + rate = 44100; + else if (rate <= 68000) + rate = 48000; + else if (rate <= 92000) + rate = 88200; + else if (rate <= 136000) + rate = 96000; + else if (rate <= 184000) + rate = 176400; + else + rate = 192000; + + if (lowpass > 14) + glb_ptr->set_lowpass = lowpass = 0; + + if (mode > 3) + glb_ptr->set_mode = mode = 0; + + if (mode > 0) + glb_ptr->dual_mode = 0; + + /* same configuration loaded */ + if ((rate == glb_ptr->set_rate) && (lowpass == glb_ptr->set_lowpass) + && (mode == glb_ptr->set_mode)) + return 0; + + switch (mode) { + case 0: /* None */ + return 1; + + case 1: /* 2.0 */ + snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component, + PCM512x_MUTE, P_DAC_UNMUTE); + snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component, + PCM512x_MUTE, P_DAC_MUTE); + glb_ptr->set_rate = rate; + glb_ptr->set_mode = mode; + glb_ptr->set_lowpass = lowpass; + return 1; + + default: + snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component, + PCM512x_MUTE, P_DAC_UNMUTE); + snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component, + PCM512x_MUTE, P_DAC_UNMUTE); + } + + for (dac = 0; dac < rtd->dai_link->num_codecs; dac++) { + struct dsp_code *dsp_code_read; + int i = 1; + + if (dac == 0) { /* high */ + snprintf(firmware_name, sizeof(firmware_name), + "allo/piano/2.2/allo-piano-dsp-%d-%d-%d.bin", + rate, ((lowpass * 10) + 60), dac); + } else { /* low */ + snprintf(firmware_name, sizeof(firmware_name), + "allo/piano/2.%d/allo-piano-dsp-%d-%d-%d.bin", + (mode - 1), rate, ((lowpass * 10) + 60), dac); + } + + dev_info(rtd->card->dev, "Dsp Firmware File Name: %s\n", + firmware_name); + + ret = request_firmware(&fw, firmware_name, rtd->card->dev); + if (ret < 0) { + dev_err(rtd->card->dev, + "Error: Allo Piano Firmware %s missing. %d\n", + firmware_name, ret); + goto err; + } + + while (i < (fw->size - 1)) { + dsp_code_read = (struct dsp_code *)&fw->data[i]; + + if (dsp_code_read->offset == 0) { + glb_ptr->dsp_page_number = dsp_code_read->val; + ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, dac)->component, + PCM512x_PAGE_BASE(0), + dsp_code_read->val); + + } else if (dsp_code_read->offset != 0) { + ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, dac)->component, + (PCM512x_PAGE_BASE( + glb_ptr->dsp_page_number) + + dsp_code_read->offset), + dsp_code_read->val); + } + if (ret < 0) { + dev_err(rtd->card->dev, + "Failed to write Register: %d\n", ret); + release_firmware(fw); + goto err; + } + i = i + 3; + } + release_firmware(fw); + } + glb_ptr->set_rate = rate; + glb_ptr->set_mode = mode; + glb_ptr->set_lowpass = lowpass; + return 1; + +err: + return ret; +} + +static int snd_allo_piano_dsp_program(struct snd_soc_pcm_runtime *rtd, + unsigned int mode, unsigned int rate, unsigned int lowpass) +{ + struct snd_soc_card *card = rtd->card; + struct glb_pool *glb_ptr = card->drvdata; + int ret = 0; + + mutex_lock(&glb_ptr->lock); + + ret = __snd_allo_piano_dsp_program(rtd, mode, rate, lowpass); + + mutex_unlock(&glb_ptr->lock); + + return ret; +} + +static int snd_allo_piano_dual_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct glb_pool *glb_ptr = card->drvdata; + + ucontrol->value.integer.value[0] = glb_ptr->dual_mode; + + return 0; +} + +static int snd_allo_piano_dual_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct glb_pool *glb_ptr = card->drvdata; + struct snd_soc_pcm_runtime *rtd; + struct snd_card *snd_card_ptr = card->snd_card; + struct snd_kcontrol *kctl; + struct soc_mixer_control *mc; + unsigned int left_val = 0, right_val = 0; + + rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]); + + if (ucontrol->value.integer.value[0] > 0) { + glb_ptr->dual_mode = ucontrol->value.integer.value[0]; + glb_ptr->set_mode = 0; + } else { + if (glb_ptr->set_mode <= 0) { + glb_ptr->dual_mode = 1; + glb_ptr->set_mode = 0; + } else { + glb_ptr->dual_mode = 0; + return 0; + } + } + + if (glb_ptr->dual_mode == 1) { // Dual Mono + snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component, + PCM512x_MUTE, P_DAC_RIGHT_MUTE); + snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component, + PCM512x_MUTE, P_DAC_LEFT_MUTE); + snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component, + PCM512x_DIGITAL_VOLUME_3, 0xff); + snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component, + PCM512x_DIGITAL_VOLUME_2, 0xff); + + list_for_each_entry(kctl, &snd_card_ptr->controls, list) { + if (!strncmp(kctl->id.name, "Main Digital Playback Volume", + sizeof(kctl->id.name))) { + mc = (struct soc_mixer_control *) + kctl->private_value; + mc->rreg = mc->reg; + break; + } + if (!strncmp(kctl->id.name, "Sub Digital Playback Volume", + sizeof(kctl->id.name))) { + mc = (struct soc_mixer_control *) + kctl->private_value; + mc->rreg = mc->reg; + break; + } + } + } else { + left_val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 0)->component, + PCM512x_DIGITAL_VOLUME_2); + right_val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 1)->component, + PCM512x_DIGITAL_VOLUME_3); + + list_for_each_entry(kctl, &snd_card_ptr->controls, list) { + if (!strncmp(kctl->id.name, "Main Digital Playback Volume", + sizeof(kctl->id.name))) { + mc = (struct soc_mixer_control *) + kctl->private_value; + mc->rreg = PCM512x_DIGITAL_VOLUME_3; + break; + } + if (!strncmp(kctl->id.name, "Sub Digital Playback Volume", + sizeof(kctl->id.name))) { + mc = (struct soc_mixer_control *) + kctl->private_value; + mc->rreg = PCM512x_DIGITAL_VOLUME_2; + break; + } + } + + snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component, + PCM512x_DIGITAL_VOLUME_3, left_val); + snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component, + PCM512x_DIGITAL_VOLUME_2, right_val); + snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component, + PCM512x_MUTE, P_DAC_UNMUTE); + snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component, + PCM512x_MUTE, P_DAC_UNMUTE); + } + + return 0; +} + +static int snd_allo_piano_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct glb_pool *glb_ptr = card->drvdata; + + ucontrol->value.integer.value[0] = glb_ptr->set_mode; + return 0; +} + +static int snd_allo_piano_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct snd_soc_pcm_runtime *rtd; + struct glb_pool *glb_ptr = card->drvdata; + struct snd_card *snd_card_ptr = card->snd_card; + struct snd_kcontrol *kctl; + struct soc_mixer_control *mc; + unsigned int left_val = 0, right_val = 0; + + rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]); + + if ((glb_ptr->dual_mode == 1) && + (ucontrol->value.integer.value[0] > 0)) { + left_val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 0)->component, + PCM512x_DIGITAL_VOLUME_2); + right_val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 1)->component, + PCM512x_DIGITAL_VOLUME_2); + + list_for_each_entry(kctl, &snd_card_ptr->controls, list) { + if (!strncmp(kctl->id.name, "Main Digital Playback Volume", + sizeof(kctl->id.name))) { + mc = (struct soc_mixer_control *) + kctl->private_value; + mc->rreg = PCM512x_DIGITAL_VOLUME_3; + break; + } + if (!strncmp(kctl->id.name, "Sub Digital Playback Volume", + sizeof(kctl->id.name))) { + mc = (struct soc_mixer_control *) + kctl->private_value; + mc->rreg = PCM512x_DIGITAL_VOLUME_2; + break; + } + } + snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component, + PCM512x_DIGITAL_VOLUME_3, left_val); + snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component, + PCM512x_DIGITAL_VOLUME_3, right_val); + } + + return(snd_allo_piano_dsp_program(rtd, + ucontrol->value.integer.value[0], + glb_ptr->set_rate, glb_ptr->set_lowpass)); +} + +static int snd_allo_piano_lowpass_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct glb_pool *glb_ptr = card->drvdata; + + ucontrol->value.integer.value[0] = glb_ptr->set_lowpass; + return 0; +} + +static int snd_allo_piano_lowpass_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct snd_soc_pcm_runtime *rtd; + struct glb_pool *glb_ptr = card->drvdata; + + rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]); + return(snd_allo_piano_dsp_program(rtd, + glb_ptr->set_mode, glb_ptr->set_rate, + ucontrol->value.integer.value[0])); +} + +static int pcm512x_get_reg_sub(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct glb_pool *glb_ptr = card->drvdata; + struct snd_soc_pcm_runtime *rtd; + unsigned int left_val = 0; + unsigned int right_val = 0; + rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]); + + right_val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 1)->component, + PCM512x_DIGITAL_VOLUME_3); + if (glb_ptr->dual_mode != 1) { + left_val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 1)->component, + PCM512x_DIGITAL_VOLUME_2); + + } else { + left_val = right_val; + } + + ucontrol->value.integer.value[0] = + (~(left_val >> mc->shift)) & mc->max; + ucontrol->value.integer.value[1] = + (~(right_val >> mc->shift)) & mc->max; + + return 0; +} + +static int pcm512x_set_reg_sub(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct glb_pool *glb_ptr = card->drvdata; + struct snd_soc_pcm_runtime *rtd; + unsigned int left_val = (ucontrol->value.integer.value[0] & mc->max); + unsigned int right_val = (ucontrol->value.integer.value[1] & mc->max); + int ret = 0; + + rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]); + + // When in Dual Mono, Sub vol control should not set anything. + if (glb_ptr->dual_mode != 1) { //Not in Dual Mono mode + + ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component, + PCM512x_DIGITAL_VOLUME_2, (~left_val)); + if (ret < 0) + return ret; + + ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component, + PCM512x_DIGITAL_VOLUME_3, (~right_val)); + if (ret < 0) + return ret; + + } + + return 1; +} + +static int pcm512x_get_reg_sub_switch(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct snd_soc_pcm_runtime *rtd; + int val = 0; + + rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]); + val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 1)->component, PCM512x_MUTE); + + ucontrol->value.integer.value[0] = + (val & P_DAC_LEFT_MUTE) ? P_UNMUTE : P_MUTE; + ucontrol->value.integer.value[1] = + (val & P_DAC_RIGHT_MUTE) ? P_UNMUTE : P_MUTE; + + return val; +} + +static int pcm512x_set_reg_sub_switch(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct snd_soc_pcm_runtime *rtd; + struct glb_pool *glb_ptr = card->drvdata; + unsigned int left_val = (ucontrol->value.integer.value[0]); + unsigned int right_val = (ucontrol->value.integer.value[1]); + int ret = 0; + + rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]); + if (glb_ptr->set_mode != 1) { + ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component, PCM512x_MUTE, + ~((left_val & 0x01)<<4 | (right_val & 0x01))); + if (ret < 0) + return ret; + } + return 1; + +} + +static int pcm512x_get_reg_master(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct glb_pool *glb_ptr = card->drvdata; + struct snd_soc_pcm_runtime *rtd; + unsigned int left_val = 0, right_val = 0; + + rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]); + + left_val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 0)->component, + PCM512x_DIGITAL_VOLUME_2); + + if (glb_ptr->dual_mode == 1) { // in Dual Mono mode + right_val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 1)->component, + PCM512x_DIGITAL_VOLUME_3); + } else { + right_val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 0)->component, + PCM512x_DIGITAL_VOLUME_3); + } + + ucontrol->value.integer.value[0] = + (~(left_val >> mc->shift)) & mc->max; + ucontrol->value.integer.value[1] = + (~(right_val >> mc->shift)) & mc->max; + + return 0; +} + +static int pcm512x_set_reg_master(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct glb_pool *glb_ptr = card->drvdata; + struct snd_soc_pcm_runtime *rtd; + unsigned int left_val = (ucontrol->value.integer.value[0] & mc->max); + unsigned int right_val = (ucontrol->value.integer.value[1] & mc->max); + int ret = 0; + + rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]); + + if (glb_ptr->dual_mode == 1) { //in Dual Mono Mode + + ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component, + PCM512x_DIGITAL_VOLUME_2, (~left_val)); + if (ret < 0) + return ret; + + ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component, + PCM512x_DIGITAL_VOLUME_3, (~right_val)); + if (ret < 0) + return ret; + + } else { + + ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component, + PCM512x_DIGITAL_VOLUME_2, (~left_val)); + if (ret < 0) + return ret; + + ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component, + PCM512x_DIGITAL_VOLUME_3, (~right_val)); + if (ret < 0) + return ret; + + } + return 1; +} + +static int pcm512x_get_reg_master_switch(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct glb_pool *glb_ptr = card->drvdata; + struct snd_soc_pcm_runtime *rtd; + int val = 0; + + rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]); + + val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 0)->component, PCM512x_MUTE); + + ucontrol->value.integer.value[0] = + (val & P_DAC_LEFT_MUTE) ? P_UNMUTE : P_MUTE; + + if (glb_ptr->dual_mode == 1) { + val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 1)->component, PCM512x_MUTE); + } + ucontrol->value.integer.value[1] = + (val & P_DAC_RIGHT_MUTE) ? P_UNMUTE : P_MUTE; + + return val; +} + +static int pcm512x_set_reg_master_switch(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct snd_soc_pcm_runtime *rtd; + struct glb_pool *glb_ptr = card->drvdata; + unsigned int left_val = (ucontrol->value.integer.value[0]); + unsigned int right_val = (ucontrol->value.integer.value[1]); + int ret = 0; + + rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]); + if (glb_ptr->dual_mode == 1) { + ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component, PCM512x_MUTE, + ~((left_val & 0x01)<<4)); + if (ret < 0) + return ret; + ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component, PCM512x_MUTE, + ~((right_val & 0x01))); + if (ret < 0) + return ret; + + } else if (glb_ptr->set_mode == 1) { + ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component, PCM512x_MUTE, + ~((left_val & 0x01)<<4 | (right_val & 0x01))); + if (ret < 0) + return ret; + + } else { + ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component, PCM512x_MUTE, + ~((left_val & 0x01)<<4 | (right_val & 0x01))); + if (ret < 0) + return ret; + + ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component, PCM512x_MUTE, + ~((left_val & 0x01)<<4 | (right_val & 0x01))); + if (ret < 0) + return ret; + } + return 1; +} + +static const DECLARE_TLV_DB_SCALE(digital_tlv_sub, -10350, 50, 1); +static const DECLARE_TLV_DB_SCALE(digital_tlv_master, -10350, 50, 1); + +static const struct snd_kcontrol_new allo_piano_controls[] = { + SOC_ENUM_EXT("Subwoofer mode Route", + allo_piano_mode_enum, + snd_allo_piano_mode_get, + snd_allo_piano_mode_put), + + SOC_ENUM_EXT("Dual Mode Route", + allo_piano_dual_mode_enum, + snd_allo_piano_dual_mode_get, + snd_allo_piano_dual_mode_put), + + SOC_ENUM_EXT("Lowpass Route", allo_piano_enum, + snd_allo_piano_lowpass_get, + snd_allo_piano_lowpass_put), + + SOC_DOUBLE_R_EXT_TLV("Subwoofer Playback Volume", + PCM512x_DIGITAL_VOLUME_2, + PCM512x_DIGITAL_VOLUME_3, 0, 255, 1, + pcm512x_get_reg_sub, + pcm512x_set_reg_sub, + digital_tlv_sub), + + SOC_DOUBLE_EXT("Subwoofer Playback Switch", + PCM512x_MUTE, + PCM512x_RQML_SHIFT, + PCM512x_RQMR_SHIFT, 1, 1, + pcm512x_get_reg_sub_switch, + pcm512x_set_reg_sub_switch), + + SOC_DOUBLE_R_EXT_TLV("Master Playback Volume", + PCM512x_DIGITAL_VOLUME_2, + PCM512x_DIGITAL_VOLUME_3, 0, 255, 1, + pcm512x_get_reg_master, + pcm512x_set_reg_master, + digital_tlv_master), + + SOC_DOUBLE_EXT("Master Playback Switch", + PCM512x_MUTE, + PCM512x_RQML_SHIFT, + PCM512x_RQMR_SHIFT, 1, 1, + pcm512x_get_reg_master_switch, + pcm512x_set_reg_master_switch), +}; + +static const char * const codec_ctl_pfx[] = { "Main", "Sub" }; +static const char * const codec_ctl_name[] = { + "Digital Playback Volume", + "Digital Playback Switch", + "Auto Mute Mono Switch", + "Auto Mute Switch", + "Auto Mute Time Left", + "Auto Mute Time Right", + "Clock Missing Period", + "Max Overclock DAC", + "Max Overclock DSP", + "Max Overclock PLL", + "Volume Ramp Down Emergency Rate", + "Volume Ramp Down Emergency Step", + "Volume Ramp Up Rate", + "Volume Ramp Down Rate", + "Volume Ramp Up Step", + "Volume Ramp Down Step" +}; + +static int snd_allo_piano_dac_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + struct glb_pool *glb_ptr; + struct snd_kcontrol *kctl; + int i, j; + + glb_ptr = kzalloc(sizeof(struct glb_pool), GFP_KERNEL); + if (!glb_ptr) + return -ENOMEM; + + card->drvdata = glb_ptr; + glb_ptr->dual_mode = 2; + glb_ptr->set_mode = 0; + + mutex_init(&glb_ptr->lock); + + // Remove codec controls + for (i = 0; i < ARRAY_SIZE(codec_ctl_pfx); i++) { + for (j = 0; j < ARRAY_SIZE(codec_ctl_name); j++) { + char cname[256]; + + sprintf(cname, "%s %s", codec_ctl_pfx[i], codec_ctl_name[j]); + kctl = snd_soc_card_get_kcontrol(card, cname); + if (kctl) { + kctl->vd[0].access = + SNDRV_CTL_ELEM_ACCESS_READWRITE; + snd_ctl_remove(card->snd_card, kctl); + } + } + } + + return 0; +} + +static void snd_allo_piano_gpio_mute(struct snd_soc_card *card) +{ + if (mute_gpio[0]) + gpiod_set_value_cansleep(mute_gpio[0], P_MUTE); + + if (mute_gpio[1]) + gpiod_set_value_cansleep(mute_gpio[1], P_MUTE); +} + +static void snd_allo_piano_gpio_unmute(struct snd_soc_card *card) +{ + if (mute_gpio[0]) + gpiod_set_value_cansleep(mute_gpio[0], P_UNMUTE); + + if (mute_gpio[1]) + gpiod_set_value_cansleep(mute_gpio[1], P_UNMUTE); +} + +static int snd_allo_piano_set_bias_level(struct snd_soc_card *card, + struct snd_soc_dapm_context *dapm, enum snd_soc_bias_level level) +{ + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_dai *codec_dai; + + rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]); + codec_dai = snd_soc_rtd_to_codec(rtd, 0); + + if (dapm->dev != codec_dai->dev) + return 0; + + switch (level) { + case SND_SOC_BIAS_PREPARE: + if (dapm->bias_level != SND_SOC_BIAS_STANDBY) + break; + /* UNMUTE DAC */ + snd_allo_piano_gpio_unmute(card); + break; + + case SND_SOC_BIAS_STANDBY: + if (dapm->bias_level != SND_SOC_BIAS_PREPARE) + break; + /* MUTE DAC */ + snd_allo_piano_gpio_mute(card); + break; + + default: + break; + } + + return 0; +} + +static int snd_allo_piano_dac_startup( + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + + snd_allo_piano_gpio_mute(card); + + return 0; +} + +static int snd_allo_piano_dac_hw_params( + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + unsigned int rate = params_rate(params); + struct snd_soc_card *card = rtd->card; + struct glb_pool *glb_ptr = card->drvdata; + int ret = 0, val = 0, dac; + + for (dac = 0; (glb_mclk && dac < 2); dac++) { + /* Configure the PLL clock reference for both the Codecs */ + val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, dac)->component, + PCM512x_RATE_DET_4); + + if (val & 0x40) { + snd_soc_component_write(snd_soc_rtd_to_codec(rtd, dac)->component, + PCM512x_PLL_REF, + PCM512x_SREF_BCK); + + dev_info(snd_soc_rtd_to_codec(rtd, dac)->component->dev, + "Setting BCLK as input clock & Enable PLL\n"); + } else { + snd_soc_component_write(snd_soc_rtd_to_codec(rtd, dac)->component, + PCM512x_PLL_EN, + 0x00); + + snd_soc_component_write(snd_soc_rtd_to_codec(rtd, dac)->component, + PCM512x_PLL_REF, + PCM512x_SREF_SCK); + + dev_info(snd_soc_rtd_to_codec(rtd, dac)->component->dev, + "Setting SCLK as input clock & disabled PLL\n"); + } + } + + ret = snd_allo_piano_dsp_program(rtd, glb_ptr->set_mode, rate, + glb_ptr->set_lowpass); + if (ret < 0) + return ret; + + return ret; +} + +static int snd_allo_piano_dac_prepare( + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + + snd_allo_piano_gpio_unmute(card); + + return 0; +} + +/* machine stream operations */ +static struct snd_soc_ops snd_allo_piano_dac_ops = { + .startup = snd_allo_piano_dac_startup, + .hw_params = snd_allo_piano_dac_hw_params, + .prepare = snd_allo_piano_dac_prepare, +}; + +static struct snd_soc_dai_link_component allo_piano_2_1_codecs[] = { + { + .dai_name = "pcm512x-hifi", + }, + { + .dai_name = "pcm512x-hifi", + }, +}; + +SND_SOC_DAILINK_DEFS(allo_piano_dai_plus, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004c", "pcm512x-hifi"), + COMP_CODEC("pcm512x.1-004d", "pcm512x-hifi")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link snd_allo_piano_dac_dai[] = { + { + .name = "PianoDACPlus", + .stream_name = "PianoDACPlus", + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBC_CFC, + .ops = &snd_allo_piano_dac_ops, + .init = snd_allo_piano_dac_init, + SND_SOC_DAILINK_REG(allo_piano_dai_plus), + }, +}; + +/* audio machine driver */ +static struct snd_soc_card snd_allo_piano_dac = { + .name = "PianoDACPlus", + .owner = THIS_MODULE, + .dai_link = snd_allo_piano_dac_dai, + .num_links = ARRAY_SIZE(snd_allo_piano_dac_dai), + .controls = allo_piano_controls, + .num_controls = ARRAY_SIZE(allo_piano_controls), +}; + +static int snd_allo_piano_dac_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &snd_allo_piano_dac; + int ret = 0, i = 0; + + card->dev = &pdev->dev; + platform_set_drvdata(pdev, &snd_allo_piano_dac); + + if (pdev->dev.of_node) { + struct device_node *i2s_node; + struct snd_soc_dai_link *dai; + + dai = &snd_allo_piano_dac_dai[0]; + i2s_node = of_parse_phandle(pdev->dev.of_node, + "i2s-controller", 0); + if (i2s_node) { + for (i = 0; i < card->num_links; i++) { + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + } + } + digital_gain_0db_limit = + !of_property_read_bool(pdev->dev.of_node, + "allo,24db_digital_gain"); + + glb_mclk = of_property_read_bool(pdev->dev.of_node, + "allo,glb_mclk"); + + allo_piano_2_1_codecs[0].of_node = + of_parse_phandle(pdev->dev.of_node, "audio-codec", 0); + allo_piano_2_1_codecs[1].of_node = + of_parse_phandle(pdev->dev.of_node, "audio-codec", 1); + if (!allo_piano_2_1_codecs[0].of_node || !allo_piano_2_1_codecs[1].of_node) + return dev_err_probe(&pdev->dev, -EINVAL, + "Property 'audio-codec' missing or invalid\n"); + + mute_gpio[0] = devm_gpiod_get_optional(&pdev->dev, "mute1", + GPIOD_OUT_LOW); + if (IS_ERR(mute_gpio[0])) + return dev_err_probe(&pdev->dev, PTR_ERR(mute_gpio[0]), + "failed to get mute1 gpio\n"); + + mute_gpio[1] = devm_gpiod_get_optional(&pdev->dev, "mute2", + GPIOD_OUT_LOW); + if (IS_ERR(mute_gpio[1])) + return dev_err_probe(&pdev->dev, PTR_ERR(mute_gpio[1]), + "failed to get mute2 gpio\n"); + + if (mute_gpio[0] && mute_gpio[1]) + snd_allo_piano_dac.set_bias_level = + snd_allo_piano_set_bias_level; + + ret = snd_soc_register_card(&snd_allo_piano_dac); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, "snd_soc_register_card() failed\n"); + + if (digital_gain_0db_limit) { + ret = snd_soc_limit_volume(card, "Master Playback Volume", + 207); + if (ret < 0) + dev_warn(card->dev, "Failed to set master volume limit: %d\n", + ret); + + ret = snd_soc_limit_volume(card, "Subwoofer Playback Volume", + 207); + if (ret < 0) + dev_warn(card->dev, "Failed to set subwoofer volume limit: %d\n", + ret); + } + + if ((mute_gpio[0]) && (mute_gpio[1])) + snd_allo_piano_gpio_mute(&snd_allo_piano_dac); + + return 0; + } + + return -EINVAL; +} + +static void snd_allo_piano_dac_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + + kfree(&card->drvdata); + snd_allo_piano_gpio_mute(&snd_allo_piano_dac); + snd_soc_unregister_card(&snd_allo_piano_dac); +} + +static const struct of_device_id snd_allo_piano_dac_of_match[] = { + { .compatible = "allo,piano-dac-plus", }, + { /* sentinel */ }, +}; + +MODULE_DEVICE_TABLE(of, snd_allo_piano_dac_of_match); + +static struct platform_driver snd_allo_piano_dac_driver = { + .driver = { + .name = "snd-allo-piano-dac-plus", + .owner = THIS_MODULE, + .of_match_table = snd_allo_piano_dac_of_match, + }, + .probe = snd_allo_piano_dac_probe, + .remove = snd_allo_piano_dac_remove, +}; + +module_platform_driver(snd_allo_piano_dac_driver); + +MODULE_AUTHOR("Baswaraj K "); +MODULE_DESCRIPTION("ALSA ASoC Machine Driver for Allo Piano DAC Plus"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/allo-piano-dac.c b/sound/soc/bcm/allo-piano-dac.c new file mode 100644 index 000000000000..bb36de653626 --- /dev/null +++ b/sound/soc/bcm/allo-piano-dac.c @@ -0,0 +1,122 @@ +/* + * ALSA ASoC Machine Driver for Allo Piano DAC + * + * Author: Baswaraj K + * Copyright 2016 + * based on code by Daniel Matuschek + * based on code by Florian Meier + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include + +#include +#include +#include +#include + +static bool digital_gain_0db_limit = true; + +static int snd_allo_piano_dac_init(struct snd_soc_pcm_runtime *rtd) +{ + if (digital_gain_0db_limit) { + int ret; + struct snd_soc_card *card = rtd->card; + + ret = snd_soc_limit_volume(card, "Digital Playback Volume", + 207); + if (ret < 0) + dev_warn(card->dev, "Failed to set volume limit: %d\n", + ret); + } + + return 0; +} + +SND_SOC_DAILINK_DEFS(allo_piano_dai, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), + DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004c", "pcm512x-hifi")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0"))); + +static struct snd_soc_dai_link snd_allo_piano_dac_dai[] = { +{ + .name = "Piano DAC", + .stream_name = "Piano DAC HiFi", + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBC_CFC, + .init = snd_allo_piano_dac_init, + SND_SOC_DAILINK_REG(allo_piano_dai), +}, +}; + +/* audio machine driver */ +static struct snd_soc_card snd_allo_piano_dac = { + .name = "PianoDAC", + .owner = THIS_MODULE, + .dai_link = snd_allo_piano_dac_dai, + .num_links = ARRAY_SIZE(snd_allo_piano_dac_dai), +}; + +static int snd_allo_piano_dac_probe(struct platform_device *pdev) +{ + int ret = 0; + + snd_allo_piano_dac.dev = &pdev->dev; + + if (pdev->dev.of_node) { + struct device_node *i2s_node; + struct snd_soc_dai_link *dai; + + dai = &snd_allo_piano_dac_dai[0]; + i2s_node = of_parse_phandle(pdev->dev.of_node, + "i2s-controller", 0); + + if (i2s_node) { + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + } + + digital_gain_0db_limit = !of_property_read_bool( + pdev->dev.of_node, "allo,24db_digital_gain"); + } + + ret = devm_snd_soc_register_card(&pdev->dev, &snd_allo_piano_dac); + if (ret && ret != -EPROBE_DEFER) + dev_err(&pdev->dev, + "snd_soc_register_card() failed: %d\n", ret); + + return ret; +} + +static const struct of_device_id snd_allo_piano_dac_of_match[] = { + { .compatible = "allo,piano-dac", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, snd_allo_piano_dac_of_match); + +static struct platform_driver snd_allo_piano_dac_driver = { + .driver = { + .name = "snd-allo-piano-dac", + .owner = THIS_MODULE, + .of_match_table = snd_allo_piano_dac_of_match, + }, + .probe = snd_allo_piano_dac_probe, +}; + +module_platform_driver(snd_allo_piano_dac_driver); + +MODULE_AUTHOR("Baswaraj K "); +MODULE_DESCRIPTION("ALSA ASoC Machine Driver for Allo Piano DAC"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/audioinjector-isolated-soundcard.c b/sound/soc/bcm/audioinjector-isolated-soundcard.c new file mode 100644 index 000000000000..cf3c9929cb8d --- /dev/null +++ b/sound/soc/bcm/audioinjector-isolated-soundcard.c @@ -0,0 +1,184 @@ +/* + * ASoC Driver for AudioInjector.net isolated soundcard + * + * Created on: 20-February-2020 + * Author: flatmax@flatmax.org + * based on audioinjector-octo-soundcard.c + * + * Copyright (C) 2020 Flatmax Pty. Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include + +#include +#include +#include +#include + +static struct gpio_desc *mute_gpio; + +static const unsigned int audioinjector_isolated_rates[] = { + 192000, 96000, 48000, 32000, 24000, 16000, 8000 +}; + +static struct snd_pcm_hw_constraint_list audioinjector_isolated_constraints = { + .list = audioinjector_isolated_rates, + .count = ARRAY_SIZE(audioinjector_isolated_rates), +}; + +static int audioinjector_isolated_dai_init(struct snd_soc_pcm_runtime *rtd) +{ + int ret=snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), 0, 24576000, 0); + if (ret) + return ret; + + return snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_cpu(rtd, 0), 64); +} + +static int audioinjector_isolated_startup(struct snd_pcm_substream *substream) +{ + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &audioinjector_isolated_constraints); + + return 0; +} + +static int audioinjector_isolated_trigger(struct snd_pcm_substream *substream, + int cmd){ + + switch (cmd) { + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + gpiod_set_value(mute_gpio, 0); + break; + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + gpiod_set_value(mute_gpio, 1); + break; + default: + return -EINVAL; + } + return 0; +} + +static struct snd_soc_ops audioinjector_isolated_ops = { + .startup = audioinjector_isolated_startup, + .trigger = audioinjector_isolated_trigger, +}; + +SND_SOC_DAILINK_DEFS(audioinjector_isolated, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), + DAILINK_COMP_ARRAY(COMP_CODEC("cs4271.1-0010", "cs4271-hifi")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0"))); + +static struct snd_soc_dai_link audioinjector_isolated_dai[] = { + { + .name = "AudioInjector ISO", + .stream_name = "AI-HIFI", + .ops = &audioinjector_isolated_ops, + .init = audioinjector_isolated_dai_init, + .symmetric_rate = 1, + .symmetric_channels = 1, + .dai_fmt = SND_SOC_DAIFMT_CBP_CFP|SND_SOC_DAIFMT_I2S|SND_SOC_DAIFMT_NB_NF, + SND_SOC_DAILINK_REG(audioinjector_isolated), + } +}; + +static const struct snd_soc_dapm_widget audioinjector_isolated_widgets[] = { + SND_SOC_DAPM_OUTPUT("OUTPUTS"), + SND_SOC_DAPM_INPUT("INPUTS"), +}; + +static const struct snd_soc_dapm_route audioinjector_isolated_route[] = { + /* Balanced outputs */ + {"OUTPUTS", NULL, "AOUTA+"}, + {"OUTPUTS", NULL, "AOUTA-"}, + {"OUTPUTS", NULL, "AOUTB+"}, + {"OUTPUTS", NULL, "AOUTB-"}, + + /* Balanced inputs */ + {"AINA", NULL, "INPUTS"}, + {"AINB", NULL, "INPUTS"}, +}; + +static struct snd_soc_card snd_soc_audioinjector_isolated = { + .name = "audioinjector-isolated-soundcard", + .owner = THIS_MODULE, + .dai_link = audioinjector_isolated_dai, + .num_links = ARRAY_SIZE(audioinjector_isolated_dai), + + .dapm_widgets = audioinjector_isolated_widgets, + .num_dapm_widgets = ARRAY_SIZE(audioinjector_isolated_widgets), + .dapm_routes = audioinjector_isolated_route, + .num_dapm_routes = ARRAY_SIZE(audioinjector_isolated_route), +}; + +static int audioinjector_isolated_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &snd_soc_audioinjector_isolated; + int ret; + + card->dev = &pdev->dev; + + if (pdev->dev.of_node) { + struct snd_soc_dai_link *dai = &audioinjector_isolated_dai[0]; + struct device_node *i2s_node = + of_parse_phandle(pdev->dev.of_node, "i2s-controller", 0); + + if (i2s_node) { + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + } else { + dev_err(&pdev->dev, + "i2s-controller missing or invalid in DT\n"); + return -EINVAL; + } + + mute_gpio = devm_gpiod_get_optional(&pdev->dev, "mute", GPIOD_OUT_LOW); + if (IS_ERR(mute_gpio)){ + dev_err(&pdev->dev, "mute gpio not found in dt overlay\n"); + return PTR_ERR(mute_gpio); + } + } + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret && ret != -EPROBE_DEFER) + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + return ret; +} + +static const struct of_device_id audioinjector_isolated_of_match[] = { + { .compatible = "ai,audioinjector-isolated-soundcard", }, + {}, +}; +MODULE_DEVICE_TABLE(of, audioinjector_isolated_of_match); + +static struct platform_driver audioinjector_isolated_driver = { + .driver = { + .name = "audioinjector-isolated", + .owner = THIS_MODULE, + .of_match_table = audioinjector_isolated_of_match, + }, + .probe = audioinjector_isolated_probe, +}; + +module_platform_driver(audioinjector_isolated_driver); +MODULE_AUTHOR("Matt Flax "); +MODULE_DESCRIPTION("AudioInjector.net isolated Soundcard"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:audioinjector-isolated-soundcard"); diff --git a/sound/soc/bcm/audioinjector-octo-soundcard.c b/sound/soc/bcm/audioinjector-octo-soundcard.c new file mode 100644 index 000000000000..0a5761109865 --- /dev/null +++ b/sound/soc/bcm/audioinjector-octo-soundcard.c @@ -0,0 +1,347 @@ +/* + * ASoC Driver for AudioInjector Pi octo channel soundcard (hat) + * + * Created on: 27-October-2016 + * Author: flatmax@flatmax.org + * based on audioinjector-pi-soundcard.c + * + * Copyright (C) 2016 Flatmax Pty. Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include + +#include +#include +#include +#include + +static struct gpio_descs *mult_gpios; +static struct gpio_desc *codec_rst_gpio; +static unsigned int audioinjector_octo_rate; +static bool non_stop_clocks; + +static const unsigned int audioinjector_octo_rates[] = { + 96000, 48000, 32000, 24000, 16000, 8000, 88200, 44100, 29400, 22050, 14700, +}; + +static struct snd_pcm_hw_constraint_list audioinjector_octo_constraints = { + .list = audioinjector_octo_rates, + .count = ARRAY_SIZE(audioinjector_octo_rates), +}; + +static int audioinjector_octo_dai_init(struct snd_soc_pcm_runtime *rtd) +{ + return snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_cpu(rtd, 0), 64); +} + +static int audioinjector_octo_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + snd_soc_rtd_to_cpu(rtd, 0)->driver->playback.channels_min = 8; + snd_soc_rtd_to_cpu(rtd, 0)->driver->playback.channels_max = 8; + snd_soc_rtd_to_cpu(rtd, 0)->driver->capture.channels_min = 8; + snd_soc_rtd_to_cpu(rtd, 0)->driver->capture.channels_max = 8; + snd_soc_rtd_to_codec(rtd, 0)->driver->capture.channels_max = 8; + + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &audioinjector_octo_constraints); + + return 0; +} + +static void audioinjector_octo_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + snd_soc_rtd_to_cpu(rtd, 0)->driver->playback.channels_min = 2; + snd_soc_rtd_to_cpu(rtd, 0)->driver->playback.channels_max = 2; + snd_soc_rtd_to_cpu(rtd, 0)->driver->capture.channels_min = 2; + snd_soc_rtd_to_cpu(rtd, 0)->driver->capture.channels_max = 2; + snd_soc_rtd_to_codec(rtd, 0)->driver->capture.channels_max = 6; +} + +static int audioinjector_octo_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + // set codec DAI configuration + int ret = snd_soc_dai_set_fmt(snd_soc_rtd_to_codec(rtd, 0), + SND_SOC_DAIFMT_CBC_CFC|SND_SOC_DAIFMT_DSP_A| + SND_SOC_DAIFMT_NB_NF); + if (ret < 0) + return ret; + + // set cpu DAI configuration + ret = snd_soc_dai_set_fmt(snd_soc_rtd_to_cpu(rtd, 0), + SND_SOC_DAIFMT_CBP_CFP|SND_SOC_DAIFMT_I2S| + SND_SOC_DAIFMT_NB_NF); + if (ret < 0) + return ret; + + audioinjector_octo_rate = params_rate(params); + + // Set the correct sysclock for the codec + switch (audioinjector_octo_rate) { + case 96000: + case 48000: + return snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), 0, 49152000, + 0); + break; + case 24000: + return snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), 0, 49152000/2, + 0); + break; + case 32000: + case 16000: + return snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), 0, 49152000/3, + 0); + break; + case 8000: + return snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), 0, 49152000/6, + 0); + break; + case 88200: + case 44100: + return snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), 0, 45185400, + 0); + break; + case 22050: + return snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), 0, 45185400/2, + 0); + break; + case 29400: + case 14700: + return snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), 0, 45185400/3, + 0); + break; + default: + return -EINVAL; + } +} + +static int audioinjector_octo_trigger(struct snd_pcm_substream *substream, + int cmd){ + DECLARE_BITMAP(mult, 4); + + memset(mult, 0, sizeof(mult)); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (!non_stop_clocks) + break; + fallthrough; + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + switch (audioinjector_octo_rate) { + case 96000: + __assign_bit(3, mult, 1); + fallthrough; + case 88200: + __assign_bit(1, mult, 1); + __assign_bit(2, mult, 1); + break; + case 48000: + __assign_bit(3, mult, 1); + fallthrough; + case 44100: + __assign_bit(2, mult, 1); + break; + case 32000: + __assign_bit(3, mult, 1); + fallthrough; + case 29400: + __assign_bit(0, mult, 1); + __assign_bit(1, mult, 1); + break; + case 24000: + __assign_bit(3, mult, 1); + fallthrough; + case 22050: + __assign_bit(1, mult, 1); + break; + case 16000: + __assign_bit(3, mult, 1); + fallthrough; + case 14700: + __assign_bit(0, mult, 1); + break; + case 8000: + __assign_bit(3, mult, 1); + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + gpiod_set_array_value_cansleep(mult_gpios->ndescs, mult_gpios->desc, + NULL, mult); + + return 0; +} + +static struct snd_soc_ops audioinjector_octo_ops = { + .startup = audioinjector_octo_startup, + .shutdown = audioinjector_octo_shutdown, + .hw_params = audioinjector_octo_hw_params, + .trigger = audioinjector_octo_trigger, +}; + +SND_SOC_DAILINK_DEFS(audioinjector_octo, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "cs42448")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link audioinjector_octo_dai[] = { + { + .name = "AudioInjector Octo", + .stream_name = "AudioInject-HIFI", + .ops = &audioinjector_octo_ops, + .init = audioinjector_octo_dai_init, + .symmetric_rate = 1, + .symmetric_channels = 1, + SND_SOC_DAILINK_REG(audioinjector_octo), + }, +}; + +static const struct snd_soc_dapm_widget audioinjector_octo_widgets[] = { + SND_SOC_DAPM_OUTPUT("OUTPUTS0"), + SND_SOC_DAPM_OUTPUT("OUTPUTS1"), + SND_SOC_DAPM_OUTPUT("OUTPUTS2"), + SND_SOC_DAPM_OUTPUT("OUTPUTS3"), + SND_SOC_DAPM_INPUT("INPUTS0"), + SND_SOC_DAPM_INPUT("INPUTS1"), + SND_SOC_DAPM_INPUT("INPUTS2"), +}; + +static const struct snd_soc_dapm_route audioinjector_octo_route[] = { + /* Balanced outputs */ + {"OUTPUTS0", NULL, "AOUT1L"}, + {"OUTPUTS0", NULL, "AOUT1R"}, + {"OUTPUTS1", NULL, "AOUT2L"}, + {"OUTPUTS1", NULL, "AOUT2R"}, + {"OUTPUTS2", NULL, "AOUT3L"}, + {"OUTPUTS2", NULL, "AOUT3R"}, + {"OUTPUTS3", NULL, "AOUT4L"}, + {"OUTPUTS3", NULL, "AOUT4R"}, + + /* Balanced inputs */ + {"AIN1L", NULL, "INPUTS0"}, + {"AIN1R", NULL, "INPUTS0"}, + {"AIN2L", NULL, "INPUTS1"}, + {"AIN2R", NULL, "INPUTS1"}, + {"AIN3L", NULL, "INPUTS2"}, + {"AIN3R", NULL, "INPUTS2"}, +}; + +static struct snd_soc_card snd_soc_audioinjector_octo = { + .name = "audioinjector-octo-soundcard", + .owner = THIS_MODULE, + .dai_link = audioinjector_octo_dai, + .num_links = ARRAY_SIZE(audioinjector_octo_dai), + + .dapm_widgets = audioinjector_octo_widgets, + .num_dapm_widgets = ARRAY_SIZE(audioinjector_octo_widgets), + .dapm_routes = audioinjector_octo_route, + .num_dapm_routes = ARRAY_SIZE(audioinjector_octo_route), +}; + +static int audioinjector_octo_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &snd_soc_audioinjector_octo; + int ret; + + card->dev = &pdev->dev; + + if (pdev->dev.of_node) { + struct snd_soc_dai_link *dai = &audioinjector_octo_dai[0]; + struct device_node *i2s_node = + of_parse_phandle(pdev->dev.of_node, + "i2s-controller", 0); + struct device_node *codec_node = + of_parse_phandle(pdev->dev.of_node, + "codec", 0); + + mult_gpios = devm_gpiod_get_array_optional(&pdev->dev, "mult", + GPIOD_OUT_LOW); + if (IS_ERR(mult_gpios)) + return PTR_ERR(mult_gpios); + + codec_rst_gpio = devm_gpiod_get_optional(&pdev->dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(codec_rst_gpio)) + return PTR_ERR(codec_rst_gpio); + + non_stop_clocks = of_property_read_bool(pdev->dev.of_node, "non-stop-clocks"); + + if (codec_rst_gpio) + gpiod_set_value(codec_rst_gpio, 1); + msleep(500); + if (codec_rst_gpio) + gpiod_set_value(codec_rst_gpio, 0); + msleep(500); + if (codec_rst_gpio) + gpiod_set_value(codec_rst_gpio, 1); + msleep(500); + + if (i2s_node && codec_node) { + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + dai->codecs->name = NULL; + dai->codecs->of_node = codec_node; + } else + if (!i2s_node) { + dev_err(&pdev->dev, + "i2s-controller missing or invalid in DT\n"); + return -EINVAL; + } else { + dev_err(&pdev->dev, + "Property 'codec' missing or invalid\n"); + return -EINVAL; + } + } + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret != 0) + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + return ret; +} + +static const struct of_device_id audioinjector_octo_of_match[] = { + { .compatible = "ai,audioinjector-octo-soundcard", }, + {}, +}; +MODULE_DEVICE_TABLE(of, audioinjector_octo_of_match); + +static struct platform_driver audioinjector_octo_driver = { + .driver = { + .name = "audioinjector-octo", + .owner = THIS_MODULE, + .of_match_table = audioinjector_octo_of_match, + }, + .probe = audioinjector_octo_probe, +}; + +module_platform_driver(audioinjector_octo_driver); +MODULE_AUTHOR("Matt Flax "); +MODULE_DESCRIPTION("AudioInjector.net octo Soundcard"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:audioinjector-octo-soundcard"); diff --git a/sound/soc/bcm/audioinjector-pi-soundcard.c b/sound/soc/bcm/audioinjector-pi-soundcard.c new file mode 100644 index 000000000000..5ff0b28eac8b --- /dev/null +++ b/sound/soc/bcm/audioinjector-pi-soundcard.c @@ -0,0 +1,190 @@ +/* + * ASoC Driver for AudioInjector Pi add on soundcard + * + * Created on: 13-May-2016 + * Author: flatmax@flatmax.org + * based on code by Cliff Cai for the ssm2602 machine blackfin. + * with help from Lars-Peter Clausen for simplifying the original code to use the dai_fmt field. + * i2s_node code taken from the other sound/soc/bcm machine drivers. + * + * Copyright (C) 2016 Flatmax Pty. Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include + +#include +#include +#include +#include + +#include "../codecs/wm8731.h" + +static const unsigned int bcm2835_rates_12000000[] = { + 8000, 16000, 32000, 44100, 48000, 96000, 88200, +}; + +static struct snd_pcm_hw_constraint_list bcm2835_constraints_12000000 = { + .list = bcm2835_rates_12000000, + .count = ARRAY_SIZE(bcm2835_rates_12000000), +}; + +static int snd_audioinjector_pi_soundcard_startup(struct snd_pcm_substream *substream) +{ + /* Setup constraints, because there is a 12 MHz XTAL on the board */ + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &bcm2835_constraints_12000000); + return 0; +} + +static int snd_audioinjector_pi_soundcard_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + + switch (params_rate(params)){ + case 8000: + return snd_soc_dai_set_bclk_ratio(cpu_dai, 1); + case 16000: + return snd_soc_dai_set_bclk_ratio(cpu_dai, 750); + case 32000: + return snd_soc_dai_set_bclk_ratio(cpu_dai, 375); + case 44100: + return snd_soc_dai_set_bclk_ratio(cpu_dai, 272); + case 48000: + return snd_soc_dai_set_bclk_ratio(cpu_dai, 250); + case 88200: + return snd_soc_dai_set_bclk_ratio(cpu_dai, 136); + case 96000: + return snd_soc_dai_set_bclk_ratio(cpu_dai, 125); + default: + return snd_soc_dai_set_bclk_ratio(cpu_dai, 125); + } +} + +/* machine stream operations */ +static struct snd_soc_ops snd_audioinjector_pi_soundcard_ops = { + .startup = snd_audioinjector_pi_soundcard_startup, + .hw_params = snd_audioinjector_pi_soundcard_hw_params, +}; + +static int audioinjector_pi_soundcard_dai_init(struct snd_soc_pcm_runtime *rtd) +{ + return snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), WM8731_SYSCLK_XTAL, 12000000, SND_SOC_CLOCK_IN); +} + +SND_SOC_DAILINK_DEFS(audioinjector_pi, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), + DAILINK_COMP_ARRAY(COMP_CODEC("wm8731.1-001a", "wm8731-hifi")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2835-i2s.0"))); + +static struct snd_soc_dai_link audioinjector_pi_soundcard_dai[] = { + { + .name = "AudioInjector audio", + .stream_name = "AudioInjector audio", + .ops = &snd_audioinjector_pi_soundcard_ops, + .init = audioinjector_pi_soundcard_dai_init, + .dai_fmt = SND_SOC_DAIFMT_CBP_CFP|SND_SOC_DAIFMT_I2S|SND_SOC_DAIFMT_NB_NF, + SND_SOC_DAILINK_REG(audioinjector_pi), + }, +}; + +static const struct snd_soc_dapm_widget wm8731_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), + SND_SOC_DAPM_LINE("Line In Jacks", NULL), + SND_SOC_DAPM_MIC("Microphone", NULL), +}; + +static const struct snd_soc_dapm_route audioinjector_audio_map[] = { + /* headphone connected to LHPOUT, RHPOUT */ + {"Headphone Jack", NULL, "LHPOUT"}, + {"Headphone Jack", NULL, "RHPOUT"}, + + /* speaker connected to LOUT, ROUT */ + {"Ext Spk", NULL, "ROUT"}, + {"Ext Spk", NULL, "LOUT"}, + + /* line inputs */ + {"Line In Jacks", NULL, "Line Input"}, + + /* mic is connected to Mic Jack, with WM8731 Mic Bias */ + {"Microphone", NULL, "Mic Bias"}, +}; + +static struct snd_soc_card snd_soc_audioinjector = { + .name = "audioinjector-pi-soundcard", + .owner = THIS_MODULE, + .dai_link = audioinjector_pi_soundcard_dai, + .num_links = ARRAY_SIZE(audioinjector_pi_soundcard_dai), + + .dapm_widgets = wm8731_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8731_dapm_widgets), + .dapm_routes = audioinjector_audio_map, + .num_dapm_routes = ARRAY_SIZE(audioinjector_audio_map), +}; + +static int audioinjector_pi_soundcard_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &snd_soc_audioinjector; + int ret; + + card->dev = &pdev->dev; + + if (pdev->dev.of_node) { + struct snd_soc_dai_link *dai = &audioinjector_pi_soundcard_dai[0]; + struct device_node *i2s_node = of_parse_phandle(pdev->dev.of_node, + "i2s-controller", 0); + + if (i2s_node) { + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + } else + if (!dai->cpus->of_node) { + dev_err(&pdev->dev, "Property 'i2s-controller' missing or invalid\n"); + return -EINVAL; + } + } + + if ((ret = devm_snd_soc_register_card(&pdev->dev, card))) + return dev_err_probe(&pdev->dev, ret, "%s\n", __func__); + + dev_info(&pdev->dev, "successfully loaded\n"); + + return ret; +} + +static const struct of_device_id audioinjector_pi_soundcard_of_match[] = { + { .compatible = "ai,audioinjector-pi-soundcard", }, + {}, +}; +MODULE_DEVICE_TABLE(of, audioinjector_pi_soundcard_of_match); + +static struct platform_driver audioinjector_pi_soundcard_driver = { + .driver = { + .name = "audioinjector-stereo", + .owner = THIS_MODULE, + .of_match_table = audioinjector_pi_soundcard_of_match, + }, + .probe = audioinjector_pi_soundcard_probe, +}; + +module_platform_driver(audioinjector_pi_soundcard_driver); +MODULE_AUTHOR("Matt Flax "); +MODULE_DESCRIPTION("AudioInjector.net Pi Soundcard"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:audioinjector-pi-soundcard"); + diff --git a/sound/soc/bcm/audiosense-pi.c b/sound/soc/bcm/audiosense-pi.c new file mode 100644 index 000000000000..1975576e49ac --- /dev/null +++ b/sound/soc/bcm/audiosense-pi.c @@ -0,0 +1,247 @@ +/* + * ASoC Driver for AudioSense add on soundcard + * Author: + * Bhargav A K + * Copyright 2017 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "../codecs/tlv320aic32x4.h" + +#define AIC32X4_SYSCLK_XTAL 0x00 + +/* + * Setup Codec Sample Rates and Channels + * Supported Rates: + * 8000, 11025, 16000, 22050, 32000, 44100, 48000, 64000, 88200, 96000, + */ +static const unsigned int audiosense_pi_rate[] = { + 48000, +}; + +static struct snd_pcm_hw_constraint_list audiosense_constraints_rates = { + .list = audiosense_pi_rate, + .count = ARRAY_SIZE(audiosense_pi_rate), +}; + +static const unsigned int audiosense_pi_channels[] = { + 2, +}; + +static struct snd_pcm_hw_constraint_list audiosense_constraints_ch = { + .count = ARRAY_SIZE(audiosense_pi_channels), + .list = audiosense_pi_channels, + .mask = 0, +}; + +/* Setup DAPM widgets and paths */ +static const struct snd_soc_dapm_widget audiosense_pi_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_LINE("Line Out", NULL), + SND_SOC_DAPM_LINE("Line In", NULL), + SND_SOC_DAPM_INPUT("CM_L"), + SND_SOC_DAPM_INPUT("CM_R"), +}; + +static const struct snd_soc_dapm_route audiosense_pi_audio_map[] = { + /* Line Inputs are connected to + * (IN1_L | IN1_R) + * (IN2_L | IN2_R) + * (IN3_L | IN3_R) + */ + {"IN1_L", NULL, "Line In"}, + {"IN1_R", NULL, "Line In"}, + {"IN2_L", NULL, "Line In"}, + {"IN2_R", NULL, "Line In"}, + {"IN3_L", NULL, "Line In"}, + {"IN3_R", NULL, "Line In"}, + + /* Mic is connected to IN2_L and IN2_R */ + {"Left ADC", NULL, "Mic Bias"}, + {"Right ADC", NULL, "Mic Bias"}, + + /* Headphone connected to HPL, HPR */ + {"Headphone Jack", NULL, "HPL"}, + {"Headphone Jack", NULL, "HPR"}, + + /* Speakers connected to LOL and LOR */ + {"Line Out", NULL, "LOL"}, + {"Line Out", NULL, "LOR"}, +}; + +static int audiosense_pi_card_init(struct snd_soc_pcm_runtime *rtd) +{ + /* TODO: init of the codec specific dapm data, ignore suspend/resume */ + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + + snd_soc_component_update_bits(component, AIC32X4_MICBIAS, 0x78, + AIC32X4_MICBIAS_LDOIN | + AIC32X4_MICBIAS_2075V); + snd_soc_component_update_bits(component, AIC32X4_PWRCFG, 0x08, + AIC32X4_AVDDWEAKDISABLE); + snd_soc_component_update_bits(component, AIC32X4_LDOCTL, 0x01, + AIC32X4_LDOCTLEN); + + return 0; +} + +static int audiosense_pi_card_hw_params( + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + int ret = 0; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + + /* Set the codec system clock, there is a 12 MHz XTAL on the board */ + ret = snd_soc_dai_set_sysclk(codec_dai, AIC32X4_SYSCLK_XTAL, + 12000000, SND_SOC_CLOCK_IN); + if (ret) { + dev_err(rtd->card->dev, + "could not set codec driver clock params\n"); + return ret; + } + return 0; +} + +static int audiosense_pi_card_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + /* + * Set codec to 48Khz Sampling, Stereo I/O and 16 bit audio + */ + runtime->hw.channels_max = 2; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &audiosense_constraints_ch); + + runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; + snd_pcm_hw_constraint_msbits(runtime, 0, 16, 16); + + + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &audiosense_constraints_rates); + return 0; +} + +static struct snd_soc_ops audiosense_pi_card_ops = { + .startup = audiosense_pi_card_startup, + .hw_params = audiosense_pi_card_hw_params, +}; + +SND_SOC_DAILINK_DEFS(audiosense_pi, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), + DAILINK_COMP_ARRAY(COMP_CODEC("tlv320aic32x4.1-0018", "tlv320aic32x4-hifi")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0"))); + +static struct snd_soc_dai_link audiosense_pi_card_dai[] = { + { + .name = "TLV320AIC3204 Audio", + .stream_name = "TLV320AIC3204 Hifi Audio", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBP_CFP, + .ops = &audiosense_pi_card_ops, + .init = audiosense_pi_card_init, + SND_SOC_DAILINK_REG(audiosense_pi), + }, +}; + +static struct snd_soc_card audiosense_pi_card = { + .name = "audiosense-pi", + .driver_name = "audiosense-pi", + .dai_link = audiosense_pi_card_dai, + .owner = THIS_MODULE, + .num_links = ARRAY_SIZE(audiosense_pi_card_dai), + .dapm_widgets = audiosense_pi_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(audiosense_pi_dapm_widgets), + .dapm_routes = audiosense_pi_audio_map, + .num_dapm_routes = ARRAY_SIZE(audiosense_pi_audio_map), +}; + +static int audiosense_pi_card_probe(struct platform_device *pdev) +{ + int ret = 0; + struct snd_soc_card *card = &audiosense_pi_card; + struct snd_soc_dai_link *dai = &audiosense_pi_card_dai[0]; + struct device_node *i2s_node = pdev->dev.of_node; + + card->dev = &pdev->dev; + + if (!dai) { + dev_err(&pdev->dev, "DAI not found. Missing or Invalid\n"); + return -EINVAL; + } + + i2s_node = of_parse_phandle(pdev->dev.of_node, "i2s-controller", 0); + if (!i2s_node) { + dev_err(&pdev->dev, + "Property 'i2s-controller' missing or invalid\n"); + return -EINVAL; + } + + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + + of_node_put(i2s_node); + + ret = snd_soc_register_card(card); + if (ret && ret != -EPROBE_DEFER) + dev_err(&pdev->dev, + "snd_soc_register_card() failed: %d\n", ret); + + return ret; +} + +static void audiosense_pi_card_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + + snd_soc_unregister_card(card); +} + +static const struct of_device_id audiosense_pi_card_of_match[] = { + { .compatible = "as,audiosense-pi", }, + {}, +}; +MODULE_DEVICE_TABLE(of, audiosense_pi_card_of_match); + +static struct platform_driver audiosense_pi_card_driver = { + .driver = { + .name = "audiosense-snd-card", + .owner = THIS_MODULE, + .of_match_table = audiosense_pi_card_of_match, + }, + .probe = audiosense_pi_card_probe, + .remove = audiosense_pi_card_remove, +}; + +module_platform_driver(audiosense_pi_card_driver); + +MODULE_AUTHOR("Bhargav AK "); +MODULE_DESCRIPTION("ASoC Driver for TLV320AIC3204 Audio"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:audiosense-pi"); + diff --git a/sound/soc/bcm/bcm2835-i2s.c b/sound/soc/bcm/bcm2835-i2s.c index 87d2f06c2f53..cbbf6723ba20 100644 --- a/sound/soc/bcm/bcm2835-i2s.c +++ b/sound/soc/bcm/bcm2835-i2s.c @@ -30,7 +30,6 @@ #include #include #include -#include #include #include @@ -620,6 +619,10 @@ static int bcm2835_i2s_prepare(struct snd_pcm_substream *substream, struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); uint32_t cs_reg; + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 256, + ~0); + /* * Clear both FIFOs if the one that should be started * is not empty at the moment. This should only happen @@ -701,6 +704,10 @@ static int bcm2835_i2s_startup(struct snd_pcm_substream *substream, /* Should this still be running stop it */ bcm2835_i2s_stop_clock(dev); + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + 256, ~0); + /* Enable PCM block */ regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG, BCM2835_I2S_EN, BCM2835_I2S_EN); @@ -830,8 +837,7 @@ static int bcm2835_i2s_probe(struct platform_device *pdev) struct bcm2835_i2s_dev *dev; int ret; void __iomem *base; - const __be32 *addr; - dma_addr_t dma_base; + struct resource *res; dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); @@ -846,7 +852,7 @@ static int bcm2835_i2s_probe(struct platform_device *pdev) "could not get clk\n"); /* Request ioarea */ - base = devm_platform_ioremap_resource(pdev, 0); + base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); if (IS_ERR(base)) return PTR_ERR(base); @@ -855,19 +861,11 @@ static int bcm2835_i2s_probe(struct platform_device *pdev) if (IS_ERR(dev->i2s_regmap)) return PTR_ERR(dev->i2s_regmap); - /* Set the DMA address - we have to parse DT ourselves */ - addr = of_get_address(pdev->dev.of_node, 0, NULL, NULL); - if (!addr) { - dev_err(&pdev->dev, "could not get DMA-register address\n"); - return -EINVAL; - } - dma_base = be32_to_cpup(addr); - dev->dma_data[SNDRV_PCM_STREAM_PLAYBACK].addr = - dma_base + BCM2835_I2S_FIFO_A_REG; + res->start + BCM2835_I2S_FIFO_A_REG; dev->dma_data[SNDRV_PCM_STREAM_CAPTURE].addr = - dma_base + BCM2835_I2S_FIFO_A_REG; + res->start + BCM2835_I2S_FIFO_A_REG; /* Set the bus width */ dev->dma_data[SNDRV_PCM_STREAM_PLAYBACK].addr_width = diff --git a/sound/soc/bcm/chipdip-dac.c b/sound/soc/bcm/chipdip-dac.c new file mode 100644 index 000000000000..64e346f1a066 --- /dev/null +++ b/sound/soc/bcm/chipdip-dac.c @@ -0,0 +1,275 @@ +/* + * ASoC Driver for ChipDip DAC + * + * Author: Evgenij Sapunov + * Copyright 2021 + * based on code by Milan Neskovic + * based on code by Jaikumar + * + * Thanks to Phil Elwell (pelwell) for help. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define SR_BIT_0 0 //sample rate bits +#define SR_BIT_1 1 +#define SR_BIT_2 2 +#define BD_BIT_0 3 //bit depth bits +#define BD_BIT_1 4 + +#define SAMPLE_RATE_MASK_44_1 0 +#define SAMPLE_RATE_MASK_48 (1 << SR_BIT_0) +#define SAMPLE_RATE_MASK_88_2 ((1 << SR_BIT_2) | (1 << SR_BIT_1)) +#define SAMPLE_RATE_MASK_96 (1 << SR_BIT_1) +#define SAMPLE_RATE_MASK_176_4 ((1 << SR_BIT_2) | (1 << SR_BIT_1) | (1 << SR_BIT_0)) +#define SAMPLE_RATE_MASK_192 ((1 << SR_BIT_1) | (1 << SR_BIT_0)) +#define SAMPLE_RATE_MASK ((1 << SR_BIT_2) | (1 << SR_BIT_1) | (1 << SR_BIT_0)) + +#define BIT_DEPTH_MASK_16 0 +#define BIT_DEPTH_MASK_24 (1 << BD_BIT_0) +#define BIT_DEPTH_MASK_32 (1 << BD_BIT_1) +#define BIT_DEPTH_MASK ((1 << BD_BIT_1) | (1 << BD_BIT_0)) + +#define MUTE_ACTIVE 0 +#define MUTE_NOT_ACTIVE 1 + +#define HW_PARAMS_GPIO_COUNT 5 + +static struct gpio_desc *mute_gpio; +static struct gpio_desc *sdwn_gpio; +static struct gpio_desc *hw_params_gpios[HW_PARAMS_GPIO_COUNT]; +static int current_width; +static int current_rate; + +static void snd_rpi_chipdip_dac_gpio_array_set(int value); +static void snd_rpi_chipdip_dac_gpio_set(struct gpio_desc *gpio_item, int value); + +static void snd_rpi_chipdip_dac_gpio_array_set(int value) +{ + int i = 0; + + for (i = 0; i < HW_PARAMS_GPIO_COUNT; i++) + snd_rpi_chipdip_dac_gpio_set(hw_params_gpios[i], ((value >> i) & 1)); +} + +static void snd_rpi_chipdip_dac_gpio_set(struct gpio_desc *gpio_item, int value) +{ + if (gpio_item) + gpiod_set_value_cansleep(gpio_item, value); +} + +static int snd_rpi_chipdip_dac_init(struct snd_soc_pcm_runtime *rtd) +{ + return 0; +} + +static int snd_rpi_chipdip_dac_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + int ret = 0; + int gpio_change_pending = 0; + int sample_rate_state = 0; + int bit_depth_state = 0; + int param_value = params_width(params); + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_cpu(rtd, 0), 2 * 32); + + if (current_width != param_value) { + current_width = param_value; + gpio_change_pending = 1; + + switch (param_value) { + case 16: + bit_depth_state = BIT_DEPTH_MASK_16; + break; + case 24: + bit_depth_state = BIT_DEPTH_MASK_24; + break; + case 32: + bit_depth_state = BIT_DEPTH_MASK_32; + break; + default: + return -EINVAL; + } + } + + param_value = params_rate(params); + if (current_rate != param_value) { + current_rate = param_value; + gpio_change_pending = 1; + + switch (param_value) { + case 44100: + sample_rate_state = SAMPLE_RATE_MASK_44_1; + break; + case 48000: + sample_rate_state = SAMPLE_RATE_MASK_48; + break; + case 88200: + sample_rate_state = SAMPLE_RATE_MASK_88_2; + break; + case 96000: + sample_rate_state = SAMPLE_RATE_MASK_96; + break; + case 176400: + sample_rate_state = SAMPLE_RATE_MASK_176_4; + break; + case 192000: + sample_rate_state = SAMPLE_RATE_MASK_192; + break; + default: + return -EINVAL; + } + } + + if (gpio_change_pending) { + snd_rpi_chipdip_dac_gpio_set(mute_gpio, MUTE_ACTIVE); + snd_rpi_chipdip_dac_gpio_array_set(bit_depth_state | sample_rate_state); + msleep(300); + snd_rpi_chipdip_dac_gpio_set(mute_gpio, MUTE_NOT_ACTIVE); + } + + return ret; +} + +static int snd_rpi_chipdip_dac_startup(struct snd_pcm_substream *substream) +{ + return 0; +} + +static void snd_rpi_chipdip_dac_shutdown(struct snd_pcm_substream *substream) +{ + +} + +/* machine stream operations */ +static struct snd_soc_ops snd_rpi_chipdip_dac_ops = { + .hw_params = snd_rpi_chipdip_dac_hw_params, + .startup = snd_rpi_chipdip_dac_startup, + .shutdown = snd_rpi_chipdip_dac_shutdown, +}; + +SND_SOC_DAILINK_DEFS(hifi, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), + DAILINK_COMP_ARRAY(COMP_CODEC("spdif-transmitter", "dit-hifi")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0"))); + +static struct snd_soc_dai_link snd_rpi_chipdip_dac_dai[] = { +{ + .name = "ChipDip DAC", + .stream_name = "ChipDip DAC HiFi", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBP_CFP, + .ops = &snd_rpi_chipdip_dac_ops, + .init = snd_rpi_chipdip_dac_init, + SND_SOC_DAILINK_REG(hifi), +}, +}; + +/* audio machine driver */ +static struct snd_soc_card snd_rpi_chipdip_dac = { + .name = "ChipDipDAC", + .driver_name = "ChipdipDac", + .owner = THIS_MODULE, + .dai_link = snd_rpi_chipdip_dac_dai, + .num_links = ARRAY_SIZE(snd_rpi_chipdip_dac_dai), +}; + +static int snd_rpi_chipdip_dac_probe(struct platform_device *pdev) +{ + int ret = 0; + int i = 0; + + snd_rpi_chipdip_dac.dev = &pdev->dev; + + if (pdev->dev.of_node) { + struct device_node *i2s_node; + struct snd_soc_dai_link *dai = &snd_rpi_chipdip_dac_dai[0]; + i2s_node = of_parse_phandle(pdev->dev.of_node, + "i2s-controller", 0); + + if (i2s_node) { + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + } + } + + hw_params_gpios[SR_BIT_0] = devm_gpiod_get_optional(&pdev->dev, "sr0", GPIOD_OUT_LOW); + hw_params_gpios[SR_BIT_1] = devm_gpiod_get_optional(&pdev->dev, "sr1", GPIOD_OUT_LOW); + hw_params_gpios[SR_BIT_2] = devm_gpiod_get_optional(&pdev->dev, "sr2", GPIOD_OUT_LOW); + hw_params_gpios[BD_BIT_0] = devm_gpiod_get_optional(&pdev->dev, "res0", GPIOD_OUT_LOW); + hw_params_gpios[BD_BIT_1] = devm_gpiod_get_optional(&pdev->dev, "res1", GPIOD_OUT_LOW); + mute_gpio = devm_gpiod_get_optional(&pdev->dev, "mute", GPIOD_OUT_LOW); + sdwn_gpio = devm_gpiod_get_optional(&pdev->dev, "sdwn", GPIOD_OUT_HIGH); + + for (i = 0; i < HW_PARAMS_GPIO_COUNT; i++) { + if (IS_ERR(hw_params_gpios[i])) { + ret = PTR_ERR(hw_params_gpios[i]); + dev_err(&pdev->dev, "failed to get hw_params gpio: %d\n", ret); + return ret; + } + } + + if (IS_ERR(mute_gpio)) { + ret = PTR_ERR(mute_gpio); + dev_err(&pdev->dev, "failed to get mute gpio: %d\n", ret); + return ret; + } + + if (IS_ERR(sdwn_gpio)) { + ret = PTR_ERR(sdwn_gpio); + dev_err(&pdev->dev, "failed to get sdwn gpio: %d\n", ret); + return ret; + } + + snd_rpi_chipdip_dac_gpio_set(sdwn_gpio, 1); + + ret = devm_snd_soc_register_card(&pdev->dev, &snd_rpi_chipdip_dac); + if (ret && ret != -EPROBE_DEFER) + dev_err(&pdev->dev, + "snd_soc_register_card() failed: %d\n", ret); + + return ret; +} + +static const struct of_device_id snd_rpi_chipdip_dac_of_match[] = { + { .compatible = "chipdip,chipdip-dac", }, + {}, +}; +MODULE_DEVICE_TABLE(of, snd_rpi_chipdip_dac_of_match); + +static struct platform_driver snd_rpi_chipdip_dac_driver = { + .driver = { + .name = "snd-rpi-chipdip-dac", + .owner = THIS_MODULE, + .of_match_table = snd_rpi_chipdip_dac_of_match, + }, + .probe = snd_rpi_chipdip_dac_probe, +}; + +module_platform_driver(snd_rpi_chipdip_dac_driver); + +MODULE_AUTHOR("Evgenij Sapunov "); +MODULE_DESCRIPTION("ASoC Driver for ChipDip DAC"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/dacberry400.c b/sound/soc/bcm/dacberry400.c new file mode 100644 index 000000000000..42ede1511e0f --- /dev/null +++ b/sound/soc/bcm/dacberry400.c @@ -0,0 +1,258 @@ +/* + * ASoC Driver for Dacberry400 soundcard + * Author: + * Ashish Vara + * Copyright 2022 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../sound/soc/codecs/tlv320aic3x.h" + +static const struct snd_kcontrol_new dacberry400_controls[] = { + SOC_DAPM_PIN_SWITCH("MIC Jack"), + SOC_DAPM_PIN_SWITCH("Line In"), + SOC_DAPM_PIN_SWITCH("Line Out"), + SOC_DAPM_PIN_SWITCH("Headphone Jack"), +}; + +static const struct snd_soc_dapm_widget dacberry400_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("MIC Jack", NULL), + SND_SOC_DAPM_LINE("Line In", NULL), + SND_SOC_DAPM_LINE("Line Out", NULL), +}; + +static const struct snd_soc_dapm_route dacberry400_audio_map[] = { + {"Headphone Jack", NULL, "HPLOUT"}, + {"Headphone Jack", NULL, "HPROUT"}, + + {"LINE1L", NULL, "Line In"}, + {"LINE1R", NULL, "Line In"}, + + {"Line Out", NULL, "LLOUT"}, + {"Line Out", NULL, "RLOUT"}, + + {"MIC3L", NULL, "MIC Jack"}, + {"MIC3R", NULL, "MIC Jack"}, +}; + +static int snd_rpi_dacberry400_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + struct snd_soc_component *component = codec_dai->component; + int ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, 2, 12000000, + SND_SOC_CLOCK_OUT); + + if (ret && ret != -ENOTSUPP) + goto err; + + snd_soc_component_write(component, HPRCOM_CFG, 0x20); + snd_soc_component_write(component, DACL1_2_HPLOUT_VOL, 0x80); + snd_soc_component_write(component, DACR1_2_HPROUT_VOL, 0x80); +err: + return ret; +} + +static int snd_rpi_dacberry400_set_bias_level(struct snd_soc_card *card, + struct snd_soc_dapm_context *dapm, enum snd_soc_bias_level level) +{ + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_dai *codec_dai; + struct snd_soc_component *component; + struct dacberry_priv *aic3x; + u8 hpcom_reg = 0; + + rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]); + codec_dai = snd_soc_rtd_to_codec(rtd, 0); + component = codec_dai->component; + aic3x = snd_soc_component_get_drvdata(component); + if (dapm->dev != codec_dai->dev) + return 0; + + switch (level) { + case SND_SOC_BIAS_PREPARE: + if (dapm->bias_level != SND_SOC_BIAS_STANDBY) + break; + /* UNMUTE ADC/DAC */ + hpcom_reg = snd_soc_component_read(component, HPLCOM_CFG); + snd_soc_component_write(component, HPLCOM_CFG, hpcom_reg | 0x20); + snd_soc_component_write(component, LINE1R_2_RADC_CTRL, 0x04); + snd_soc_component_write(component, LINE1L_2_LADC_CTRL, 0x04); + snd_soc_component_write(component, LADC_VOL, 0x00); + snd_soc_component_write(component, RADC_VOL, 0x00); + pr_info("%s: unmute ADC/DAC\n", __func__); + break; + + case SND_SOC_BIAS_STANDBY: + if (dapm->bias_level != SND_SOC_BIAS_PREPARE) + break; + /* MUTE ADC/DAC */ + snd_soc_component_write(component, LDAC_VOL, 0x80); + snd_soc_component_write(component, RDAC_VOL, 0x80); + snd_soc_component_write(component, LADC_VOL, 0x80); + snd_soc_component_write(component, RADC_VOL, 0x80); + snd_soc_component_write(component, LINE1R_2_RADC_CTRL, 0x00); + snd_soc_component_write(component, LINE1L_2_LADC_CTRL, 0x00); + snd_soc_component_write(component, HPLCOM_CFG, 0x00); + pr_info("%s: mute ADC/DAC\n", __func__); + break; + default: + break; + } + + return 0; +} + +static int snd_rpi_dacberry400_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + int ret = 0; + u8 data; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + struct snd_soc_component *component = codec_dai->component; + int fsref = (params_rate(params) % 11025 == 0) ? 44100 : 48000; + int channels = params_channels(params); + int width = 32; + u8 clock = 0; + + data = (LDAC2LCH | RDAC2RCH); + data |= (fsref == 44100) ? FSREF_44100 : FSREF_48000; + if (params_rate(params) >= 64000) + data |= DUAL_RATE_MODE; + ret = snd_soc_component_write(component, 0x7, data); + width = params_width(params); + + clock = snd_soc_component_read(component, 2); + + ret = snd_soc_dai_set_bclk_ratio(cpu_dai, channels*width); + + return ret; +} + +static const struct snd_soc_ops snd_rpi_dacberry400_ops = { + .hw_params = snd_rpi_dacberry400_hw_params, +}; + + +SND_SOC_DAILINK_DEFS(rpi_dacberry400, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2835-i2s.0")), + DAILINK_COMP_ARRAY(COMP_CODEC("tlv320aic3x.1-0018", "tlv320aic3x-hifi")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2835-i2s.0"))); + +static struct snd_soc_dai_link snd_rpi_dacberry400_dai[] = { +{ + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBC_CFC, + .init = snd_rpi_dacberry400_init, + .ops = &snd_rpi_dacberry400_ops, + .symmetric_rate = 1, + SND_SOC_DAILINK_REG(rpi_dacberry400), +}, +}; + +static struct snd_soc_card snd_rpi_dacberry400 = { + .owner = THIS_MODULE, + .dai_link = snd_rpi_dacberry400_dai, + .num_links = ARRAY_SIZE(snd_rpi_dacberry400_dai), + .controls = dacberry400_controls, + .num_controls = ARRAY_SIZE(dacberry400_controls), + .dapm_widgets = dacberry400_widgets, + .num_dapm_widgets = ARRAY_SIZE(dacberry400_widgets), + .dapm_routes = dacberry400_audio_map, + .num_dapm_routes = ARRAY_SIZE(dacberry400_audio_map), + .set_bias_level = snd_rpi_dacberry400_set_bias_level, +}; + +static int snd_rpi_dacberry400_probe(struct platform_device *pdev) +{ + int ret = 0; + + snd_rpi_dacberry400.dev = &pdev->dev; + + if (pdev->dev.of_node) { + struct device_node *i2s_node; + struct snd_soc_card *card = &snd_rpi_dacberry400; + struct snd_soc_dai_link *dai = &snd_rpi_dacberry400_dai[0]; + + i2s_node = of_parse_phandle(pdev->dev.of_node, + "i2s-controller", 0); + if (i2s_node) { + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + of_node_put(i2s_node); + } + + if (of_property_read_string(pdev->dev.of_node, "card_name", + &card->name)) + card->name = "tlvaudioCODEC"; + + if (of_property_read_string(pdev->dev.of_node, "dai_name", + &dai->name)) + dai->name = "tlvaudio CODEC"; + + } + + ret = snd_soc_register_card(&snd_rpi_dacberry400); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(&pdev->dev, + "snd_soc_register_card() failed: %d\n", ret); + return ret; + } + + return 0; +} + +static void snd_rpi_dacberry400_remove(struct platform_device *pdev) +{ + snd_soc_unregister_card(&snd_rpi_dacberry400); +} + +static const struct of_device_id dacberry400_match_id[] = { + { .compatible = "osaelectronics,dacberry400",}, + {}, +}; +MODULE_DEVICE_TABLE(of, dacberry400_match_id); + +static struct platform_driver snd_rpi_dacberry400_driver = { + .driver = { + .name = "snd-rpi-dacberry400", + .owner = THIS_MODULE, + .of_match_table = dacberry400_match_id, + }, + .probe = snd_rpi_dacberry400_probe, + .remove = snd_rpi_dacberry400_remove, +}; + +module_platform_driver(snd_rpi_dacberry400_driver); + +MODULE_AUTHOR("Ashish Vara"); +MODULE_DESCRIPTION("Dacberry400 sound card driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:dacberry400"); +MODULE_SOFTDEP("pre: snd-soc-tlv320aic3x"); diff --git a/sound/soc/bcm/digidac1-soundcard.c b/sound/soc/bcm/digidac1-soundcard.c new file mode 100644 index 000000000000..320de17ffa3d --- /dev/null +++ b/sound/soc/bcm/digidac1-soundcard.c @@ -0,0 +1,421 @@ +/* + * ASoC Driver for RRA DigiDAC1 + * Copyright 2016 + * Author: José M. Tasende + * based on the HifiBerry DAC driver by Florian Meier + * and the Wolfson card driver by Nikesh Oswal, + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../codecs/wm8804.h" +#include "../codecs/wm8741.h" + +#define WM8741_NUM_SUPPLIES 2 + +/* codec private data */ +struct wm8741_priv { + struct wm8741_platform_data pdata; + struct regmap *regmap; + struct regulator_bulk_data supplies[WM8741_NUM_SUPPLIES]; + unsigned int sysclk; + const struct snd_pcm_hw_constraint_list *sysclk_constraints; +}; + +static int samplerate = 44100; + +/* New Alsa Controls not exposed by original wm8741 codec driver */ +/* in actual driver the att. adjustment is wrong because */ +/* this DAC has a coarse attenuation register with 4dB steps */ +/* and a fine level register with 0.125dB steps */ +/* each register has 32 steps so combining both we have 1024 steps */ +/* of 0.125 dB. */ +/* The original level controls from driver are removed at startup */ +/* and replaced by the corrected ones. */ +/* The same wm8741 driver can be used for wm8741 and wm8742 devices */ + +static const DECLARE_TLV_DB_SCALE(dac_tlv_fine, 0, 13, 0); +static const DECLARE_TLV_DB_SCALE(dac_tlv_coarse, -12700, 400, 1); +static const char *w8741_dither[4] = {"Off", "RPDF", "TPDF", "HPDF"}; +static const char *w8741_filter[5] = { + "Type 1", "Type 2", "Type 3", "Type 4", "Type 5"}; +static const char *w8741_switch[2] = {"Off", "On"}; +static const struct soc_enum w8741_enum[] = { +SOC_ENUM_SINGLE(WM8741_MODE_CONTROL_2, 0, 4, w8741_dither),/* dithering type */ +SOC_ENUM_SINGLE(WM8741_FILTER_CONTROL, 0, 5, w8741_filter),/* filter type */ +SOC_ENUM_SINGLE(WM8741_FORMAT_CONTROL, 6, 2, w8741_switch),/* phase invert */ +SOC_ENUM_SINGLE(WM8741_VOLUME_CONTROL, 0, 2, w8741_switch),/* volume ramp */ +SOC_ENUM_SINGLE(WM8741_VOLUME_CONTROL, 3, 2, w8741_switch),/* soft mute */ +}; + +static const struct snd_kcontrol_new w8741_snd_controls_stereo[] = { +SOC_DOUBLE_R_TLV("DAC Fine Playback Volume", WM8741_DACLLSB_ATTENUATION, + WM8741_DACRLSB_ATTENUATION, 0, 31, 1, dac_tlv_fine), +SOC_DOUBLE_R_TLV("Digital Playback Volume", WM8741_DACLMSB_ATTENUATION, + WM8741_DACRMSB_ATTENUATION, 0, 31, 1, dac_tlv_coarse), +SOC_ENUM("DAC Dither", w8741_enum[0]), +SOC_ENUM("DAC Digital Filter", w8741_enum[1]), +SOC_ENUM("DAC Phase Invert", w8741_enum[2]), +SOC_ENUM("DAC Volume Ramp", w8741_enum[3]), +SOC_ENUM("DAC Soft Mute", w8741_enum[4]), +}; + +static const struct snd_kcontrol_new w8741_snd_controls_mono_left[] = { +SOC_SINGLE_TLV("DAC Fine Playback Volume", WM8741_DACLLSB_ATTENUATION, + 0, 31, 0, dac_tlv_fine), +SOC_SINGLE_TLV("Digital Playback Volume", WM8741_DACLMSB_ATTENUATION, + 0, 31, 1, dac_tlv_coarse), +SOC_ENUM("DAC Dither", w8741_enum[0]), +SOC_ENUM("DAC Digital Filter", w8741_enum[1]), +SOC_ENUM("DAC Phase Invert", w8741_enum[2]), +SOC_ENUM("DAC Volume Ramp", w8741_enum[3]), +SOC_ENUM("DAC Soft Mute", w8741_enum[4]), +}; + +static const struct snd_kcontrol_new w8741_snd_controls_mono_right[] = { +SOC_SINGLE_TLV("DAC Fine Playback Volume", WM8741_DACRLSB_ATTENUATION, + 0, 31, 0, dac_tlv_fine), +SOC_SINGLE_TLV("Digital Playback Volume", WM8741_DACRMSB_ATTENUATION, + 0, 31, 1, dac_tlv_coarse), +SOC_ENUM("DAC Dither", w8741_enum[0]), +SOC_ENUM("DAC Digital Filter", w8741_enum[1]), +SOC_ENUM("DAC Phase Invert", w8741_enum[2]), +SOC_ENUM("DAC Volume Ramp", w8741_enum[3]), +SOC_ENUM("DAC Soft Mute", w8741_enum[4]), +}; + +static int w8741_add_controls(struct snd_soc_component *component) +{ + struct wm8741_priv *wm8741 = snd_soc_component_get_drvdata(component); + + switch (wm8741->pdata.diff_mode) { + case WM8741_DIFF_MODE_STEREO: + case WM8741_DIFF_MODE_STEREO_REVERSED: + snd_soc_add_component_controls(component, + w8741_snd_controls_stereo, + ARRAY_SIZE(w8741_snd_controls_stereo)); + break; + case WM8741_DIFF_MODE_MONO_LEFT: + snd_soc_add_component_controls(component, + w8741_snd_controls_mono_left, + ARRAY_SIZE(w8741_snd_controls_mono_left)); + break; + case WM8741_DIFF_MODE_MONO_RIGHT: + snd_soc_add_component_controls(component, + w8741_snd_controls_mono_right, + ARRAY_SIZE(w8741_snd_controls_mono_right)); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int digidac1_soundcard_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + struct snd_soc_card *card = rtd->card; + struct snd_soc_pcm_runtime *wm8741_rtd; + struct snd_soc_component *wm8741_component; + struct snd_card *sound_card = card->snd_card; + struct snd_kcontrol *kctl; + int ret; + + wm8741_rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[1]); + if (!wm8741_rtd) { + dev_warn(card->dev, "digidac1_soundcard_init: couldn't get wm8741 rtd\n"); + return -EFAULT; + } + wm8741_component = snd_soc_rtd_to_codec(wm8741_rtd, 0)->component; + ret = w8741_add_controls(wm8741_component); + if (ret < 0) + dev_warn(card->dev, "Failed to add new wm8741 controls: %d\n", + ret); + + /* enable TX output */ + snd_soc_component_update_bits(component, WM8804_PWRDN, 0x4, 0x0); + + kctl = snd_soc_card_get_kcontrol(card, + "Playback Volume"); + if (kctl) { + kctl->vd[0].access = SNDRV_CTL_ELEM_ACCESS_READWRITE; + snd_ctl_remove(sound_card, kctl); + } + kctl = snd_soc_card_get_kcontrol(card, + "Fine Playback Volume"); + if (kctl) { + kctl->vd[0].access = SNDRV_CTL_ELEM_ACCESS_READWRITE; + snd_ctl_remove(sound_card, kctl); + } + return 0; +} + +static int digidac1_soundcard_startup(struct snd_pcm_substream *substream) +{ + /* turn on wm8804 digital output */ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + struct snd_soc_card *card = rtd->card; + struct snd_soc_pcm_runtime *wm8741_rtd; + struct snd_soc_component *wm8741_component; + + snd_soc_component_update_bits(component, WM8804_PWRDN, 0x3c, 0x00); + wm8741_rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[1]); + if (!wm8741_rtd) { + dev_warn(card->dev, "digidac1_soundcard_startup: couldn't get WM8741 rtd\n"); + return -EFAULT; + } + wm8741_component = snd_soc_rtd_to_codec(wm8741_rtd, 0)->component; + + /* latch wm8741 level */ + snd_soc_component_update_bits(wm8741_component, WM8741_DACLLSB_ATTENUATION, + WM8741_UPDATELL, WM8741_UPDATELL); + snd_soc_component_update_bits(wm8741_component, WM8741_DACLMSB_ATTENUATION, + WM8741_UPDATELM, WM8741_UPDATELM); + snd_soc_component_update_bits(wm8741_component, WM8741_DACRLSB_ATTENUATION, + WM8741_UPDATERL, WM8741_UPDATERL); + snd_soc_component_update_bits(wm8741_component, WM8741_DACRMSB_ATTENUATION, + WM8741_UPDATERM, WM8741_UPDATERM); + + return 0; +} + +static void digidac1_soundcard_shutdown(struct snd_pcm_substream *substream) +{ + /* turn off wm8804 digital output */ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + + snd_soc_component_update_bits(component, WM8804_PWRDN, 0x3c, 0x3c); +} + +static int digidac1_soundcard_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + struct snd_soc_card *card = rtd->card; + struct snd_soc_pcm_runtime *wm8741_rtd; + struct snd_soc_component *wm8741_component; + + int sysclk = 27000000; + long mclk_freq = 0; + int mclk_div = 1; + int sampling_freq = 1; + int ret; + + wm8741_rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[1]); + if (!wm8741_rtd) { + dev_warn(card->dev, "digidac1_soundcard_hw_params: couldn't get WM8741 rtd\n"); + return -EFAULT; + } + wm8741_component = snd_soc_rtd_to_codec(wm8741_rtd, 0)->component; + samplerate = params_rate(params); + + if (samplerate <= 96000) { + mclk_freq = samplerate*256; + mclk_div = WM8804_MCLKDIV_256FS; + } else { + mclk_freq = samplerate*128; + mclk_div = WM8804_MCLKDIV_128FS; + } + + switch (samplerate) { + case 32000: + sampling_freq = 0x03; + break; + case 44100: + sampling_freq = 0x00; + break; + case 48000: + sampling_freq = 0x02; + break; + case 88200: + sampling_freq = 0x08; + break; + case 96000: + sampling_freq = 0x0a; + break; + case 176400: + sampling_freq = 0x0c; + break; + case 192000: + sampling_freq = 0x0e; + break; + default: + dev_err(card->dev, + "Failed to set WM8804 SYSCLK, unsupported samplerate %d\n", + samplerate); + } + + snd_soc_dai_set_clkdiv(codec_dai, WM8804_MCLK_DIV, mclk_div); + snd_soc_dai_set_pll(codec_dai, 0, 0, sysclk, mclk_freq); + + ret = snd_soc_dai_set_sysclk(codec_dai, WM8804_TX_CLKSRC_PLL, + sysclk, SND_SOC_CLOCK_OUT); + if (ret < 0) { + dev_err(card->dev, + "Failed to set WM8804 SYSCLK: %d\n", ret); + return ret; + } + /* Enable wm8804 TX output */ + snd_soc_component_update_bits(component, WM8804_PWRDN, 0x4, 0x0); + + /* wm8804 Power on */ + snd_soc_component_update_bits(component, WM8804_PWRDN, 0x9, 0); + + /* wm8804 set sampling frequency status bits */ + snd_soc_component_update_bits(component, WM8804_SPDTX4, 0x0f, sampling_freq); + + /* Now update wm8741 registers for the correct oversampling */ + if (samplerate <= 48000) + snd_soc_component_update_bits(wm8741_component, WM8741_MODE_CONTROL_1, + WM8741_OSR_MASK, 0x00); + else if (samplerate <= 96000) + snd_soc_component_update_bits(wm8741_component, WM8741_MODE_CONTROL_1, + WM8741_OSR_MASK, 0x20); + else + snd_soc_component_update_bits(wm8741_component, WM8741_MODE_CONTROL_1, + WM8741_OSR_MASK, 0x40); + + /* wm8741 bit size */ + switch (params_width(params)) { + case 16: + snd_soc_component_update_bits(wm8741_component, WM8741_FORMAT_CONTROL, + WM8741_IWL_MASK, 0x00); + break; + case 20: + snd_soc_component_update_bits(wm8741_component, WM8741_FORMAT_CONTROL, + WM8741_IWL_MASK, 0x01); + break; + case 24: + snd_soc_component_update_bits(wm8741_component, WM8741_FORMAT_CONTROL, + WM8741_IWL_MASK, 0x02); + break; + case 32: + snd_soc_component_update_bits(wm8741_component, WM8741_FORMAT_CONTROL, + WM8741_IWL_MASK, 0x03); + break; + default: + dev_dbg(card->dev, "wm8741_hw_params: Unsupported bit size param = %d", + params_width(params)); + return -EINVAL; + } + + return snd_soc_dai_set_bclk_ratio(cpu_dai, 64); +} +/* machine stream operations */ +static struct snd_soc_ops digidac1_soundcard_ops = { + .hw_params = digidac1_soundcard_hw_params, + .startup = digidac1_soundcard_startup, + .shutdown = digidac1_soundcard_shutdown, +}; + +SND_SOC_DAILINK_DEFS(digidac1, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), + DAILINK_COMP_ARRAY(COMP_CODEC("wm8804.1-003b", "wm8804-spdif")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2835-i2s.0"))); + +SND_SOC_DAILINK_DEFS(digidac11, + DAILINK_COMP_ARRAY(COMP_CPU("wm8804-spdif")), + DAILINK_COMP_ARRAY(COMP_CODEC("wm8741.1-001a", "wm8741")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link digidac1_soundcard_dai[] = { + { + .name = "RRA DigiDAC1", + .stream_name = "RRA DigiDAC1 HiFi", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBP_CFP, + .ops = &digidac1_soundcard_ops, + .init = digidac1_soundcard_init, + SND_SOC_DAILINK_REG(digidac1), + }, + { + .name = "RRA DigiDAC11", + .stream_name = "RRA DigiDAC11 HiFi", + .dai_fmt = SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBC_CFC, + SND_SOC_DAILINK_REG(digidac11), + }, +}; + +/* audio machine driver */ +static struct snd_soc_card digidac1_soundcard = { + .name = "digidac1-soundcard", + .owner = THIS_MODULE, + .dai_link = digidac1_soundcard_dai, + .num_links = ARRAY_SIZE(digidac1_soundcard_dai), +}; + +static int digidac1_soundcard_probe(struct platform_device *pdev) +{ + int ret = 0; + + digidac1_soundcard.dev = &pdev->dev; + + if (pdev->dev.of_node) { + struct device_node *i2s_node; + struct snd_soc_dai_link *dai = &digidac1_soundcard_dai[0]; + + i2s_node = of_parse_phandle(pdev->dev.of_node, + "i2s-controller", 0); + + if (i2s_node) { + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + } + } + + ret = devm_snd_soc_register_card(&pdev->dev, &digidac1_soundcard); + if (ret && ret != -EPROBE_DEFER) + dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", + ret); + + return ret; +} + +static const struct of_device_id digidac1_soundcard_of_match[] = { + { .compatible = "rra,digidac1-soundcard", }, + {}, +}; +MODULE_DEVICE_TABLE(of, digidac1_soundcard_of_match); + +static struct platform_driver digidac1_soundcard_driver = { + .driver = { + .name = "digidac1-audio", + .owner = THIS_MODULE, + .of_match_table = digidac1_soundcard_of_match, + }, + .probe = digidac1_soundcard_probe, +}; + +module_platform_driver(digidac1_soundcard_driver); + +MODULE_AUTHOR("José M. Tasende "); +MODULE_DESCRIPTION("ASoC Driver for RRA DigiDAC1"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/dionaudio_loco-v2.c b/sound/soc/bcm/dionaudio_loco-v2.c new file mode 100644 index 000000000000..81506325e07f --- /dev/null +++ b/sound/soc/bcm/dionaudio_loco-v2.c @@ -0,0 +1,118 @@ +/* + * ASoC Driver for Dion Audio LOCO-V2 DAC-AMP + * + * Author: Miquel Blauw + * Copyright 2017 + * + * Based on the software of the RPi-DAC writen by Florian Meier + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include + +#include +#include +#include +#include +#include + +static bool digital_gain_0db_limit = true; + +static int snd_rpi_dionaudio_loco_v2_init(struct snd_soc_pcm_runtime *rtd) +{ + if (digital_gain_0db_limit) { + int ret; + struct snd_soc_card *card = rtd->card; + + ret = snd_soc_limit_volume(card, "Digital Playback Volume", 207); + if (ret < 0) + dev_warn(card->dev, "Failed to set volume limit: %d\n", ret); + } + + return 0; +} + +SND_SOC_DAILINK_DEFS(dionaudio_loco_v2, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), + DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004d", "pcm512x-hifi")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0"))); + +static struct snd_soc_dai_link snd_rpi_dionaudio_loco_v2_dai[] = { +{ + .name = "DionAudio LOCO-V2", + .stream_name = "DionAudio LOCO-V2 DAC-AMP", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBC_CFC, + .init = snd_rpi_dionaudio_loco_v2_init, + SND_SOC_DAILINK_REG(dionaudio_loco_v2), +},}; + +/* audio machine driver */ +static struct snd_soc_card snd_rpi_dionaudio_loco_v2 = { + .name = "Dion Audio LOCO-V2", + .owner = THIS_MODULE, + .dai_link = snd_rpi_dionaudio_loco_v2_dai, + .num_links = ARRAY_SIZE(snd_rpi_dionaudio_loco_v2_dai), +}; + +static int snd_rpi_dionaudio_loco_v2_probe(struct platform_device *pdev) +{ + int ret = 0; + + snd_rpi_dionaudio_loco_v2.dev = &pdev->dev; + + if (pdev->dev.of_node) { + struct device_node *i2s_node; + struct snd_soc_dai_link *dai = + &snd_rpi_dionaudio_loco_v2_dai[0]; + + i2s_node = of_parse_phandle(pdev->dev.of_node, + "i2s-controller", 0); + if (i2s_node) { + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + } + + digital_gain_0db_limit = !of_property_read_bool( + pdev->dev.of_node, "dionaudio,24db_digital_gain"); + } + + ret = devm_snd_soc_register_card(&pdev->dev, &snd_rpi_dionaudio_loco_v2); + if (ret) + dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", + ret); + + return ret; +} + +static const struct of_device_id dionaudio_of_match[] = { + { .compatible = "dionaudio,dionaudio-loco-v2", }, + {}, +}; +MODULE_DEVICE_TABLE(of, dionaudio_of_match); + +static struct platform_driver snd_rpi_dionaudio_loco_v2_driver = { + .driver = { + .name = "snd-rpi-dionaudio-loco-v2", + .owner = THIS_MODULE, + .of_match_table = dionaudio_of_match, + }, + .probe = snd_rpi_dionaudio_loco_v2_probe, +}; + +module_platform_driver(snd_rpi_dionaudio_loco_v2_driver); + +MODULE_AUTHOR("Miquel Blauw "); +MODULE_DESCRIPTION("ASoC Driver for DionAudio LOCO-V2"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/dionaudio_loco.c b/sound/soc/bcm/dionaudio_loco.c new file mode 100644 index 000000000000..3082df7f886b --- /dev/null +++ b/sound/soc/bcm/dionaudio_loco.c @@ -0,0 +1,121 @@ +/* + * ASoC Driver for Dion Audio LOCO DAC-AMP + * + * Author: Miquel Blauw + * Copyright 2016 + * + * Based on the software of the RPi-DAC writen by Florian Meier + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include + +#include +#include +#include +#include +#include + +static int snd_rpi_dionaudio_loco_hw_params( + struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + + unsigned int sample_bits = + snd_pcm_format_width(params_format(params)); + + /* Using powers of 2 allows for an integer clock divisor */ + sample_bits = sample_bits <= 16 ? 16 : 32; + + return snd_soc_dai_set_bclk_ratio(cpu_dai, sample_bits * 2); +} + +/* machine stream operations */ +static struct snd_soc_ops snd_rpi_dionaudio_loco_ops = { + .hw_params = snd_rpi_dionaudio_loco_hw_params, +}; + +SND_SOC_DAILINK_DEFS(dionaudio_loco, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), + DAILINK_COMP_ARRAY(COMP_CODEC("pcm5102a-codec", "pcm5102a-hifi")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0"))); + +static struct snd_soc_dai_link snd_rpi_dionaudio_loco_dai[] = { +{ + .name = "DionAudio LOCO", + .stream_name = "DionAudio LOCO DAC-AMP", + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBC_CFC, + .ops = &snd_rpi_dionaudio_loco_ops, + SND_SOC_DAILINK_REG(dionaudio_loco), +}, +}; + +/* audio machine driver */ +static struct snd_soc_card snd_rpi_dionaudio_loco = { + .name = "snd_rpi_dionaudio_loco", + .owner = THIS_MODULE, + .dai_link = snd_rpi_dionaudio_loco_dai, + .num_links = ARRAY_SIZE(snd_rpi_dionaudio_loco_dai), +}; + +static int snd_rpi_dionaudio_loco_probe(struct platform_device *pdev) +{ + struct device_node *np; + int ret = 0; + + snd_rpi_dionaudio_loco.dev = &pdev->dev; + + np = pdev->dev.of_node; + if (np) { + struct snd_soc_dai_link *dai = &snd_rpi_dionaudio_loco_dai[0]; + struct device_node *i2s_np; + + i2s_np = of_parse_phandle(np, "i2s-controller", 0); + if (i2s_np) { + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_np; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_np; + } + } + + ret = devm_snd_soc_register_card(&pdev->dev, &snd_rpi_dionaudio_loco); + if (ret && ret != -EPROBE_DEFER) + dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", + ret); + + return ret; +} + +static const struct of_device_id snd_rpi_dionaudio_loco_of_match[] = { + { .compatible = "dionaudio,loco-pcm5242-tpa3118", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, snd_rpi_dionaudio_loco_of_match); + +static struct platform_driver snd_rpi_dionaudio_loco_driver = { + .driver = { + .name = "snd-dionaudio-loco", + .owner = THIS_MODULE, + .of_match_table = snd_rpi_dionaudio_loco_of_match, + }, + .probe = snd_rpi_dionaudio_loco_probe, +}; + +module_platform_driver(snd_rpi_dionaudio_loco_driver); + +MODULE_AUTHOR("Miquel Blauw "); +MODULE_DESCRIPTION("ASoC Driver for DionAudio LOCO"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/fe-pi-audio.c b/sound/soc/bcm/fe-pi-audio.c new file mode 100644 index 000000000000..ab66cfd25d4c --- /dev/null +++ b/sound/soc/bcm/fe-pi-audio.c @@ -0,0 +1,154 @@ +/* + * ASoC Driver for Fe-Pi Audio Sound Card + * + * Author: Henry Kupis + * Copyright 2016 + * based on code by Florian Meier + * based on code by Shawn Guo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "../codecs/sgtl5000.h" + +static int snd_fe_pi_audio_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + + snd_soc_dapm_force_enable_pin(&card->dapm, "LO"); + snd_soc_dapm_force_enable_pin(&card->dapm, "ADC"); + snd_soc_dapm_force_enable_pin(&card->dapm, "DAC"); + snd_soc_dapm_force_enable_pin(&card->dapm, "HP"); + snd_soc_component_update_bits(component, SGTL5000_CHIP_ANA_POWER, + SGTL5000_VAG_POWERUP, SGTL5000_VAG_POWERUP); + + return 0; +} + +static int snd_fe_pi_audio_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct device *dev = rtd->card->dev; + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + + int ret; + + /* Set SGTL5000's SYSCLK */ + ret = snd_soc_dai_set_sysclk(codec_dai, SGTL5000_SYSCLK, 12288000, SND_SOC_CLOCK_IN); + if (ret) { + dev_err(dev, "could not set codec driver clock params\n"); + return ret; + } + + return 0; +} + + +static struct snd_soc_ops snd_fe_pi_audio_ops = { + .hw_params = snd_fe_pi_audio_hw_params, +}; + +SND_SOC_DAILINK_DEFS(fe_pi, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), + DAILINK_COMP_ARRAY(COMP_CODEC("sgtl5000.1-000a", "sgtl5000")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0"))); + +static struct snd_soc_dai_link snd_fe_pi_audio_dai[] = { + { + .name = "FE-PI", + .stream_name = "Fe-Pi HiFi", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBP_CFP, + .ops = &snd_fe_pi_audio_ops, + .init = snd_fe_pi_audio_init, + SND_SOC_DAILINK_REG(fe_pi), + }, +}; + +static const struct snd_soc_dapm_route fe_pi_audio_dapm_routes[] = { + {"ADC", NULL, "Mic Bias"}, +}; + + +static struct snd_soc_card fe_pi_audio = { + .name = "Fe-Pi Audio", + .owner = THIS_MODULE, + .dai_link = snd_fe_pi_audio_dai, + .num_links = ARRAY_SIZE(snd_fe_pi_audio_dai), + + .dapm_routes = fe_pi_audio_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(fe_pi_audio_dapm_routes), +}; + +static int snd_fe_pi_audio_probe(struct platform_device *pdev) +{ + int ret = 0; + struct snd_soc_card *card = &fe_pi_audio; + struct device_node *np = pdev->dev.of_node; + struct device_node *i2s_node; + struct snd_soc_dai_link *dai = &snd_fe_pi_audio_dai[0]; + + fe_pi_audio.dev = &pdev->dev; + + i2s_node = of_parse_phandle(np, "i2s-controller", 0); + if (!i2s_node) { + dev_err(&pdev->dev, "i2s_node phandle missing or invalid\n"); + return -EINVAL; + } + + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + + of_node_put(i2s_node); + + card->dev = &pdev->dev; + platform_set_drvdata(pdev, card); + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret && ret != -EPROBE_DEFER) + dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", ret); + + return ret; +} + +static const struct of_device_id snd_fe_pi_audio_of_match[] = { + { .compatible = "fe-pi,fe-pi-audio", }, + {}, +}; +MODULE_DEVICE_TABLE(of, snd_fe_pi_audio_of_match); + +static struct platform_driver snd_fe_pi_audio_driver = { + .driver = { + .name = "snd-fe-pi-audio", + .owner = THIS_MODULE, + .of_match_table = snd_fe_pi_audio_of_match, + }, + .probe = snd_fe_pi_audio_probe, +}; + +module_platform_driver(snd_fe_pi_audio_driver); + +MODULE_AUTHOR("Henry Kupis "); +MODULE_DESCRIPTION("ASoC Driver for Fe-Pi Audio"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/googlevoicehat-codec.c b/sound/soc/bcm/googlevoicehat-codec.c new file mode 100644 index 000000000000..572db9373e63 --- /dev/null +++ b/sound/soc/bcm/googlevoicehat-codec.c @@ -0,0 +1,212 @@ +/* + * Driver for the Google voiceHAT audio codec for Raspberry Pi. + * + * Author: Peter Malkin + * Copyright 2016 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ICS43432_RATE_MIN_HZ 7190 /* from data sheet */ +#define ICS43432_RATE_MAX_HZ 52800 /* from data sheet */ +/* Delay in enabling SDMODE after clock settles to remove pop */ +#define SDMODE_DELAY_MS 5 + +struct voicehat_priv { + struct delayed_work enable_sdmode_work; + struct gpio_desc *sdmode_gpio; + unsigned long sdmode_delay_jiffies; +}; + +static void voicehat_enable_sdmode_work(struct work_struct *work) +{ + struct voicehat_priv *voicehat = container_of(work, + struct voicehat_priv, + enable_sdmode_work.work); + gpiod_set_value(voicehat->sdmode_gpio, 1); +} + +static int voicehat_component_probe(struct snd_soc_component *component) +{ + struct voicehat_priv *voicehat = + snd_soc_component_get_drvdata(component); + + voicehat->sdmode_gpio = devm_gpiod_get(component->dev, "sdmode", + GPIOD_OUT_LOW); + if (IS_ERR(voicehat->sdmode_gpio)) { + dev_err(component->dev, "Unable to allocate GPIO pin\n"); + return PTR_ERR(voicehat->sdmode_gpio); + } + + INIT_DELAYED_WORK(&voicehat->enable_sdmode_work, + voicehat_enable_sdmode_work); + return 0; +} + +static void voicehat_component_remove(struct snd_soc_component *component) +{ + struct voicehat_priv *voicehat = + snd_soc_component_get_drvdata(component); + + cancel_delayed_work_sync(&voicehat->enable_sdmode_work); +} + +static const struct snd_soc_dapm_widget voicehat_dapm_widgets[] = { + SND_SOC_DAPM_OUTPUT("Speaker"), +}; + +static const struct snd_soc_dapm_route voicehat_dapm_routes[] = { + {"Speaker", NULL, "HiFi Playback"}, +}; + +static const struct snd_soc_component_driver voicehat_component_driver = { + .probe = voicehat_component_probe, + .remove = voicehat_component_remove, + .dapm_widgets = voicehat_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(voicehat_dapm_widgets), + .dapm_routes = voicehat_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(voicehat_dapm_routes), +}; + +static int voicehat_daiops_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct voicehat_priv *voicehat = snd_soc_component_get_drvdata(component); + + if (voicehat->sdmode_delay_jiffies == 0) + return 0; + + dev_dbg(dai->dev, "CMD %d", cmd); + dev_dbg(dai->dev, "Playback Active %d", dai->stream[SNDRV_PCM_STREAM_PLAYBACK].active); + dev_dbg(dai->dev, "Capture Active %d", dai->stream[SNDRV_PCM_STREAM_CAPTURE].active); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + dev_info(dai->dev, "Enabling audio amp...\n"); + queue_delayed_work( + system_power_efficient_wq, + &voicehat->enable_sdmode_work, + voicehat->sdmode_delay_jiffies); + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + cancel_delayed_work(&voicehat->enable_sdmode_work); + dev_info(dai->dev, "Disabling audio amp...\n"); + gpiod_set_value(voicehat->sdmode_gpio, 0); + } + break; + } + return 0; +} + +static const struct snd_soc_dai_ops voicehat_dai_ops = { + .trigger = voicehat_daiops_trigger, +}; + +static struct snd_soc_dai_driver voicehat_dai = { + .name = "voicehat-hifi", + .capture = { + .stream_name = "HiFi Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S32_LE + }, + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S32_LE + }, + .ops = &voicehat_dai_ops, + .symmetric_rate = 1 +}; + +#ifdef CONFIG_OF +static const struct of_device_id voicehat_ids[] = { + { .compatible = "google,voicehat", }, {} + }; + MODULE_DEVICE_TABLE(of, voicehat_ids); +#endif + +static int voicehat_platform_probe(struct platform_device *pdev) +{ + struct voicehat_priv *voicehat; + unsigned int sdmode_delay; + int ret; + + voicehat = devm_kzalloc(&pdev->dev, sizeof(*voicehat), GFP_KERNEL); + if (!voicehat) + return -ENOMEM; + + ret = device_property_read_u32(&pdev->dev, "voicehat_sdmode_delay", + &sdmode_delay); + + if (ret) { + sdmode_delay = SDMODE_DELAY_MS; + dev_info(&pdev->dev, + "property 'voicehat_sdmode_delay' not found default 5 mS"); + } else { + dev_info(&pdev->dev, "property 'voicehat_sdmode_delay' found delay= %d mS", + sdmode_delay); + } + voicehat->sdmode_delay_jiffies = msecs_to_jiffies(sdmode_delay); + + dev_set_drvdata(&pdev->dev, voicehat); + + return snd_soc_register_component(&pdev->dev, + &voicehat_component_driver, + &voicehat_dai, + 1); +} + +static void voicehat_platform_remove(struct platform_device *pdev) +{ + snd_soc_unregister_component(&pdev->dev); +} + +static struct platform_driver voicehat_driver = { + .driver = { + .name = "voicehat-codec", + .of_match_table = of_match_ptr(voicehat_ids), + }, + .probe = voicehat_platform_probe, + .remove = voicehat_platform_remove, +}; + +module_platform_driver(voicehat_driver); + +MODULE_DESCRIPTION("Google voiceHAT Codec driver"); +MODULE_AUTHOR("Peter Malkin "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/hifiberry_adc.c b/sound/soc/bcm/hifiberry_adc.c new file mode 100644 index 000000000000..45c642e056c1 --- /dev/null +++ b/sound/soc/bcm/hifiberry_adc.c @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ASoC Driver for HiFiBerry ADC + * + * Author: Joerg Schambacher + * Copyright 2024 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "../codecs/pcm186x.h" +#include "hifiberry_adc_controls.h" + +static bool leds_off; + +static int pcm1863_add_controls(struct snd_soc_component *component) +{ + snd_soc_add_component_controls(component, + pcm1863_snd_controls_card, + ARRAY_SIZE(pcm1863_snd_controls_card)); + return 0; +} + +static int snd_rpi_hifiberry_adc_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + struct snd_soc_component *adc = codec_dai->component; + int ret; + + ret = pcm1863_add_controls(adc); + if (ret < 0) + dev_warn(rtd->dev, "Failed to add pcm1863 controls: %d\n", + ret); + + codec_dai->driver->capture.rates = + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000; + + /* set GPIO2 to output, GPIO3 input */ + snd_soc_component_write(adc, PCM186X_GPIO3_2_CTRL, 0x00); + snd_soc_component_write(adc, PCM186X_GPIO3_2_DIR_CTRL, 0x04); + if (leds_off) + snd_soc_component_update_bits(adc, PCM186X_GPIO_IN_OUT, 0x40, 0x00); + else + snd_soc_component_update_bits(adc, PCM186X_GPIO_IN_OUT, 0x40, 0x40); + + return 0; +} + +static int snd_rpi_hifiberry_adc_hw_params( + struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) +{ + int ret = 0; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + int channels = params_channels(params); + int width = snd_pcm_format_width(params_format(params)); + + /* Using powers of 2 allows for an integer clock divisor */ + width = width <= 16 ? 16 : 32; + + ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_cpu(rtd, 0), channels * width); + return ret; +} + +/* machine stream operations */ +static const struct snd_soc_ops snd_rpi_hifiberry_adc_ops = { + .hw_params = snd_rpi_hifiberry_adc_hw_params, +}; + +SND_SOC_DAILINK_DEFS(hifi, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), + DAILINK_COMP_ARRAY(COMP_CODEC("pcm186x.1-004a", "pcm1863-aif")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0"))); + +static struct snd_soc_dai_link snd_rpi_hifiberry_adc_dai[] = { +{ + .name = "HiFiBerry ADC", + .stream_name = "HiFiBerry ADC HiFi", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBC_CFC, + .ops = &snd_rpi_hifiberry_adc_ops, + .init = snd_rpi_hifiberry_adc_init, + SND_SOC_DAILINK_REG(hifi), +}, +}; + +/* audio machine driver */ +static struct snd_soc_card snd_rpi_hifiberry_adc = { + .name = "snd_rpi_hifiberry_adc", + .driver_name = "HifiberryAdc", + .owner = THIS_MODULE, + .dai_link = snd_rpi_hifiberry_adc_dai, + .num_links = ARRAY_SIZE(snd_rpi_hifiberry_adc_dai), +}; + +static int snd_rpi_hifiberry_adc_probe(struct platform_device *pdev) +{ + int ret = 0, i = 0; + struct snd_soc_card *card = &snd_rpi_hifiberry_adc; + + snd_rpi_hifiberry_adc.dev = &pdev->dev; + if (pdev->dev.of_node) { + struct device_node *i2s_node; + struct snd_soc_dai_link *dai; + + dai = &snd_rpi_hifiberry_adc_dai[0]; + i2s_node = of_parse_phandle(pdev->dev.of_node, + "i2s-controller", 0); + if (i2s_node) { + for (i = 0; i < card->num_links; i++) { + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + } + } + } + leds_off = of_property_read_bool(pdev->dev.of_node, + "hifiberry-adc,leds_off"); + ret = snd_soc_register_card(&snd_rpi_hifiberry_adc); + if (ret && ret != -EPROBE_DEFER) + dev_err(&pdev->dev, + "snd_soc_register_card() failed: %d\n", ret); + + return ret; +} + +static const struct of_device_id snd_rpi_hifiberry_adc_of_match[] = { + { .compatible = "hifiberry,hifiberry-adc", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, snd_rpi_hifiberry_adc_of_match); + +static struct platform_driver snd_rpi_hifiberry_adc_driver = { + .driver = { + .name = "snd-rpi-hifiberry-adc", + .owner = THIS_MODULE, + .of_match_table = snd_rpi_hifiberry_adc_of_match, + }, + .probe = snd_rpi_hifiberry_adc_probe, +}; + +module_platform_driver(snd_rpi_hifiberry_adc_driver); + +MODULE_AUTHOR("Joerg Schambacher "); +MODULE_DESCRIPTION("ASoC Driver for HiFiBerry ADC"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/bcm/hifiberry_adc_controls.h b/sound/soc/bcm/hifiberry_adc_controls.h new file mode 100644 index 000000000000..8d8911bb0afb --- /dev/null +++ b/sound/soc/bcm/hifiberry_adc_controls.h @@ -0,0 +1,128 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * ALSA mixer/Kcontrol definitions common to HiFiBerry ADCs + * + * used by DAC+ADC Pro (hifiberry_dacplusadcpro.c), + * ADC (hifiberry_adc.c) + * + * Author: Joerg Schambacher + * Copyright 2024 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +static const unsigned int pcm186x_adc_input_channel_sel_value[] = { + 0x00, 0x01, 0x02, 0x03, 0x10 +}; + +static const char * const pcm186x_adcl_input_channel_sel_text[] = { + "No Select", + "VINL1[SE]", /* Default for ADCL */ + "VINL2[SE]", + "VINL2[SE] + VINL1[SE]", + "{VIN1P, VIN1M}[DIFF]" +}; + +static const char * const pcm186x_adcr_input_channel_sel_text[] = { + "No Select", + "VINR1[SE]", /* Default for ADCR */ + "VINR2[SE]", + "VINR2[SE] + VINR1[SE]", + "{VIN2P, VIN2M}[DIFF]" +}; + +static const struct soc_enum pcm186x_adc_input_channel_sel[] = { + SOC_VALUE_ENUM_SINGLE(PCM186X_ADC1_INPUT_SEL_L, 0, + PCM186X_ADC_INPUT_SEL_MASK, + ARRAY_SIZE(pcm186x_adcl_input_channel_sel_text), + pcm186x_adcl_input_channel_sel_text, + pcm186x_adc_input_channel_sel_value), + SOC_VALUE_ENUM_SINGLE(PCM186X_ADC1_INPUT_SEL_R, 0, + PCM186X_ADC_INPUT_SEL_MASK, + ARRAY_SIZE(pcm186x_adcr_input_channel_sel_text), + pcm186x_adcr_input_channel_sel_text, + pcm186x_adc_input_channel_sel_value), +}; + +static const unsigned int pcm186x_mic_bias_sel_value[] = { + 0x00, 0x01, 0x11 +}; + +static const char * const pcm186x_mic_bias_sel_text[] = { + "Mic Bias off", + "Mic Bias on", + "Mic Bias with Bypass Resistor" +}; + +static const struct soc_enum pcm186x_mic_bias_sel[] = { + SOC_VALUE_ENUM_SINGLE(PCM186X_MIC_BIAS_CTRL, 0, + GENMASK(4, 0), + ARRAY_SIZE(pcm186x_mic_bias_sel_text), + pcm186x_mic_bias_sel_text, + pcm186x_mic_bias_sel_value), +}; + +static const unsigned int pcm186x_gain_sel_value[] = { + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50 +}; + +static const char * const pcm186x_gain_sel_text[] = { + "-12.0dB", "-11.5dB", "-11.0dB", "-10.5dB", "-10.0dB", "-9.5dB", + "-9.0dB", "-8.5dB", "-8.0dB", "-7.5dB", "-7.0dB", "-6.5dB", + "-6.0dB", "-5.5dB", "-5.0dB", "-4.5dB", "-4.0dB", "-3.5dB", + "-3.0dB", "-2.5dB", "-2.0dB", "-1.5dB", "-1.0dB", "-0.5dB", + "0.0dB", "0.5dB", "1.0dB", "1.5dB", "2.0dB", "2.5dB", + "3.0dB", "3.5dB", "4.0dB", "4.5dB", "5.0dB", "5.5dB", + "6.0dB", "6.5dB", "7.0dB", "7.5dB", "8.0dB", "8.5dB", + "9.0dB", "9.5dB", "10.0dB", "10.5dB", "11.0dB", "11.5dB", + "12.0dB", "12.5dB", "13.0dB", "13.5dB", "14.0dB", "14.5dB", + "15.0dB", "15.5dB", "16.0dB", "16.5dB", "17.0dB", "17.5dB", + "18.0dB", "18.5dB", "19.0dB", "19.5dB", "20.0dB", "20.5dB", + "21.0dB", "21.5dB", "22.0dB", "22.5dB", "23.0dB", "23.5dB", + "24.0dB", "24.5dB", "25.0dB", "25.5dB", "26.0dB", "26.5dB", + "27.0dB", "27.5dB", "28.0dB", "28.5dB", "29.0dB", "29.5dB", + "30.0dB", "30.5dB", "31.0dB", "31.5dB", "32.0dB", "32.5dB", + "33.0dB", "33.5dB", "34.0dB", "34.5dB", "35.0dB", "35.5dB", + "36.0dB", "36.5dB", "37.0dB", "37.5dB", "38.0dB", "38.5dB", + "39.0dB", "39.5dB", "40.0dB"}; + +static const struct soc_enum pcm186x_gain_sel[] = { + SOC_VALUE_ENUM_SINGLE(PCM186X_PGA_VAL_CH1_L, 0, + 0xff, + ARRAY_SIZE(pcm186x_gain_sel_text), + pcm186x_gain_sel_text, + pcm186x_gain_sel_value), + SOC_VALUE_ENUM_SINGLE(PCM186X_PGA_VAL_CH1_R, 0, + 0xff, + ARRAY_SIZE(pcm186x_gain_sel_text), + pcm186x_gain_sel_text, + pcm186x_gain_sel_value), +}; + +static const struct snd_kcontrol_new pcm1863_snd_controls_card[] = { + SOC_ENUM("ADC Left Input", pcm186x_adc_input_channel_sel[0]), + SOC_ENUM("ADC Right Input", pcm186x_adc_input_channel_sel[1]), + SOC_ENUM("ADC Mic Bias", pcm186x_mic_bias_sel), + SOC_ENUM("PGA Gain Left", pcm186x_gain_sel[0]), + SOC_ENUM("PGA Gain Right", pcm186x_gain_sel[1]), +}; diff --git a/sound/soc/bcm/hifiberry_dacplus.c b/sound/soc/bcm/hifiberry_dacplus.c new file mode 100644 index 000000000000..e1335d306b27 --- /dev/null +++ b/sound/soc/bcm/hifiberry_dacplus.c @@ -0,0 +1,563 @@ +/* + * ASoC Driver for HiFiBerry DAC+ / DAC Pro / AMP100 + * + * Author: Daniel Matuschek, Stuart MacLean + * Copyright 2014-2015 + * based on code by Florian Meier + * Headphone/AMP100 Joerg Schambacher + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include <../drivers/gpio/gpiolib.h> +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "../codecs/pcm512x.h" + +#define HIFIBERRY_DACPRO_NOCLOCK 0 +#define HIFIBERRY_DACPRO_CLK44EN 1 +#define HIFIBERRY_DACPRO_CLK48EN 2 + +struct pcm512x_priv { + struct regmap *regmap; + struct clk *sclk; +}; + +/* Clock rate of CLK44EN attached to GPIO6 pin */ +#define CLK_44EN_RATE 22579200UL +/* Clock rate of CLK48EN attached to GPIO3 pin */ +#define CLK_48EN_RATE 24576000UL + +static bool slave; +static bool snd_rpi_hifiberry_is_dacpro; +static bool digital_gain_0db_limit = true; +static bool leds_off; +static bool auto_mute; +static int mute_ext_ctl; +static int mute_ext; +static bool tas_device; +static struct gpio_desc *snd_mute_gpio; +static struct gpio_desc *snd_reset_gpio; +static struct snd_soc_card snd_rpi_hifiberry_dacplus; + +static const u32 master_dai_rates[] = { + 44100, 48000, 88200, 96000, + 176400, 192000, 352800, 384000, +}; + +static const struct snd_pcm_hw_constraint_list constraints_master = { + .count = ARRAY_SIZE(master_dai_rates), + .list = master_dai_rates, +}; + +static int snd_rpi_hifiberry_dacplus_mute_set(int mute) +{ + gpiod_set_value_cansleep(snd_mute_gpio, mute); + return 1; +} + +static int snd_rpi_hifiberry_dacplus_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = mute_ext; + + return 0; +} + +static int snd_rpi_hifiberry_dacplus_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + if (mute_ext == ucontrol->value.integer.value[0]) + return 0; + + mute_ext = ucontrol->value.integer.value[0]; + + return snd_rpi_hifiberry_dacplus_mute_set(mute_ext); +} + +static const char * const mute_text[] = {"Play", "Mute"}; +static const struct soc_enum hb_dacplus_opt_mute_enum = + SOC_ENUM_SINGLE_EXT(2, mute_text); + +static const struct snd_kcontrol_new hb_dacplus_opt_mute_controls[] = { + SOC_ENUM_EXT("Mute(ext)", hb_dacplus_opt_mute_enum, + snd_rpi_hifiberry_dacplus_mute_get, + snd_rpi_hifiberry_dacplus_mute_put), +}; + +static void snd_rpi_hifiberry_dacplus_select_clk(struct snd_soc_component *component, + int clk_id) +{ + switch (clk_id) { + case HIFIBERRY_DACPRO_NOCLOCK: + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x00); + break; + case HIFIBERRY_DACPRO_CLK44EN: + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x20); + break; + case HIFIBERRY_DACPRO_CLK48EN: + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x04); + break; + } + usleep_range(3000, 4000); +} + +static void snd_rpi_hifiberry_dacplus_clk_gpio(struct snd_soc_component *component) +{ + snd_soc_component_update_bits(component, PCM512x_GPIO_EN, 0x24, 0x24); + snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_3, 0x0f, 0x02); + snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_6, 0x0f, 0x02); +} + +static bool snd_rpi_hifiberry_dacplus_is_sclk(struct snd_soc_component *component) +{ + unsigned int sck; + + sck = snd_soc_component_read(component, PCM512x_RATE_DET_4); + return (!(sck & 0x40)); +} + +static bool snd_rpi_hifiberry_dacplus_is_pro_card(struct snd_soc_component *component) +{ + bool isClk44EN, isClk48En, isNoClk; + + snd_rpi_hifiberry_dacplus_clk_gpio(component); + + snd_rpi_hifiberry_dacplus_select_clk(component, HIFIBERRY_DACPRO_CLK44EN); + isClk44EN = snd_rpi_hifiberry_dacplus_is_sclk(component); + + snd_rpi_hifiberry_dacplus_select_clk(component, HIFIBERRY_DACPRO_NOCLOCK); + isNoClk = snd_rpi_hifiberry_dacplus_is_sclk(component); + + snd_rpi_hifiberry_dacplus_select_clk(component, HIFIBERRY_DACPRO_CLK48EN); + isClk48En = snd_rpi_hifiberry_dacplus_is_sclk(component); + + return (isClk44EN && isClk48En && !isNoClk); +} + +static int snd_rpi_hifiberry_dacplus_clk_for_rate(int sample_rate) +{ + int type; + + switch (sample_rate) { + case 11025: + case 22050: + case 44100: + case 88200: + case 176400: + case 352800: + type = HIFIBERRY_DACPRO_CLK44EN; + break; + default: + type = HIFIBERRY_DACPRO_CLK48EN; + break; + } + return type; +} + +static void snd_rpi_hifiberry_dacplus_set_sclk(struct snd_soc_component *component, + int sample_rate) +{ + struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component); + + if (!IS_ERR(pcm512x->sclk)) { + int ctype; + + ctype = snd_rpi_hifiberry_dacplus_clk_for_rate(sample_rate); + clk_set_rate(pcm512x->sclk, (ctype == HIFIBERRY_DACPRO_CLK44EN) + ? CLK_44EN_RATE : CLK_48EN_RATE); + snd_rpi_hifiberry_dacplus_select_clk(component, ctype); + } +} + +static int snd_rpi_hifiberry_dacplus_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + struct pcm512x_priv *priv; + struct snd_soc_card *card = &snd_rpi_hifiberry_dacplus; + + if (slave) + snd_rpi_hifiberry_is_dacpro = false; + else + snd_rpi_hifiberry_is_dacpro = + snd_rpi_hifiberry_dacplus_is_pro_card(component); + + if (snd_rpi_hifiberry_is_dacpro) { + struct snd_soc_dai_link *dai = rtd->dai_link; + + if (tas_device) { + dai->name = "HiFiBerry AMP4 Pro"; + dai->stream_name = "HiFiBerry AMP4 Pro HiFi"; + } else { + dai->name = "HiFiBerry DAC+ Pro"; + dai->stream_name = "HiFiBerry DAC+ Pro HiFi"; + } + dai->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBP_CFP; + + snd_soc_component_update_bits(component, PCM512x_BCLK_LRCLK_CFG, 0x31, 0x11); + snd_soc_component_update_bits(component, PCM512x_MASTER_MODE, 0x03, 0x03); + snd_soc_component_update_bits(component, PCM512x_MASTER_CLKDIV_2, 0x7f, 63); + } else { + priv = snd_soc_component_get_drvdata(component); + priv->sclk = ERR_PTR(-ENOENT); + } + + snd_soc_component_update_bits(component, PCM512x_GPIO_EN, 0x08, 0x08); + snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_4, 0x0f, 0x02); + if (leds_off) + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x00); + else + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x08); + + if (digital_gain_0db_limit) { + int ret; + struct snd_soc_card *card = rtd->card; + + ret = snd_soc_limit_volume(card, "Digital Playback Volume", 207); + if (ret < 0) + dev_warn(card->dev, "Failed to set volume limit: %d\n", ret); + } + if (snd_reset_gpio) { + gpiod_set_value_cansleep(snd_reset_gpio, 0); + msleep(1); + gpiod_set_value_cansleep(snd_reset_gpio, 1); + msleep(1); + gpiod_set_value_cansleep(snd_reset_gpio, 0); + } + + if (mute_ext_ctl) + snd_soc_add_card_controls(card, hb_dacplus_opt_mute_controls, + ARRAY_SIZE(hb_dacplus_opt_mute_controls)); + + if (snd_mute_gpio) + gpiod_set_value_cansleep(snd_mute_gpio, mute_ext); + + return 0; +} + +static int snd_rpi_hifiberry_dacplus_update_rate_den( + struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component); + struct snd_ratnum *rats_no_pll; + unsigned int num = 0, den = 0; + int err; + + rats_no_pll = devm_kzalloc(rtd->dev, sizeof(*rats_no_pll), GFP_KERNEL); + if (!rats_no_pll) + return -ENOMEM; + + rats_no_pll->num = clk_get_rate(pcm512x->sclk) / 64; + rats_no_pll->den_min = 1; + rats_no_pll->den_max = 128; + rats_no_pll->den_step = 1; + + err = snd_interval_ratnum(hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE), 1, rats_no_pll, &num, &den); + if (err >= 0 && den) { + params->rate_num = num; + params->rate_den = den; + } + + devm_kfree(rtd->dev, rats_no_pll); + return 0; +} + +static int snd_rpi_hifiberry_dacplus_hw_params( + struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) +{ + int ret = 0; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + int channels = params_channels(params); + int width = snd_pcm_format_width(params_format(params)); + + /* Using powers of 2 allows for an integer clock divisor */ + width = width <= 16 ? 16 : 32; + + if (snd_rpi_hifiberry_is_dacpro) { + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + + snd_rpi_hifiberry_dacplus_set_sclk(component, + params_rate(params)); + + ret = snd_rpi_hifiberry_dacplus_update_rate_den( + substream, params); + } + + ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_cpu(rtd, 0), channels * width); + if (ret) + return ret; + ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_codec(rtd, 0), channels * width); + return ret; +} + +static int snd_rpi_hifiberry_dacplus_startup( + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + int ret; + + if (tas_device && !slave) { + ret = snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_master); + if (ret < 0) { + dev_err(rtd->card->dev, + "Cannot apply constraints for sample rates\n"); + return ret; + } + } + + if (auto_mute) + gpiod_set_value_cansleep(snd_mute_gpio, 0); + if (leds_off) + return 0; + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x08); + return 0; +} + +static void snd_rpi_hifiberry_dacplus_shutdown( + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x00); + if (auto_mute) + gpiod_set_value_cansleep(snd_mute_gpio, 1); +} + +/* machine stream operations */ +static const struct snd_soc_ops snd_rpi_hifiberry_dacplus_ops = { + .hw_params = snd_rpi_hifiberry_dacplus_hw_params, + .startup = snd_rpi_hifiberry_dacplus_startup, + .shutdown = snd_rpi_hifiberry_dacplus_shutdown, +}; + +SND_SOC_DAILINK_DEFS(rpi_hifiberry_dacplus, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), + DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004d", "pcm512x-hifi")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0"))); + +static struct snd_soc_dai_link snd_rpi_hifiberry_dacplus_dai[] = { +{ + .name = "HiFiBerry DAC+", + .stream_name = "HiFiBerry DAC+ HiFi", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBC_CFC, + .ops = &snd_rpi_hifiberry_dacplus_ops, + .init = snd_rpi_hifiberry_dacplus_init, + SND_SOC_DAILINK_REG(rpi_hifiberry_dacplus), +}, +}; + +/* aux device for optional headphone amp */ +static struct snd_soc_aux_dev hifiberry_dacplus_aux_devs[] = { + { + .dlc = { + .name = "tpa6130a2.1-0060", + }, + }, +}; + +/* audio machine driver */ +static struct snd_soc_card snd_rpi_hifiberry_dacplus = { + .name = "snd_rpi_hifiberry_dacplus", + .driver_name = "HifiberryDacp", + .owner = THIS_MODULE, + .dai_link = snd_rpi_hifiberry_dacplus_dai, + .num_links = ARRAY_SIZE(snd_rpi_hifiberry_dacplus_dai), +}; + +static int hb_hp_detect(void) +{ + struct i2c_adapter *adap = i2c_get_adapter(1); + int ret; + struct i2c_client tpa_i2c_client = { + .addr = 0x60, + .adapter = adap, + }; + + if (!adap) + return -EPROBE_DEFER; /* I2C module not yet available */ + + ret = i2c_smbus_read_byte(&tpa_i2c_client) >= 0; + i2c_put_adapter(adap); + return ret; +}; + +static struct property tpa_enable_prop = { + .name = "status", + .length = 4 + 1, /* length 'okay' + 1 */ + .value = "okay", + }; + +static int snd_rpi_hifiberry_dacplus_probe(struct platform_device *pdev) +{ + int ret = 0; + struct snd_soc_card *card = &snd_rpi_hifiberry_dacplus; + int len; + struct device_node *tpa_node; + struct device_node *tas_node; + struct property *tpa_prop; + struct of_changeset ocs; + struct property *pp; + int tmp; + + /* probe for head phone amp */ + ret = hb_hp_detect(); + if (ret < 0) + return ret; + if (ret) { + card->aux_dev = hifiberry_dacplus_aux_devs; + card->num_aux_devs = + ARRAY_SIZE(hifiberry_dacplus_aux_devs); + tpa_node = of_find_compatible_node(NULL, NULL, "ti,tpa6130a2"); + tpa_prop = of_find_property(tpa_node, "status", &len); + + if (strcmp((char *)tpa_prop->value, "okay")) { + /* and activate headphone using change_sets */ + dev_info(&pdev->dev, "activating headphone amplifier"); + of_changeset_init(&ocs); + ret = of_changeset_update_property(&ocs, tpa_node, + &tpa_enable_prop); + if (ret) { + dev_err(&pdev->dev, + "cannot activate headphone amplifier\n"); + return -ENODEV; + } + ret = of_changeset_apply(&ocs); + if (ret) { + dev_err(&pdev->dev, + "cannot activate headphone amplifier\n"); + return -ENODEV; + } + } + } + + tas_node = of_find_compatible_node(NULL, NULL, "ti,tas5756"); + if (tas_node) { + tas_device = true; + dev_info(&pdev->dev, "TAS5756 device found!\n"); + }; + + snd_rpi_hifiberry_dacplus.dev = &pdev->dev; + if (pdev->dev.of_node) { + struct device_node *i2s_node; + struct snd_soc_dai_link *dai; + + dai = &snd_rpi_hifiberry_dacplus_dai[0]; + i2s_node = of_parse_phandle(pdev->dev.of_node, + "i2s-controller", 0); + + if (i2s_node) { + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + } + + digital_gain_0db_limit = !of_property_read_bool( + pdev->dev.of_node, "hifiberry,24db_digital_gain"); + slave = of_property_read_bool(pdev->dev.of_node, + "hifiberry-dacplus,slave"); + leds_off = of_property_read_bool(pdev->dev.of_node, + "hifiberry-dacplus,leds_off"); + auto_mute = of_property_read_bool(pdev->dev.of_node, + "hifiberry-dacplus,auto_mute"); + + /* + * check for HW MUTE as defined in DT-overlay + * active high, therefore default to HIGH to MUTE + */ + snd_mute_gpio = devm_gpiod_get_optional(&pdev->dev, + "mute", GPIOD_OUT_HIGH); + if (IS_ERR(snd_mute_gpio)) { + dev_err(&pdev->dev, "Can't allocate GPIO (HW-MUTE)"); + return PTR_ERR(snd_mute_gpio); + } + + /* add ALSA control if requested in DT-overlay (AMP100) */ + pp = of_find_property(pdev->dev.of_node, + "hifiberry-dacplus,mute_ext_ctl", &tmp); + if (pp) { + if (!of_property_read_u32(pdev->dev.of_node, + "hifiberry-dacplus,mute_ext_ctl", &mute_ext)) { + /* ALSA control will be used */ + mute_ext_ctl = 1; + } + } + + /* check for HW RESET (AMP100) */ + snd_reset_gpio = devm_gpiod_get_optional(&pdev->dev, + "reset", GPIOD_OUT_HIGH); + if (IS_ERR(snd_reset_gpio)) { + dev_err(&pdev->dev, "Can't allocate GPIO (HW-RESET)"); + return PTR_ERR(snd_reset_gpio); + } + + } + + ret = devm_snd_soc_register_card(&pdev->dev, + &snd_rpi_hifiberry_dacplus); + if (ret && ret != -EPROBE_DEFER) + dev_err(&pdev->dev, + "snd_soc_register_card() failed: %d\n", ret); + if (!ret) { + if (snd_mute_gpio) + dev_info(&pdev->dev, "GPIO%i for HW-MUTE selected", + gpio_chip_hwgpio(snd_mute_gpio)); + if (snd_reset_gpio) + dev_info(&pdev->dev, "GPIO%i for HW-RESET selected", + gpio_chip_hwgpio(snd_reset_gpio)); + } + return ret; +} + +static const struct of_device_id snd_rpi_hifiberry_dacplus_of_match[] = { + { .compatible = "hifiberry,hifiberry-dacplus", }, + {}, +}; +MODULE_DEVICE_TABLE(of, snd_rpi_hifiberry_dacplus_of_match); + +static struct platform_driver snd_rpi_hifiberry_dacplus_driver = { + .driver = { + .name = "snd-rpi-hifiberry-dacplus", + .owner = THIS_MODULE, + .of_match_table = snd_rpi_hifiberry_dacplus_of_match, + }, + .probe = snd_rpi_hifiberry_dacplus_probe, +}; + +module_platform_driver(snd_rpi_hifiberry_dacplus_driver); + +MODULE_AUTHOR("Daniel Matuschek "); +MODULE_DESCRIPTION("ASoC Driver for HiFiBerry DAC+"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/hifiberry_dacplusadc.c b/sound/soc/bcm/hifiberry_dacplusadc.c new file mode 100644 index 000000000000..e73da125e70d --- /dev/null +++ b/sound/soc/bcm/hifiberry_dacplusadc.c @@ -0,0 +1,399 @@ +/* + * ASoC Driver for HiFiBerry DAC+ / DAC Pro with ADC + * + * Author: Daniel Matuschek, Stuart MacLean + * Copyright 2014-2015 + * based on code by Florian Meier + * ADC added by Joerg Schambacher + * Copyright 2018 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "../codecs/pcm512x.h" + +#define HIFIBERRY_DACPRO_NOCLOCK 0 +#define HIFIBERRY_DACPRO_CLK44EN 1 +#define HIFIBERRY_DACPRO_CLK48EN 2 + +struct platform_device *dmic_codec_dev; + +struct pcm512x_priv { + struct regmap *regmap; + struct clk *sclk; +}; + +/* Clock rate of CLK44EN attached to GPIO6 pin */ +#define CLK_44EN_RATE 22579200UL +/* Clock rate of CLK48EN attached to GPIO3 pin */ +#define CLK_48EN_RATE 24576000UL + +static bool slave; +static bool snd_rpi_hifiberry_is_dacpro; +static bool digital_gain_0db_limit = true; +static bool leds_off; + +static void snd_rpi_hifiberry_dacplusadc_select_clk(struct snd_soc_component *component, + int clk_id) +{ + switch (clk_id) { + case HIFIBERRY_DACPRO_NOCLOCK: + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x00); + break; + case HIFIBERRY_DACPRO_CLK44EN: + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x20); + break; + case HIFIBERRY_DACPRO_CLK48EN: + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x04); + break; + } +} + +static void snd_rpi_hifiberry_dacplusadc_clk_gpio(struct snd_soc_component *component) +{ + snd_soc_component_update_bits(component, PCM512x_GPIO_EN, 0x24, 0x24); + snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_3, 0x0f, 0x02); + snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_6, 0x0f, 0x02); +} + +static bool snd_rpi_hifiberry_dacplusadc_is_sclk(struct snd_soc_component *component) +{ + unsigned int sck; + + sck = snd_soc_component_read(component, PCM512x_RATE_DET_4); + return (!(sck & 0x40)); +} + +static bool snd_rpi_hifiberry_dacplusadc_is_sclk_sleep( + struct snd_soc_component *component) +{ + msleep(2); + return snd_rpi_hifiberry_dacplusadc_is_sclk(component); +} + +static bool snd_rpi_hifiberry_dacplusadc_is_pro_card(struct snd_soc_component *component) +{ + bool isClk44EN, isClk48En, isNoClk; + + snd_rpi_hifiberry_dacplusadc_clk_gpio(component); + + snd_rpi_hifiberry_dacplusadc_select_clk(component, HIFIBERRY_DACPRO_CLK44EN); + isClk44EN = snd_rpi_hifiberry_dacplusadc_is_sclk_sleep(component); + + snd_rpi_hifiberry_dacplusadc_select_clk(component, HIFIBERRY_DACPRO_NOCLOCK); + isNoClk = snd_rpi_hifiberry_dacplusadc_is_sclk_sleep(component); + + snd_rpi_hifiberry_dacplusadc_select_clk(component, HIFIBERRY_DACPRO_CLK48EN); + isClk48En = snd_rpi_hifiberry_dacplusadc_is_sclk_sleep(component); + + return (isClk44EN && isClk48En && !isNoClk); +} + +static int snd_rpi_hifiberry_dacplusadc_clk_for_rate(int sample_rate) +{ + int type; + + switch (sample_rate) { + case 11025: + case 22050: + case 44100: + case 88200: + case 176400: + case 352800: + type = HIFIBERRY_DACPRO_CLK44EN; + break; + default: + type = HIFIBERRY_DACPRO_CLK48EN; + break; + } + return type; +} + +static void snd_rpi_hifiberry_dacplusadc_set_sclk(struct snd_soc_component *component, + int sample_rate) +{ + struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component); + + if (!IS_ERR(pcm512x->sclk)) { + int ctype; + + ctype = snd_rpi_hifiberry_dacplusadc_clk_for_rate(sample_rate); + clk_set_rate(pcm512x->sclk, (ctype == HIFIBERRY_DACPRO_CLK44EN) + ? CLK_44EN_RATE : CLK_48EN_RATE); + snd_rpi_hifiberry_dacplusadc_select_clk(component, ctype); + } +} + +static int snd_rpi_hifiberry_dacplusadc_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + struct pcm512x_priv *priv; + + if (slave) + snd_rpi_hifiberry_is_dacpro = false; + else + snd_rpi_hifiberry_is_dacpro = + snd_rpi_hifiberry_dacplusadc_is_pro_card(component); + + if (snd_rpi_hifiberry_is_dacpro) { + struct snd_soc_dai_link *dai = rtd->dai_link; + + dai->name = "HiFiBerry ADCDAC+ Pro"; + dai->stream_name = "HiFiBerry ADCDAC+ Pro HiFi"; + dai->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBP_CFP; + + snd_soc_component_update_bits(component, PCM512x_BCLK_LRCLK_CFG, 0x31, 0x11); + snd_soc_component_update_bits(component, PCM512x_MASTER_MODE, 0x03, 0x03); + snd_soc_component_update_bits(component, PCM512x_MASTER_CLKDIV_2, 0x7f, 63); + } else { + priv = snd_soc_component_get_drvdata(component); + priv->sclk = ERR_PTR(-ENOENT); + } + + snd_soc_component_update_bits(component, PCM512x_GPIO_EN, 0x08, 0x08); + snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_4, 0x0f, 0x02); + if (leds_off) + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x00); + else + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x08); + + if (digital_gain_0db_limit) { + int ret; + struct snd_soc_card *card = rtd->card; + + ret = snd_soc_limit_volume(card, "Digital Playback Volume", 207); + if (ret < 0) + dev_warn(card->dev, "Failed to set volume limit: %d\n", ret); + } + + return 0; +} + +static int snd_rpi_hifiberry_dacplusadc_update_rate_den( + struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component); + struct snd_ratnum *rats_no_pll; + unsigned int num = 0, den = 0; + int err; + + rats_no_pll = devm_kzalloc(rtd->dev, sizeof(*rats_no_pll), GFP_KERNEL); + if (!rats_no_pll) + return -ENOMEM; + + rats_no_pll->num = clk_get_rate(pcm512x->sclk) / 64; + rats_no_pll->den_min = 1; + rats_no_pll->den_max = 128; + rats_no_pll->den_step = 1; + + err = snd_interval_ratnum(hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE), 1, rats_no_pll, &num, &den); + if (err >= 0 && den) { + params->rate_num = num; + params->rate_den = den; + } + + devm_kfree(rtd->dev, rats_no_pll); + return 0; +} + +static int snd_rpi_hifiberry_dacplusadc_hw_params( + struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) +{ + int ret = 0; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + int channels = params_channels(params); + int width = snd_pcm_format_width(params_format(params)); + + /* Using powers of 2 allows for an integer clock divisor */ + width = width <= 16 ? 16 : 32; + + if (snd_rpi_hifiberry_is_dacpro) { + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + + snd_rpi_hifiberry_dacplusadc_set_sclk(component, + params_rate(params)); + + ret = snd_rpi_hifiberry_dacplusadc_update_rate_den( + substream, params); + } + + ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_cpu(rtd, 0), channels * width); + if (ret) + return ret; + ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_codec(rtd, 0), channels * width); + return ret; +} + +static int hifiberry_dacplusadc_LED_cnt; + +static int snd_rpi_hifiberry_dacplusadc_startup( + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + + if (leds_off) + return 0; + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, + 0x08, 0x08); + hifiberry_dacplusadc_LED_cnt++; + return 0; +} + +static void snd_rpi_hifiberry_dacplusadc_shutdown( + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + + hifiberry_dacplusadc_LED_cnt--; + if (!hifiberry_dacplusadc_LED_cnt) + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, + 0x08, 0x00); +} + +/* machine stream operations */ +static struct snd_soc_ops snd_rpi_hifiberry_dacplusadc_ops = { + .hw_params = snd_rpi_hifiberry_dacplusadc_hw_params, + .startup = snd_rpi_hifiberry_dacplusadc_startup, + .shutdown = snd_rpi_hifiberry_dacplusadc_shutdown, +}; + +SND_SOC_DAILINK_DEFS(hifi, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), + DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004d", "pcm512x-hifi"), + COMP_CODEC("dmic-codec", "dmic-hifi")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0"))); + +static struct snd_soc_dai_link snd_rpi_hifiberry_dacplusadc_dai[] = { +{ + .name = "HiFiBerry DAC+ADC", + .stream_name = "HiFiBerry DAC+ADC HiFi", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBC_CFC, + .ops = &snd_rpi_hifiberry_dacplusadc_ops, + .init = snd_rpi_hifiberry_dacplusadc_init, + SND_SOC_DAILINK_REG(hifi), +}, +}; + +/* audio machine driver */ +static struct snd_soc_card snd_rpi_hifiberry_dacplusadc = { + .name = "snd_rpi_hifiberry_dacplusadc", + .driver_name = "HifiberryDacpAdc", + .owner = THIS_MODULE, + .dai_link = snd_rpi_hifiberry_dacplusadc_dai, + .num_links = ARRAY_SIZE(snd_rpi_hifiberry_dacplusadc_dai), +}; + + +static int snd_rpi_hifiberry_dacplusadc_probe(struct platform_device *pdev) +{ + int ret = 0; + + snd_rpi_hifiberry_dacplusadc.dev = &pdev->dev; + if (pdev->dev.of_node) { + struct device_node *i2s_node; + struct snd_soc_dai_link *dai; + + dai = &snd_rpi_hifiberry_dacplusadc_dai[0]; + i2s_node = of_parse_phandle(pdev->dev.of_node, + "i2s-controller", 0); + if (i2s_node) { + dai->cpus->of_node = i2s_node; + dai->platforms->of_node = i2s_node; + dai->cpus->dai_name = NULL; + dai->platforms->name = NULL; + } + } + digital_gain_0db_limit = !of_property_read_bool( + pdev->dev.of_node, "hifiberry,24db_digital_gain"); + slave = of_property_read_bool(pdev->dev.of_node, + "hifiberry-dacplusadc,slave"); + leds_off = of_property_read_bool(pdev->dev.of_node, + "hifiberry-dacplusadc,leds_off"); + + ret = devm_snd_soc_register_card(&pdev->dev, + &snd_rpi_hifiberry_dacplusadc); + if (ret && ret != -EPROBE_DEFER) + dev_err(&pdev->dev, + "snd_soc_register_card() failed: %d\n", ret); + + return ret; +} + +static const struct of_device_id snd_rpi_hifiberry_dacplusadc_of_match[] = { + { .compatible = "hifiberry,hifiberry-dacplusadc", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, snd_rpi_hifiberry_dacplusadc_of_match); + +static struct platform_driver snd_rpi_hifiberry_dacplusadc_driver = { + .driver = { + .name = "snd-rpi-hifiberry-dacplusadc", + .owner = THIS_MODULE, + .of_match_table = snd_rpi_hifiberry_dacplusadc_of_match, + }, + .probe = snd_rpi_hifiberry_dacplusadc_probe, +}; + +static int __init hifiberry_dacplusadc_init(void) +{ + int ret; + + dmic_codec_dev = platform_device_register_simple("dmic-codec", -1, NULL, + 0); + if (IS_ERR(dmic_codec_dev)) { + pr_err("%s: dmic-codec device registration failed\n", __func__); + return PTR_ERR(dmic_codec_dev); + } + + ret = platform_driver_register(&snd_rpi_hifiberry_dacplusadc_driver); + if (ret) { + pr_err("%s: platform driver registration failed\n", __func__); + platform_device_unregister(dmic_codec_dev); + } + + return ret; +} +module_init(hifiberry_dacplusadc_init); + +static void __exit hifiberry_dacplusadc_exit(void) +{ + platform_driver_unregister(&snd_rpi_hifiberry_dacplusadc_driver); + platform_device_unregister(dmic_codec_dev); +} +module_exit(hifiberry_dacplusadc_exit); + +MODULE_AUTHOR("Joerg Schambacher "); +MODULE_AUTHOR("Daniel Matuschek "); +MODULE_DESCRIPTION("ASoC Driver for HiFiBerry DAC+ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/hifiberry_dacplusadcpro.c b/sound/soc/bcm/hifiberry_dacplusadcpro.c new file mode 100644 index 000000000000..9c853379dc49 --- /dev/null +++ b/sound/soc/bcm/hifiberry_dacplusadcpro.c @@ -0,0 +1,498 @@ +/* + * ASoC Driver for HiFiBerry DAC+ / DAC Pro with ADC PRO Version (SW control) + * + * Author: Daniel Matuschek, Stuart MacLean + * Copyright 2014-2015 + * based on code by Florian Meier + * ADC, HP added by Joerg Schambacher + * Copyright 2018-21 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "../codecs/pcm512x.h" +#include "../codecs/pcm186x.h" +#include "hifiberry_adc_controls.h" + +#define HIFIBERRY_DACPRO_NOCLOCK 0 +#define HIFIBERRY_DACPRO_CLK44EN 1 +#define HIFIBERRY_DACPRO_CLK48EN 2 + +struct pcm512x_priv { + struct regmap *regmap; + struct clk *sclk; +}; + +/* Clock rate of CLK44EN attached to GPIO6 pin */ +#define CLK_44EN_RATE 22579200UL +/* Clock rate of CLK48EN attached to GPIO3 pin */ +#define CLK_48EN_RATE 24576000UL + +static bool slave; +static bool snd_rpi_hifiberry_is_dacpro; +static bool digital_gain_0db_limit = true; +static bool leds_off; + +static int pcm1863_add_controls(struct snd_soc_component *component) +{ + snd_soc_add_component_controls(component, + pcm1863_snd_controls_card, + ARRAY_SIZE(pcm1863_snd_controls_card)); + return 0; +} + +static void snd_rpi_hifiberry_dacplusadcpro_select_clk( + struct snd_soc_component *component, int clk_id) +{ + switch (clk_id) { + case HIFIBERRY_DACPRO_NOCLOCK: + snd_soc_component_update_bits(component, + PCM512x_GPIO_CONTROL_1, 0x24, 0x00); + break; + case HIFIBERRY_DACPRO_CLK44EN: + snd_soc_component_update_bits(component, + PCM512x_GPIO_CONTROL_1, 0x24, 0x20); + break; + case HIFIBERRY_DACPRO_CLK48EN: + snd_soc_component_update_bits(component, + PCM512x_GPIO_CONTROL_1, 0x24, 0x04); + break; + } + usleep_range(3000, 4000); +} + +static void snd_rpi_hifiberry_dacplusadcpro_clk_gpio(struct snd_soc_component *component) +{ + snd_soc_component_update_bits(component, PCM512x_GPIO_EN, 0x24, 0x24); + snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_3, 0x0f, 0x02); + snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_6, 0x0f, 0x02); +} + +static bool snd_rpi_hifiberry_dacplusadcpro_is_sclk(struct snd_soc_component *component) +{ + unsigned int sck; + + sck = snd_soc_component_read(component, PCM512x_RATE_DET_4); + return (!(sck & 0x40)); +} + +static bool snd_rpi_hifiberry_dacplusadcpro_is_pro_card(struct snd_soc_component *component) +{ + bool isClk44EN, isClk48En, isNoClk; + + snd_rpi_hifiberry_dacplusadcpro_clk_gpio(component); + + snd_rpi_hifiberry_dacplusadcpro_select_clk(component, HIFIBERRY_DACPRO_CLK44EN); + isClk44EN = snd_rpi_hifiberry_dacplusadcpro_is_sclk(component); + + snd_rpi_hifiberry_dacplusadcpro_select_clk(component, HIFIBERRY_DACPRO_NOCLOCK); + isNoClk = snd_rpi_hifiberry_dacplusadcpro_is_sclk(component); + + snd_rpi_hifiberry_dacplusadcpro_select_clk(component, HIFIBERRY_DACPRO_CLK48EN); + isClk48En = snd_rpi_hifiberry_dacplusadcpro_is_sclk(component); + + return (isClk44EN && isClk48En && !isNoClk); +} + +static int snd_rpi_hifiberry_dacplusadcpro_clk_for_rate(int sample_rate) +{ + int type; + + switch (sample_rate) { + case 11025: + case 22050: + case 44100: + case 88200: + case 176400: + case 352800: + type = HIFIBERRY_DACPRO_CLK44EN; + break; + default: + type = HIFIBERRY_DACPRO_CLK48EN; + break; + } + return type; +} + +static void snd_rpi_hifiberry_dacplusadcpro_set_sclk(struct snd_soc_component *component, + int sample_rate) +{ + struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component); + + if (!IS_ERR(pcm512x->sclk)) { + int ctype; + + ctype = snd_rpi_hifiberry_dacplusadcpro_clk_for_rate(sample_rate); + clk_set_rate(pcm512x->sclk, (ctype == HIFIBERRY_DACPRO_CLK44EN) + ? CLK_44EN_RATE : CLK_48EN_RATE); + snd_rpi_hifiberry_dacplusadcpro_select_clk(component, ctype); + } +} + +static int snd_rpi_hifiberry_dacplusadcpro_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *dac = snd_soc_rtd_to_codec(rtd, 0)->component; + struct snd_soc_component *adc = snd_soc_rtd_to_codec(rtd, 1)->component; + struct snd_soc_dai_driver *adc_driver = snd_soc_rtd_to_codec(rtd, 1)->driver; + struct pcm512x_priv *priv; + int ret; + + if (slave) + snd_rpi_hifiberry_is_dacpro = false; + else + snd_rpi_hifiberry_is_dacpro = + snd_rpi_hifiberry_dacplusadcpro_is_pro_card(dac); + + if (snd_rpi_hifiberry_is_dacpro) { + struct snd_soc_dai_link *dai = rtd->dai_link; + + dai->name = "HiFiBerry DAC+ADC Pro"; + dai->stream_name = "HiFiBerry DAC+ADC Pro HiFi"; + dai->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBP_CFP; + + // set DAC DAI configuration + ret = snd_soc_dai_set_fmt(snd_soc_rtd_to_codec(rtd, 0), + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBP_CFP); + if (ret < 0) + return ret; + + // set ADC DAI configuration + ret = snd_soc_dai_set_fmt(snd_soc_rtd_to_codec(rtd, 1), + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBC_CFC); + if (ret < 0) + return ret; + + // set CPU DAI configuration + ret = snd_soc_dai_set_fmt(snd_soc_rtd_to_cpu(rtd, 0), + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBC_CFC); + if (ret < 0) + return ret; + + snd_soc_component_update_bits(dac, PCM512x_BCLK_LRCLK_CFG, 0x31, 0x11); + snd_soc_component_update_bits(dac, PCM512x_MASTER_MODE, 0x03, 0x03); + snd_soc_component_update_bits(dac, PCM512x_MASTER_CLKDIV_2, 0x7f, 63); + } else { + priv = snd_soc_component_get_drvdata(dac); + priv->sclk = ERR_PTR(-ENOENT); + } + + /* disable 24bit mode as long as I2S module does not have sign extension fixed */ + adc_driver->capture.formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE; + + snd_soc_component_update_bits(dac, PCM512x_GPIO_EN, 0x08, 0x08); + snd_soc_component_update_bits(dac, PCM512x_GPIO_OUTPUT_4, 0x0f, 0x02); + if (leds_off) + snd_soc_component_update_bits(dac, PCM512x_GPIO_CONTROL_1, 0x08, 0x00); + else + snd_soc_component_update_bits(dac, PCM512x_GPIO_CONTROL_1, 0x08, 0x08); + + ret = pcm1863_add_controls(adc); + if (ret < 0) + dev_warn(rtd->dev, "Failed to add pcm1863 controls: %d\n", + ret); + + /* set GPIO2 to output, GPIO3 input */ + snd_soc_component_write(adc, PCM186X_GPIO3_2_CTRL, 0x00); + snd_soc_component_write(adc, PCM186X_GPIO3_2_DIR_CTRL, 0x04); + if (leds_off) + snd_soc_component_update_bits(adc, PCM186X_GPIO_IN_OUT, 0x40, 0x00); + else + snd_soc_component_update_bits(adc, PCM186X_GPIO_IN_OUT, 0x40, 0x40); + + if (digital_gain_0db_limit) { + int ret; + struct snd_soc_card *card = rtd->card; + + ret = snd_soc_limit_volume(card, "Digital Playback Volume", 207); + if (ret < 0) + dev_warn(card->dev, "Failed to set volume limit: %d\n", ret); + } + + return 0; +} + +static int snd_rpi_hifiberry_dacplusadcpro_update_rate_den( + struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; /* only use DAC */ + struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component); + struct snd_ratnum *rats_no_pll; + unsigned int num = 0, den = 0; + int err; + + rats_no_pll = devm_kzalloc(rtd->dev, sizeof(*rats_no_pll), GFP_KERNEL); + if (!rats_no_pll) + return -ENOMEM; + + rats_no_pll->num = clk_get_rate(pcm512x->sclk) / 64; + rats_no_pll->den_min = 1; + rats_no_pll->den_max = 128; + rats_no_pll->den_step = 1; + + err = snd_interval_ratnum(hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE), 1, rats_no_pll, &num, &den); + if (err >= 0 && den) { + params->rate_num = num; + params->rate_den = den; + } + + devm_kfree(rtd->dev, rats_no_pll); + return 0; +} + +static int snd_rpi_hifiberry_dacplusadcpro_hw_params( + struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) +{ + int ret = 0; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + int channels = params_channels(params); + int width = snd_pcm_format_width(params_format(params)); + struct snd_soc_component *dac = snd_soc_rtd_to_codec(rtd, 0)->component; + struct snd_soc_dai *dai = snd_soc_rtd_to_codec(rtd, 0); + struct snd_soc_dai_driver *drv = dai->driver; + const struct snd_soc_dai_ops *ops = drv->ops; + + /* Using powers of 2 allows for an integer clock divisor */ + width = width <= 16 ? 16 : 32; + + if (snd_rpi_hifiberry_is_dacpro) { + snd_rpi_hifiberry_dacplusadcpro_set_sclk(dac, + params_rate(params)); + + ret = snd_rpi_hifiberry_dacplusadcpro_update_rate_den( + substream, params); + if (ret) + return ret; + } + + ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_cpu(rtd, 0), channels * width); + if (ret) + return ret; + ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_codec(rtd, 0), channels * width); + if (ret) + return ret; + if (snd_rpi_hifiberry_is_dacpro && ops->hw_params) + ret = ops->hw_params(substream, params, dai); + return ret; +} + +static int snd_rpi_hifiberry_dacplusadcpro_startup( + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *dac = snd_soc_rtd_to_codec(rtd, 0)->component; + struct snd_soc_component *adc = snd_soc_rtd_to_codec(rtd, 1)->component; + + if (leds_off) + return 0; + /* switch on respective LED */ + if (!substream->stream) + snd_soc_component_update_bits(dac, PCM512x_GPIO_CONTROL_1, 0x08, 0x08); + else + snd_soc_component_update_bits(adc, PCM186X_GPIO_IN_OUT, 0x40, 0x40); + return 0; +} + +static void snd_rpi_hifiberry_dacplusadcpro_shutdown( + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *dac = snd_soc_rtd_to_codec(rtd, 0)->component; + struct snd_soc_component *adc = snd_soc_rtd_to_codec(rtd, 1)->component; + + /* switch off respective LED */ + if (!substream->stream) + snd_soc_component_update_bits(dac, PCM512x_GPIO_CONTROL_1, 0x08, 0x00); + else + snd_soc_component_update_bits(adc, PCM186X_GPIO_IN_OUT, 0x40, 0x00); +} + + +/* machine stream operations */ +static struct snd_soc_ops snd_rpi_hifiberry_dacplusadcpro_ops = { + .hw_params = snd_rpi_hifiberry_dacplusadcpro_hw_params, + .startup = snd_rpi_hifiberry_dacplusadcpro_startup, + .shutdown = snd_rpi_hifiberry_dacplusadcpro_shutdown, +}; + +SND_SOC_DAILINK_DEFS(hifi, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), + DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004d", "pcm512x-hifi"), + COMP_CODEC("pcm186x.1-004a", "pcm1863-aif")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0"))); + +static struct snd_soc_dai_link snd_rpi_hifiberry_dacplusadcpro_dai[] = { +{ + .name = "HiFiBerry DAC+ADC PRO", + .stream_name = "HiFiBerry DAC+ADC PRO HiFi", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBC_CFC, + .ops = &snd_rpi_hifiberry_dacplusadcpro_ops, + .init = snd_rpi_hifiberry_dacplusadcpro_init, + SND_SOC_DAILINK_REG(hifi), +}, +}; + +/* aux device for optional headphone amp */ +static struct snd_soc_aux_dev hifiberry_dacplusadcpro_aux_devs[] = { + { + .dlc = { + .name = "tpa6130a2.1-0060", + }, + }, +}; + +/* audio machine driver */ +static struct snd_soc_card snd_rpi_hifiberry_dacplusadcpro = { + .name = "snd_rpi_hifiberry_dacplusadcpro", + .driver_name = "HifiberryDacpAdcPro", + .owner = THIS_MODULE, + .dai_link = snd_rpi_hifiberry_dacplusadcpro_dai, + .num_links = ARRAY_SIZE(snd_rpi_hifiberry_dacplusadcpro_dai), +}; + +static int hb_hp_detect(void) +{ + struct i2c_adapter *adap = i2c_get_adapter(1); + int ret; + struct i2c_client tpa_i2c_client = { + .addr = 0x60, + .adapter = adap, + }; + + if (!adap) + return -EPROBE_DEFER; /* I2C module not yet available */ + + ret = i2c_smbus_read_byte(&tpa_i2c_client) >= 0; + i2c_put_adapter(adap); + return ret; +}; + +static struct property tpa_enable_prop = { + .name = "status", + .length = 4 + 1, /* length 'okay' + 1 */ + .value = "okay", + }; + +static int snd_rpi_hifiberry_dacplusadcpro_probe(struct platform_device *pdev) +{ + int ret = 0, i = 0; + struct snd_soc_card *card = &snd_rpi_hifiberry_dacplusadcpro; + struct device_node *tpa_node; + struct property *tpa_prop; + struct of_changeset ocs; + int len; + + /* probe for head phone amp */ + ret = hb_hp_detect(); + if (ret < 0) + return ret; + if (ret) { + card->aux_dev = hifiberry_dacplusadcpro_aux_devs; + card->num_aux_devs = + ARRAY_SIZE(hifiberry_dacplusadcpro_aux_devs); + tpa_node = of_find_compatible_node(NULL, NULL, "ti,tpa6130a2"); + tpa_prop = of_find_property(tpa_node, "status", &len); + + if (strcmp((char *)tpa_prop->value, "okay")) { + /* and activate headphone using change_sets */ + dev_info(&pdev->dev, "activating headphone amplifier"); + of_changeset_init(&ocs); + ret = of_changeset_update_property(&ocs, tpa_node, + &tpa_enable_prop); + if (ret) { + dev_err(&pdev->dev, + "cannot activate headphone amplifier\n"); + return -ENODEV; + } + ret = of_changeset_apply(&ocs); + if (ret) { + dev_err(&pdev->dev, + "cannot activate headphone amplifier\n"); + return -ENODEV; + } + } + } + + snd_rpi_hifiberry_dacplusadcpro.dev = &pdev->dev; + if (pdev->dev.of_node) { + struct device_node *i2s_node; + struct snd_soc_dai_link *dai; + + dai = &snd_rpi_hifiberry_dacplusadcpro_dai[0]; + i2s_node = of_parse_phandle(pdev->dev.of_node, + "i2s-controller", 0); + if (i2s_node) { + for (i = 0; i < card->num_links; i++) { + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + } + } + } + digital_gain_0db_limit = !of_property_read_bool( + pdev->dev.of_node, "hifiberry-dacplusadcpro,24db_digital_gain"); + slave = of_property_read_bool(pdev->dev.of_node, + "hifiberry-dacplusadcpro,slave"); + leds_off = of_property_read_bool(pdev->dev.of_node, + "hifiberry-dacplusadcpro,leds_off"); + ret = snd_soc_register_card(&snd_rpi_hifiberry_dacplusadcpro); + if (ret && ret != -EPROBE_DEFER) + dev_err(&pdev->dev, + "snd_soc_register_card() failed: %d\n", ret); + + return ret; +} + +static const struct of_device_id snd_rpi_hifiberry_dacplusadcpro_of_match[] = { + { .compatible = "hifiberry,hifiberry-dacplusadcpro", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, snd_rpi_hifiberry_dacplusadcpro_of_match); + +static struct platform_driver snd_rpi_hifiberry_dacplusadcpro_driver = { + .driver = { + .name = "snd-rpi-hifiberry-dacplusadcpro", + .owner = THIS_MODULE, + .of_match_table = snd_rpi_hifiberry_dacplusadcpro_of_match, + }, + .probe = snd_rpi_hifiberry_dacplusadcpro_probe, +}; + +module_platform_driver(snd_rpi_hifiberry_dacplusadcpro_driver); + +MODULE_AUTHOR("Joerg Schambacher "); +MODULE_AUTHOR("Daniel Matuschek "); +MODULE_DESCRIPTION("ASoC Driver for HiFiBerry DAC+ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/hifiberry_dacplusdsp.c b/sound/soc/bcm/hifiberry_dacplusdsp.c new file mode 100644 index 000000000000..5a6ea965042d --- /dev/null +++ b/sound/soc/bcm/hifiberry_dacplusdsp.c @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ASoC Driver for HiFiBerry DAC + DSP + * + * Author: Joerg Schambacher + * Copyright 2018 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include +#include + +static struct snd_soc_component_driver dacplusdsp_component_driver; + +static struct snd_soc_dai_driver dacplusdsp_dai = { + .name = "dacplusdsp-hifi", + .capture = { + .stream_name = "DAC+DSP Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .playback = { + .stream_name = "DACP+DSP Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .symmetric_rate = 1}; + +#ifdef CONFIG_OF +static const struct of_device_id dacplusdsp_ids[] = { + { + .compatible = "hifiberry,dacplusdsp", + }, + {} }; +MODULE_DEVICE_TABLE(of, dacplusdsp_ids); +#endif + +static int dacplusdsp_platform_probe(struct platform_device *pdev) +{ + int ret; + + ret = snd_soc_register_component(&pdev->dev, + &dacplusdsp_component_driver, &dacplusdsp_dai, 1); + if (ret) { + pr_alert("snd_soc_register_component failed\n"); + return ret; + } + + return 0; +} + +static void dacplusdsp_platform_remove(struct platform_device *pdev) +{ + snd_soc_unregister_component(&pdev->dev); +} + +static struct platform_driver dacplusdsp_driver = { + .driver = { + .name = "hifiberry-dacplusdsp-codec", + .of_match_table = of_match_ptr(dacplusdsp_ids), + }, + .probe = dacplusdsp_platform_probe, + .remove = dacplusdsp_platform_remove, +}; + +module_platform_driver(dacplusdsp_driver); + +MODULE_AUTHOR("Joerg Schambacher "); +MODULE_DESCRIPTION("ASoC Driver for HiFiBerry DAC+DSP"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/hifiberry_dacplushd.c b/sound/soc/bcm/hifiberry_dacplushd.c new file mode 100644 index 000000000000..b535384f9fc7 --- /dev/null +++ b/sound/soc/bcm/hifiberry_dacplushd.c @@ -0,0 +1,233 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ASoC Driver for HiFiBerry DAC+ HD + * + * Author: Joerg Schambacher, i2Audio GmbH for HiFiBerry + * Copyright 2020 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../codecs/pcm179x.h" + +#define DEFAULT_RATE 44100 + +struct brd_drv_data { + struct regmap *regmap; + struct clk *sclk; +}; + +static struct brd_drv_data drvdata; +static struct gpio_desc *reset_gpio; +static const unsigned int hb_dacplushd_rates[] = { + 192000, 96000, 48000, 176400, 88200, 44100, +}; + +static struct snd_pcm_hw_constraint_list hb_dacplushd_constraints = { + .list = hb_dacplushd_rates, + .count = ARRAY_SIZE(hb_dacplushd_rates), +}; + +static int snd_rpi_hb_dacplushd_startup(struct snd_pcm_substream *substream) +{ + /* constraints for standard sample rates */ + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &hb_dacplushd_constraints); + return 0; +} + +static void snd_rpi_hifiberry_dacplushd_set_sclk( + struct snd_soc_component *component, + int sample_rate) +{ + if (!IS_ERR(drvdata.sclk)) + clk_set_rate(drvdata.sclk, sample_rate); +} + +static int snd_rpi_hifiberry_dacplushd_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai_link *dai = rtd->dai_link; + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + + dai->name = "HiFiBerry DAC+ HD"; + dai->stream_name = "HiFiBerry DAC+ HD HiFi"; + dai->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBP_CFP; + + /* allow only fixed 32 clock counts per channel */ + snd_soc_dai_set_bclk_ratio(cpu_dai, 32*2); + + return 0; +} + +static int snd_rpi_hifiberry_dacplushd_hw_params( + struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) +{ + int ret = 0; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + + snd_rpi_hifiberry_dacplushd_set_sclk(component, params_rate(params)); + return ret; +} + +/* machine stream operations */ +static struct snd_soc_ops snd_rpi_hifiberry_dacplushd_ops = { + .startup = snd_rpi_hb_dacplushd_startup, + .hw_params = snd_rpi_hifiberry_dacplushd_hw_params, +}; + +SND_SOC_DAILINK_DEFS(hifi, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), + DAILINK_COMP_ARRAY(COMP_CODEC("pcm179x.1-004c", "pcm179x-hifi")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0"))); + + +static struct snd_soc_dai_link snd_rpi_hifiberry_dacplushd_dai[] = { +{ + .name = "HiFiBerry DAC+ HD", + .stream_name = "HiFiBerry DAC+ HD HiFi", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBC_CFC, + .ops = &snd_rpi_hifiberry_dacplushd_ops, + .init = snd_rpi_hifiberry_dacplushd_init, + SND_SOC_DAILINK_REG(hifi), +}, +}; + +/* audio machine driver */ +static struct snd_soc_card snd_rpi_hifiberry_dacplushd = { + .name = "snd_rpi_hifiberry_dacplushd", + .driver_name = "HifiberryDacplusHD", + .owner = THIS_MODULE, + .dai_link = snd_rpi_hifiberry_dacplushd_dai, + .num_links = ARRAY_SIZE(snd_rpi_hifiberry_dacplushd_dai), +}; + +static int snd_rpi_hifiberry_dacplushd_probe(struct platform_device *pdev) +{ + int ret = 0; + static int dac_reset_done; + struct device *dev = &pdev->dev; + struct device_node *dev_node = dev->of_node; + + snd_rpi_hifiberry_dacplushd.dev = &pdev->dev; + + /* get GPIO and release DAC from RESET */ + if (!dac_reset_done) { + reset_gpio = gpiod_get(&pdev->dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(reset_gpio)) { + dev_err(&pdev->dev, "gpiod_get() failed\n"); + return -EINVAL; + } + dac_reset_done = 1; + } + if (!IS_ERR(reset_gpio)) + gpiod_set_value(reset_gpio, 0); + msleep(1); + if (!IS_ERR(reset_gpio)) + gpiod_set_value(reset_gpio, 1); + msleep(1); + if (!IS_ERR(reset_gpio)) + gpiod_set_value(reset_gpio, 0); + + if (pdev->dev.of_node) { + struct device_node *i2s_node; + struct snd_soc_dai_link *dai; + + dai = &snd_rpi_hifiberry_dacplushd_dai[0]; + i2s_node = of_parse_phandle(pdev->dev.of_node, + "i2s-controller", 0); + + if (i2s_node) { + dai->cpus->of_node = i2s_node; + dai->platforms->of_node = i2s_node; + dai->cpus->dai_name = NULL; + dai->platforms->name = NULL; + } else { + return -EPROBE_DEFER; + } + + } + + ret = devm_snd_soc_register_card(&pdev->dev, + &snd_rpi_hifiberry_dacplushd); + if (ret && ret != -EPROBE_DEFER) { + dev_err(&pdev->dev, + "snd_soc_register_card() failed: %d\n", ret); + return ret; + } + if (ret == -EPROBE_DEFER) + return ret; + + dev_set_drvdata(dev, &drvdata); + if (dev_node == NULL) { + dev_err(&pdev->dev, "Device tree node not found\n"); + return -ENODEV; + } + + drvdata.sclk = devm_clk_get(dev, NULL); + if (IS_ERR(drvdata.sclk)) { + drvdata.sclk = ERR_PTR(-ENOENT); + return -ENODEV; + } + + clk_set_rate(drvdata.sclk, DEFAULT_RATE); + + return ret; +} + +static void snd_rpi_hifiberry_dacplushd_remove(struct platform_device *pdev) +{ + /* put DAC into RESET and release GPIO */ + gpiod_set_value(reset_gpio, 0); + gpiod_put(reset_gpio); +} + +static const struct of_device_id snd_rpi_hifiberry_dacplushd_of_match[] = { + { .compatible = "hifiberry,hifiberry-dacplushd", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, snd_rpi_hifiberry_dacplushd_of_match); + +static struct platform_driver snd_rpi_hifiberry_dacplushd_driver = { + .driver = { + .name = "snd-rpi-hifiberry-dacplushd", + .owner = THIS_MODULE, + .of_match_table = snd_rpi_hifiberry_dacplushd_of_match, + }, + .probe = snd_rpi_hifiberry_dacplushd_probe, + .remove = snd_rpi_hifiberry_dacplushd_remove, +}; + +module_platform_driver(snd_rpi_hifiberry_dacplushd_driver); + +MODULE_AUTHOR("Joerg Schambacher "); +MODULE_DESCRIPTION("ASoC Driver for HiFiBerry DAC+ HD"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/i-sabre-q2m.c b/sound/soc/bcm/i-sabre-q2m.c new file mode 100644 index 000000000000..dc538400a05d --- /dev/null +++ b/sound/soc/bcm/i-sabre-q2m.c @@ -0,0 +1,159 @@ +/* + * ASoC Driver for I-Sabre Q2M + * + * Author: Satoru Kawase + * Modified by: Xiao Qingyong + * Update kernel v4.18+ by : Audiophonics + * Copyright 2018 Audiophonics + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../codecs/i-sabre-codec.h" + + +static int snd_rpi_i_sabre_q2m_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + unsigned int value; + + /* Device ID */ + value = snd_soc_component_read(component, ISABRECODEC_REG_01); + dev_info(component->card->dev, "Audiophonics Device ID : %02X\n", value); + + /* API revision */ + value = snd_soc_component_read(component, ISABRECODEC_REG_02); + dev_info(component->card->dev, "Audiophonics API revision : %02X\n", value); + + return 0; +} + +static int snd_rpi_i_sabre_q2m_hw_params( + struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + int bclk_ratio; + + /* Using powers of 2 allows for an integer clock divisor */ + bclk_ratio = (snd_pcm_format_width(params_format(params)) <= 16 ? 16 : 32) * + params_channels(params); + return snd_soc_dai_set_bclk_ratio(cpu_dai, bclk_ratio); +} + +/* machine stream operations */ +static struct snd_soc_ops snd_rpi_i_sabre_q2m_ops = { + .hw_params = snd_rpi_i_sabre_q2m_hw_params, +}; + +SND_SOC_DAILINK_DEFS(rpi_i_sabre_q2m, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), + DAILINK_COMP_ARRAY(COMP_CODEC("i-sabre-codec-i2c.1-0048", "i-sabre-codec-dai")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0"))); + +static struct snd_soc_dai_link snd_rpi_i_sabre_q2m_dai[] = { + { + .name = "I-Sabre Q2M", + .stream_name = "I-Sabre Q2M DAC", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBC_CFC, + .init = snd_rpi_i_sabre_q2m_init, + .ops = &snd_rpi_i_sabre_q2m_ops, + SND_SOC_DAILINK_REG(rpi_i_sabre_q2m), + } +}; + +/* audio machine driver */ +static struct snd_soc_card snd_rpi_i_sabre_q2m = { + .name = "I-Sabre Q2M DAC", + .owner = THIS_MODULE, + .dai_link = snd_rpi_i_sabre_q2m_dai, + .num_links = ARRAY_SIZE(snd_rpi_i_sabre_q2m_dai) +}; + + +static int snd_rpi_i_sabre_q2m_probe(struct platform_device *pdev) +{ + int ret = 0; + + snd_rpi_i_sabre_q2m.dev = &pdev->dev; + if (pdev->dev.of_node) { + struct device_node *i2s_node; + struct snd_soc_dai_link *dai; + + dai = &snd_rpi_i_sabre_q2m_dai[0]; + i2s_node = of_parse_phandle(pdev->dev.of_node, + "i2s-controller", 0); + if (i2s_node) { + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + } else { + dev_err(&pdev->dev, + "Property 'i2s-controller' missing or invalid\n"); + return (-EINVAL); + } + + dai->name = "I-Sabre Q2M"; + dai->stream_name = "I-Sabre Q2M DAC"; + dai->dai_fmt = SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBC_CFC; + } + + /* Wait for registering codec driver */ + mdelay(50); + + ret = snd_soc_register_card(&snd_rpi_i_sabre_q2m); + if (ret) { + dev_err(&pdev->dev, + "snd_soc_register_card() failed: %d\n", ret); + } + + return ret; +} + +static void snd_rpi_i_sabre_q2m_remove(struct platform_device *pdev) +{ + snd_soc_unregister_card(&snd_rpi_i_sabre_q2m); +} + +static const struct of_device_id snd_rpi_i_sabre_q2m_of_match[] = { + { .compatible = "audiophonics,i-sabre-q2m", }, + {} +}; +MODULE_DEVICE_TABLE(of, snd_rpi_i_sabre_q2m_of_match); + +static struct platform_driver snd_rpi_i_sabre_q2m_driver = { + .driver = { + .name = "snd-rpi-i-sabre-q2m", + .owner = THIS_MODULE, + .of_match_table = snd_rpi_i_sabre_q2m_of_match, + }, + .probe = snd_rpi_i_sabre_q2m_probe, + .remove = snd_rpi_i_sabre_q2m_remove, +}; +module_platform_driver(snd_rpi_i_sabre_q2m_driver); + +MODULE_DESCRIPTION("ASoC Driver for I-Sabre Q2M"); +MODULE_AUTHOR("Audiophonics "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/bcm/iqaudio-codec.c b/sound/soc/bcm/iqaudio-codec.c new file mode 100644 index 000000000000..02ea79029efc --- /dev/null +++ b/sound/soc/bcm/iqaudio-codec.c @@ -0,0 +1,283 @@ +/* + * ASoC Driver for IQaudIO Raspberry Pi Codec board + * + * Author: Gordon Garrity + * (C) Copyright IQaudio Limited, 2017-2019 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include "../codecs/da7213.h" + +static int pll_out = DA7213_PLL_FREQ_OUT_90316800; + +static int snd_rpi_iqaudio_pll_control(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + int ret = 0; + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_card *card = dapm->card; + struct snd_soc_pcm_runtime *rtd = + snd_soc_get_pcm_runtime(card, &card->dai_link[0]); + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + + if (SND_SOC_DAPM_EVENT_OFF(event)) { + ret = snd_soc_dai_set_pll(codec_dai, 0, DA7213_SYSCLK_MCLK, 0, + 0); + if (ret) + dev_err(card->dev, "Failed to bypass PLL: %d\n", ret); + /* Allow PLL time to bypass */ + msleep(100); + } else if (SND_SOC_DAPM_EVENT_ON(event)) { + ret = snd_soc_dai_set_pll(codec_dai, 0, DA7213_SYSCLK_PLL, 0, + pll_out); + if (ret) + dev_err(card->dev, "Failed to enable PLL: %d\n", ret); + /* Allow PLL time to lock */ + msleep(100); + } + + return ret; +} + +static int snd_rpi_iqaudio_post_dapm_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + switch (event) { + case SND_SOC_DAPM_POST_PMU: + /* Delay for mic bias ramp */ + msleep(1000); + break; + default: + break; + } + + return 0; +} + +static const struct snd_kcontrol_new dapm_controls[] = { + SOC_DAPM_PIN_SWITCH("HP Jack"), + SOC_DAPM_PIN_SWITCH("MIC Jack"), + SOC_DAPM_PIN_SWITCH("Onboard MIC"), + SOC_DAPM_PIN_SWITCH("AUX Jack"), +}; + +static const struct snd_soc_dapm_widget dapm_widgets[] = { + SND_SOC_DAPM_HP("HP Jack", NULL), + SND_SOC_DAPM_MIC("MIC Jack", NULL), + SND_SOC_DAPM_MIC("Onboard MIC", NULL), + SND_SOC_DAPM_LINE("AUX Jack", NULL), + SND_SOC_DAPM_SUPPLY("PLL Control", SND_SOC_NOPM, 0, 0, + snd_rpi_iqaudio_pll_control, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_POST("Post Power Up Event", snd_rpi_iqaudio_post_dapm_event), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + {"HP Jack", NULL, "HPL"}, + {"HP Jack", NULL, "HPR"}, + {"HP Jack", NULL, "PLL Control"}, + + {"AUXR", NULL, "AUX Jack"}, + {"AUXL", NULL, "AUX Jack"}, + {"AUX Jack", NULL, "PLL Control"}, + + /* Assume Mic1 is linked to Headset and Mic2 to on-board mic */ + {"MIC1", NULL, "MIC Jack"}, + {"MIC Jack", NULL, "PLL Control"}, + {"MIC2", NULL, "Onboard MIC"}, + {"Onboard MIC", NULL, "PLL Control"}, +}; + +/* machine stream operations */ + +static int snd_rpi_iqaudio_codec_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + int ret; + + /* + * Disable AUX Jack Pin by default to prevent PLL being enabled at + * startup. This avoids holding the PLL to a fixed SR config for + * subsequent streams. + * + * This pin can still be enabled later, as required by user-space. + */ + snd_soc_dapm_disable_pin(&rtd->card->dapm, "AUX Jack"); + snd_soc_dapm_sync(&rtd->card->dapm); + + /* Impose BCLK ratios otherwise the codec may cheat */ + ret = snd_soc_dai_set_bclk_ratio(cpu_dai, 64); + if (ret) { + dev_err(rtd->dev, "Failed to set CPU BLCK ratio\n"); + return ret; + } + + ret = snd_soc_dai_set_bclk_ratio(codec_dai, 64); + if (ret) { + dev_err(rtd->dev, "Failed to set codec BCLK ratio\n"); + return ret; + } + + /* Set MCLK frequency to codec, onboard 11.2896MHz clock */ + return snd_soc_dai_set_sysclk(codec_dai, DA7213_CLKSRC_MCLK, 11289600, + SND_SOC_CLOCK_OUT); +} + +static int snd_rpi_iqaudio_codec_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + unsigned int samplerate = params_rate(params); + + switch (samplerate) { + case 8000: + case 16000: + case 32000: + case 48000: + case 96000: + pll_out = DA7213_PLL_FREQ_OUT_98304000; + break; + case 44100: + case 88200: + pll_out = DA7213_PLL_FREQ_OUT_90316800; + break; + default: + dev_err(rtd->dev,"Unsupported samplerate %d\n", samplerate); + return -EINVAL; + } + + return snd_soc_dai_set_pll(codec_dai, 0, DA7213_SYSCLK_PLL, 0, pll_out); +} + +static const struct snd_soc_ops snd_rpi_iqaudio_codec_ops = { + .hw_params = snd_rpi_iqaudio_codec_hw_params, +}; + +SND_SOC_DAILINK_DEFS(rpi_iqaudio, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), + DAILINK_COMP_ARRAY(COMP_CODEC("da7213.1-001a", "da7213-hifi")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2835-i2s.0"))); + +static struct snd_soc_dai_link snd_rpi_iqaudio_codec_dai[] = { +{ + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBP_CFP, + .init = snd_rpi_iqaudio_codec_init, + .ops = &snd_rpi_iqaudio_codec_ops, + .symmetric_rate = 1, + .symmetric_channels = 1, + .symmetric_sample_bits = 1, + SND_SOC_DAILINK_REG(rpi_iqaudio), +}, +}; + +/* audio machine driver */ +static struct snd_soc_card snd_rpi_iqaudio_codec = { + .owner = THIS_MODULE, + .dai_link = snd_rpi_iqaudio_codec_dai, + .num_links = ARRAY_SIZE(snd_rpi_iqaudio_codec_dai), + .controls = dapm_controls, + .num_controls = ARRAY_SIZE(dapm_controls), + .dapm_widgets = dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(dapm_widgets), + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), +}; + +static int snd_rpi_iqaudio_codec_probe(struct platform_device *pdev) +{ + int ret = 0; + + snd_rpi_iqaudio_codec.dev = &pdev->dev; + + if (pdev->dev.of_node) { + struct device_node *i2s_node; + struct snd_soc_card *card = &snd_rpi_iqaudio_codec; + struct snd_soc_dai_link *dai = &snd_rpi_iqaudio_codec_dai[0]; + + i2s_node = of_parse_phandle(pdev->dev.of_node, + "i2s-controller", 0); + if (i2s_node) { + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + } + + if (of_property_read_string(pdev->dev.of_node, "card_name", + &card->name)) + card->name = "IQaudIOCODEC"; + + if (of_property_read_string(pdev->dev.of_node, "dai_name", + &dai->name)) + dai->name = "IQaudIO CODEC"; + + if (of_property_read_string(pdev->dev.of_node, + "dai_stream_name", &dai->stream_name)) + dai->stream_name = "IQaudIO CODEC HiFi v1.2"; + + } + + ret = snd_soc_register_card(&snd_rpi_iqaudio_codec); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(&pdev->dev, + "snd_soc_register_card() failed: %d\n", ret); + return ret; + } + + return 0; +} + +static void snd_rpi_iqaudio_codec_remove(struct platform_device *pdev) +{ + snd_soc_unregister_card(&snd_rpi_iqaudio_codec); +} + +static const struct of_device_id iqaudio_of_match[] = { + { .compatible = "iqaudio,iqaudio-codec", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, iqaudio_of_match); + +static struct platform_driver snd_rpi_iqaudio_codec_driver = { + .driver = { + .name = "snd-rpi-iqaudio-codec", + .owner = THIS_MODULE, + .of_match_table = iqaudio_of_match, + }, + .probe = snd_rpi_iqaudio_codec_probe, + .remove = snd_rpi_iqaudio_codec_remove, +}; + + + +module_platform_driver(snd_rpi_iqaudio_codec_driver); + +MODULE_AUTHOR("Gordon Garrity "); +MODULE_DESCRIPTION("ASoC Driver for IQaudIO CODEC"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/iqaudio-dac.c b/sound/soc/bcm/iqaudio-dac.c new file mode 100644 index 000000000000..a86531bf3687 --- /dev/null +++ b/sound/soc/bcm/iqaudio-dac.c @@ -0,0 +1,223 @@ +/* + * ASoC Driver for IQaudIO DAC + * + * Author: Florian Meier + * Copyright 2013 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +static bool digital_gain_0db_limit = true; + +static struct gpio_desc *mute_gpio; + +static int snd_rpi_iqaudio_dac_init(struct snd_soc_pcm_runtime *rtd) +{ + if (digital_gain_0db_limit) + { + int ret; + struct snd_soc_card *card = rtd->card; + + ret = snd_soc_limit_volume(card, "Digital Playback Volume", 207); + if (ret < 0) + dev_warn(card->dev, "Failed to set volume limit: %d\n", ret); + } + + return 0; +} + +static void snd_rpi_iqaudio_gpio_mute(struct snd_soc_card *card) +{ + if (mute_gpio) { + dev_info(card->dev, "%s: muting amp using GPIO22\n", + __func__); + gpiod_set_value_cansleep(mute_gpio, 0); + } +} + +static void snd_rpi_iqaudio_gpio_unmute(struct snd_soc_card *card) +{ + if (mute_gpio) { + dev_info(card->dev, "%s: un-muting amp using GPIO22\n", + __func__); + gpiod_set_value_cansleep(mute_gpio, 1); + } +} + +static int snd_rpi_iqaudio_set_bias_level(struct snd_soc_card *card, + struct snd_soc_dapm_context *dapm, enum snd_soc_bias_level level) +{ + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_dai *codec_dai; + + rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]); + codec_dai = snd_soc_rtd_to_codec(rtd, 0); + + if (dapm->dev != codec_dai->dev) + return 0; + + switch (level) { + case SND_SOC_BIAS_PREPARE: + if (dapm->bias_level != SND_SOC_BIAS_STANDBY) + break; + + /* UNMUTE AMP */ + snd_rpi_iqaudio_gpio_unmute(card); + + break; + case SND_SOC_BIAS_STANDBY: + if (dapm->bias_level != SND_SOC_BIAS_PREPARE) + break; + + /* MUTE AMP */ + snd_rpi_iqaudio_gpio_mute(card); + + break; + default: + break; + } + + return 0; +} + +SND_SOC_DAILINK_DEFS(hifi, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), + DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004c", "pcm512x-hifi")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0"))); + +static struct snd_soc_dai_link snd_rpi_iqaudio_dac_dai[] = { +{ + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBC_CFC, + .init = snd_rpi_iqaudio_dac_init, + SND_SOC_DAILINK_REG(hifi), +}, +}; + +/* audio machine driver */ +static struct snd_soc_card snd_rpi_iqaudio_dac = { + .owner = THIS_MODULE, + .dai_link = snd_rpi_iqaudio_dac_dai, + .num_links = ARRAY_SIZE(snd_rpi_iqaudio_dac_dai), +}; + +static int snd_rpi_iqaudio_dac_probe(struct platform_device *pdev) +{ + int ret = 0; + bool gpio_unmute = false; + + snd_rpi_iqaudio_dac.dev = &pdev->dev; + + if (pdev->dev.of_node) { + struct device_node *i2s_node; + struct snd_soc_card *card = &snd_rpi_iqaudio_dac; + struct snd_soc_dai_link *dai = &snd_rpi_iqaudio_dac_dai[0]; + bool auto_gpio_mute = false; + + i2s_node = of_parse_phandle(pdev->dev.of_node, + "i2s-controller", 0); + if (i2s_node) { + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + } + + digital_gain_0db_limit = !of_property_read_bool( + pdev->dev.of_node, "iqaudio,24db_digital_gain"); + + if (of_property_read_string(pdev->dev.of_node, "card_name", + &card->name)) + card->name = "IQaudIODAC"; + + if (of_property_read_string(pdev->dev.of_node, "dai_name", + &dai->name)) + dai->name = "IQaudIO DAC"; + + if (of_property_read_string(pdev->dev.of_node, + "dai_stream_name", &dai->stream_name)) + dai->stream_name = "IQaudIO DAC HiFi"; + + /* gpio_unmute - one time unmute amp using GPIO */ + gpio_unmute = of_property_read_bool(pdev->dev.of_node, + "iqaudio-dac,unmute-amp"); + + /* auto_gpio_mute - mute/unmute amp using GPIO */ + auto_gpio_mute = of_property_read_bool(pdev->dev.of_node, + "iqaudio-dac,auto-mute-amp"); + + if (auto_gpio_mute || gpio_unmute) { + mute_gpio = devm_gpiod_get_optional(&pdev->dev, "mute", + GPIOD_OUT_LOW); + if (IS_ERR(mute_gpio)) { + ret = PTR_ERR(mute_gpio); + dev_err(&pdev->dev, + "Failed to get mute gpio: %d\n", ret); + return ret; + } + + if (auto_gpio_mute && mute_gpio) + snd_rpi_iqaudio_dac.set_bias_level = + snd_rpi_iqaudio_set_bias_level; + } + } + + ret = snd_soc_register_card(&snd_rpi_iqaudio_dac); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(&pdev->dev, + "snd_soc_register_card() failed: %d\n", ret); + return ret; + } + + if (gpio_unmute && mute_gpio) + snd_rpi_iqaudio_gpio_unmute(&snd_rpi_iqaudio_dac); + + return 0; +} + +static void snd_rpi_iqaudio_dac_remove(struct platform_device *pdev) +{ + snd_rpi_iqaudio_gpio_mute(&snd_rpi_iqaudio_dac); + + snd_soc_unregister_card(&snd_rpi_iqaudio_dac); +} + +static const struct of_device_id iqaudio_of_match[] = { + { .compatible = "iqaudio,iqaudio-dac", }, + {}, +}; +MODULE_DEVICE_TABLE(of, iqaudio_of_match); + +static struct platform_driver snd_rpi_iqaudio_dac_driver = { + .driver = { + .name = "snd-rpi-iqaudio-dac", + .owner = THIS_MODULE, + .of_match_table = iqaudio_of_match, + }, + .probe = snd_rpi_iqaudio_dac_probe, + .remove = snd_rpi_iqaudio_dac_remove, +}; + +module_platform_driver(snd_rpi_iqaudio_dac_driver); + +MODULE_AUTHOR("Florian Meier "); +MODULE_DESCRIPTION("ASoC Driver for IQAudio DAC"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/justboom-both.c b/sound/soc/bcm/justboom-both.c new file mode 100644 index 000000000000..6e5e6510238a --- /dev/null +++ b/sound/soc/bcm/justboom-both.c @@ -0,0 +1,266 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * rpi--wm8804.c -- ALSA SoC Raspberry Pi soundcard. + * + * Authors: Johannes Krude + * justboom-dac.c + * by Milan Neskovic + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include + +#include +#include +#include +#include +#include + +#include "../codecs/wm8804.h" +#include "../codecs/pcm512x.h" + + +static bool digital_gain_0db_limit = true; + +static int snd_rpi_justboom_both_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *digi = snd_soc_rtd_to_codec(rtd, 0)->component; + struct snd_soc_component *dac = snd_soc_rtd_to_codec(rtd, 1)->component; + + /* enable TX output */ + snd_soc_component_update_bits(digi, WM8804_PWRDN, 0x4, 0x0); + + snd_soc_component_update_bits(dac, PCM512x_GPIO_EN, 0x08, 0x08); + snd_soc_component_update_bits(dac, PCM512x_GPIO_OUTPUT_4, 0xf, 0x02); + snd_soc_component_update_bits(dac, PCM512x_GPIO_CONTROL_1, 0x08, 0x08); + + if (digital_gain_0db_limit) { + int ret; + struct snd_soc_card *card = rtd->card; + + ret = snd_soc_limit_volume(card, "Digital Playback Volume", + 207); + if (ret < 0) + dev_warn(card->dev, "Failed to set volume limit: %d\n", + ret); + } + + return 0; +} + +static int snd_rpi_justboom_both_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + struct snd_soc_component *digi = snd_soc_rtd_to_codec(rtd, 0)->component; + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + + int sysclk = 27000000; /* This is fixed on this board */ + + long mclk_freq = 0; + int mclk_div = 1; + int sampling_freq = 1; + + int ret; + + int samplerate = params_rate(params); + + if (samplerate <= 96000) { + mclk_freq = samplerate*256; + mclk_div = WM8804_MCLKDIV_256FS; + } else { + mclk_freq = samplerate*128; + mclk_div = WM8804_MCLKDIV_128FS; + } + + switch (samplerate) { + case 32000: + sampling_freq = 0x03; + break; + case 44100: + sampling_freq = 0x00; + break; + case 48000: + sampling_freq = 0x02; + break; + case 88200: + sampling_freq = 0x08; + break; + case 96000: + sampling_freq = 0x0a; + break; + case 176400: + sampling_freq = 0x0c; + break; + case 192000: + sampling_freq = 0x0e; + break; + default: + dev_err(rtd->card->dev, + "Failed to set WM8804 SYSCLK, unsupported samplerate %d\n", + samplerate); + } + + snd_soc_dai_set_clkdiv(codec_dai, WM8804_MCLK_DIV, mclk_div); + snd_soc_dai_set_pll(codec_dai, 0, 0, sysclk, mclk_freq); + + ret = snd_soc_dai_set_sysclk(codec_dai, WM8804_TX_CLKSRC_PLL, + sysclk, SND_SOC_CLOCK_OUT); + if (ret < 0) { + dev_err(rtd->card->dev, + "Failed to set WM8804 SYSCLK: %d\n", ret); + return ret; + } + + /* Enable TX output */ + snd_soc_component_update_bits(digi, WM8804_PWRDN, 0x4, 0x0); + + /* Power on */ + snd_soc_component_update_bits(digi, WM8804_PWRDN, 0x9, 0); + + /* set sampling frequency status bits */ + snd_soc_component_update_bits(digi, WM8804_SPDTX4, 0x0f, sampling_freq); + + return snd_soc_dai_set_bclk_ratio(cpu_dai, 64); +} + +static int snd_rpi_justboom_both_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *digi = snd_soc_rtd_to_codec(rtd, 0)->component; + struct snd_soc_component *dac = snd_soc_rtd_to_codec(rtd, 1)->component; + + /* turn on digital output */ + snd_soc_component_update_bits(digi, WM8804_PWRDN, 0x3c, 0x00); + + snd_soc_component_update_bits(dac, PCM512x_GPIO_CONTROL_1, 0x08, 0x08); + + return 0; +} + +static void snd_rpi_justboom_both_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *digi = snd_soc_rtd_to_codec(rtd, 0)->component; + struct snd_soc_component *dac = snd_soc_rtd_to_codec(rtd, 1)->component; + + snd_soc_component_update_bits(dac, PCM512x_GPIO_CONTROL_1, 0x08, 0x00); + + /* turn off output */ + snd_soc_component_update_bits(digi, WM8804_PWRDN, 0x3c, 0x3c); +} + +/* machine stream operations */ +static struct snd_soc_ops snd_rpi_justboom_both_ops = { + .hw_params = snd_rpi_justboom_both_hw_params, + .startup = snd_rpi_justboom_both_startup, + .shutdown = snd_rpi_justboom_both_shutdown, +}; + +SND_SOC_DAILINK_DEFS(rpi_justboom_both, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), + DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004d", "pcm512x-hifi"), + COMP_CODEC("wm8804.1-003b", "wm8804-spdif")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0"))); + +static struct snd_soc_dai_link snd_rpi_justboom_both_dai[] = { +{ + .name = "JustBoom Digi", + .stream_name = "JustBoom Digi HiFi", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBP_CFP, + .ops = &snd_rpi_justboom_both_ops, + .init = snd_rpi_justboom_both_init, + SND_SOC_DAILINK_REG(rpi_justboom_both), +}, +}; + +/* audio machine driver */ +static struct snd_soc_card snd_rpi_justboom_both = { + .name = "snd_rpi_justboom_both", + .driver_name = "JustBoomBoth", + .owner = THIS_MODULE, + .dai_link = snd_rpi_justboom_both_dai, + .num_links = ARRAY_SIZE(snd_rpi_justboom_both_dai), +}; + +static int snd_rpi_justboom_both_probe(struct platform_device *pdev) +{ + int ret = 0; + struct snd_soc_card *card = &snd_rpi_justboom_both; + + snd_rpi_justboom_both.dev = &pdev->dev; + + if (pdev->dev.of_node) { + struct device_node *i2s_node; + struct snd_soc_dai_link *dai = &snd_rpi_justboom_both_dai[0]; + + i2s_node = of_parse_phandle(pdev->dev.of_node, + "i2s-controller", 0); + + if (i2s_node) { + int i; + + for (i = 0; i < card->num_links; i++) { + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + } + } + + digital_gain_0db_limit = !of_property_read_bool( + pdev->dev.of_node, "justboom,24db_digital_gain"); + } + + ret = snd_soc_register_card(card); + if (ret && ret != -EPROBE_DEFER) { + dev_err(&pdev->dev, + "snd_soc_register_card() failed: %d\n", ret); + } + + return ret; +} + +static void snd_rpi_justboom_both_remove(struct platform_device *pdev) +{ + snd_soc_unregister_card(&snd_rpi_justboom_both); +} + +static const struct of_device_id snd_rpi_justboom_both_of_match[] = { + { .compatible = "justboom,justboom-both", }, + {}, +}; +MODULE_DEVICE_TABLE(of, snd_rpi_justboom_both_of_match); + +static struct platform_driver snd_rpi_justboom_both_driver = { + .driver = { + .name = "snd-rpi-justboom-both", + .owner = THIS_MODULE, + .of_match_table = snd_rpi_justboom_both_of_match, + }, + .probe = snd_rpi_justboom_both_probe, + .remove = snd_rpi_justboom_both_remove, +}; + +module_platform_driver(snd_rpi_justboom_both_driver); + +MODULE_AUTHOR("Johannes Krude "); +MODULE_DESCRIPTION("ASoC Driver for simultaneous use of JustBoom PI Digi & DAC HAT Sound Cards"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/justboom-dac.c b/sound/soc/bcm/justboom-dac.c new file mode 100644 index 000000000000..dc6b2ddaa8cb --- /dev/null +++ b/sound/soc/bcm/justboom-dac.c @@ -0,0 +1,147 @@ +/* + * ASoC Driver for JustBoom DAC Raspberry Pi HAT Sound Card + * + * Author: Milan Neskovic + * Copyright 2016 + * based on code by Daniel Matuschek + * based on code by Florian Meier + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include + +#include +#include +#include +#include +#include + +#include "../codecs/pcm512x.h" + +static bool digital_gain_0db_limit = true; + +static int snd_rpi_justboom_dac_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + snd_soc_component_update_bits(component, PCM512x_GPIO_EN, 0x08, 0x08); + snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_4, 0xf, 0x02); + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08,0x08); + + if (digital_gain_0db_limit) + { + int ret; + struct snd_soc_card *card = rtd->card; + + ret = snd_soc_limit_volume(card, "Digital Playback Volume", 207); + if (ret < 0) + dev_warn(card->dev, "Failed to set volume limit: %d\n", ret); + } + + return 0; +} + +static int snd_rpi_justboom_dac_startup(struct snd_pcm_substream *substream) { + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08,0x08); + return 0; +} + +static void snd_rpi_justboom_dac_shutdown(struct snd_pcm_substream *substream) { + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08,0x00); +} + +/* machine stream operations */ +static struct snd_soc_ops snd_rpi_justboom_dac_ops = { + .startup = snd_rpi_justboom_dac_startup, + .shutdown = snd_rpi_justboom_dac_shutdown, +}; + +SND_SOC_DAILINK_DEFS(hifi, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), + DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004d", "pcm512x-hifi")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0"))); + +static struct snd_soc_dai_link snd_rpi_justboom_dac_dai[] = { +{ + .name = "JustBoom DAC", + .stream_name = "JustBoom DAC HiFi", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBC_CFC, + .ops = &snd_rpi_justboom_dac_ops, + .init = snd_rpi_justboom_dac_init, + SND_SOC_DAILINK_REG(hifi), +}, +}; + +/* audio machine driver */ +static struct snd_soc_card snd_rpi_justboom_dac = { + .name = "snd_rpi_justboom_dac", + .driver_name = "JustBoomDac", + .owner = THIS_MODULE, + .dai_link = snd_rpi_justboom_dac_dai, + .num_links = ARRAY_SIZE(snd_rpi_justboom_dac_dai), +}; + +static int snd_rpi_justboom_dac_probe(struct platform_device *pdev) +{ + int ret = 0; + + snd_rpi_justboom_dac.dev = &pdev->dev; + + if (pdev->dev.of_node) { + struct device_node *i2s_node; + struct snd_soc_dai_link *dai = &snd_rpi_justboom_dac_dai[0]; + i2s_node = of_parse_phandle(pdev->dev.of_node, + "i2s-controller", 0); + + if (i2s_node) { + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + } + + digital_gain_0db_limit = !of_property_read_bool( + pdev->dev.of_node, "justboom,24db_digital_gain"); + } + + ret = devm_snd_soc_register_card(&pdev->dev, &snd_rpi_justboom_dac); + if (ret && ret != -EPROBE_DEFER) + dev_err(&pdev->dev, + "snd_soc_register_card() failed: %d\n", ret); + + return ret; +} + +static const struct of_device_id snd_rpi_justboom_dac_of_match[] = { + { .compatible = "justboom,justboom-dac", }, + {}, +}; +MODULE_DEVICE_TABLE(of, snd_rpi_justboom_dac_of_match); + +static struct platform_driver snd_rpi_justboom_dac_driver = { + .driver = { + .name = "snd-rpi-justboom-dac", + .owner = THIS_MODULE, + .of_match_table = snd_rpi_justboom_dac_of_match, + }, + .probe = snd_rpi_justboom_dac_probe, +}; + +module_platform_driver(snd_rpi_justboom_dac_driver); + +MODULE_AUTHOR("Milan Neskovic "); +MODULE_DESCRIPTION("ASoC Driver for JustBoom PI DAC HAT Sound Card"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/pifi-40.c b/sound/soc/bcm/pifi-40.c new file mode 100644 index 000000000000..64e755cf0595 --- /dev/null +++ b/sound/soc/bcm/pifi-40.c @@ -0,0 +1,281 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ALSA ASoC Machine Driver for PiFi-40 + * + * Author: David Knell + * based on code by Florian Meier + * Copyright (C) 2020 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static struct gpio_desc *pdn_gpio; +static int vol = 0x30; + +// Volume control +static int pifi_40_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = vol; + ucontrol->value.integer.value[1] = vol; + return 0; +} + +static int pifi_40_vol_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct snd_soc_pcm_runtime *rtd; + unsigned int v = ucontrol->value.integer.value[0]; + struct snd_soc_component *dac[2]; + + rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]); + dac[0] = snd_soc_rtd_to_codec(rtd, 0)->component; + dac[1] = snd_soc_rtd_to_codec(rtd, 1)->component; + + snd_soc_component_write(dac[0], 0x07, 255 - v); + snd_soc_component_write(dac[1], 0x07, 255 - v); + + vol = v; + return 1; +} + +static const DECLARE_TLV_DB_SCALE(digital_tlv_master, -10350, 50, 1); +static const struct snd_kcontrol_new pifi_40_controls[] = { + SOC_DOUBLE_R_EXT_TLV("Master Volume", 0x00, 0x01, + 0x00, // Min + 0xff, // Max + 0x01, // Invert + pifi_40_vol_get, pifi_40_vol_set, + digital_tlv_master) +}; + +static const char * const codec_ctl_pfx[] = { "Left", "Right" }; + +static const char * const codec_ctl_name[] = { "Master Volume", + "Speaker Volume", + "Speaker Switch" }; + +static int snd_pifi_40_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + struct snd_soc_component *dac[2]; + struct snd_kcontrol *kctl; + int i, j; + + dac[0] = snd_soc_rtd_to_codec(rtd, 0)->component; + dac[1] = snd_soc_rtd_to_codec(rtd, 1)->component; + + + // Set up cards - pulse power down first + gpiod_set_value_cansleep(pdn_gpio, 1); + usleep_range(1000, 10000); + gpiod_set_value_cansleep(pdn_gpio, 0); + usleep_range(20000, 30000); + + // Oscillator trim + snd_soc_component_write(dac[0], 0x1b, 0); + snd_soc_component_write(dac[1], 0x1b, 0); + usleep_range(60000, 80000); + + // Common setup + for (i = 0; i < 2; i++) { + // MCLK at 64fs, sample rate 44.1 or 48kHz + snd_soc_component_write(dac[i], 0x00, 0x60); + + // Set up for PBTL + snd_soc_component_write(dac[i], 0x19, 0x3A); + snd_soc_component_write(dac[i], 0x25, 0x01103245); + + // Master vol to -10db + snd_soc_component_write(dac[i], 0x07, 0x44); + } + // Inputs set to L and R respectively + snd_soc_component_write(dac[0], 0x20, 0x00017772); + snd_soc_component_write(dac[1], 0x20, 0x00107772); + + // Remove codec controls + for (i = 0; i < 2; i++) { + for (j = 0; j < 3; j++) { + char cname[256]; + + sprintf(cname, "%s %s", codec_ctl_pfx[i], + codec_ctl_name[j]); + kctl = snd_soc_card_get_kcontrol(card, cname); + if (!kctl) { + pr_info("Control %s not found\n", + cname); + } else { + kctl->vd[0].access = + SNDRV_CTL_ELEM_ACCESS_READWRITE; + snd_ctl_remove(card->snd_card, kctl); + } + } + } + + return 0; +} + +static int snd_pifi_40_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + + return snd_soc_dai_set_bclk_ratio(cpu_dai, 64); +} + +static struct snd_soc_ops snd_pifi_40_ops = { .hw_params = + snd_pifi_40_hw_params }; + +static struct snd_soc_dai_link_component pifi_40_codecs[] = { + { + .dai_name = "tas571x-hifi", + }, + { + .dai_name = "tas571x-hifi", + }, +}; + +SND_SOC_DAILINK_DEFS( + pifi_40_dai, DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC("tas571x.1-001a", "tas571x-hifi"), + COMP_CODEC("tas571x.1-001b", "tas571x-hifi")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link snd_pifi_40_dai[] = { + { + .name = "PiFi40", + .stream_name = "PiFi40", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBC_CFC, + .ops = &snd_pifi_40_ops, + .init = snd_pifi_40_init, + SND_SOC_DAILINK_REG(pifi_40_dai), + }, +}; + +// Machine driver +static struct snd_soc_card snd_pifi_40 = { + .name = "PiFi40", + .owner = THIS_MODULE, + .dai_link = snd_pifi_40_dai, + .num_links = ARRAY_SIZE(snd_pifi_40_dai), + .controls = pifi_40_controls, + .num_controls = ARRAY_SIZE(pifi_40_controls) +}; + +static void snd_pifi_40_pdn(struct snd_soc_card *card, int on) +{ + if (pdn_gpio) + gpiod_set_value_cansleep(pdn_gpio, on ? 0 : 1); +} + +static int snd_pifi_40_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &snd_pifi_40; + int ret = 0, i = 0; + + card->dev = &pdev->dev; + platform_set_drvdata(pdev, &snd_pifi_40); + + if (pdev->dev.of_node) { + struct device_node *i2s_node; + struct snd_soc_dai_link *dai; + + dai = &snd_pifi_40_dai[0]; + i2s_node = of_parse_phandle(pdev->dev.of_node, "i2s-controller", + 0); + if (i2s_node) { + for (i = 0; i < card->num_links; i++) { + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + } + } + + pifi_40_codecs[0].of_node = + of_parse_phandle(pdev->dev.of_node, "audio-codec", 0); + pifi_40_codecs[1].of_node = + of_parse_phandle(pdev->dev.of_node, "audio-codec", 1); + if (!pifi_40_codecs[0].of_node || !pifi_40_codecs[1].of_node) { + dev_err(&pdev->dev, + "Property 'audio-codec' missing or invalid\n"); + return -EINVAL; + } + + pdn_gpio = devm_gpiod_get_optional(&pdev->dev, "pdn", + GPIOD_OUT_LOW); + if (IS_ERR(pdn_gpio)) { + ret = PTR_ERR(pdn_gpio); + dev_err(&pdev->dev, "failed to get pdn gpio: %d\n", + ret); + return ret; + } + + ret = snd_soc_register_card(&snd_pifi_40); + if (ret < 0) { + dev_err(&pdev->dev, + "snd_soc_register_card() failed: %d\n", ret); + return ret; + } + + return 0; + } + + return -EINVAL; +} + +static void snd_pifi_40_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + + kfree(&card->drvdata); + snd_pifi_40_pdn(&snd_pifi_40, 0); + snd_soc_unregister_card(&snd_pifi_40); +} + +static const struct of_device_id snd_pifi_40_of_match[] = { + { + .compatible = "pifi,pifi-40", + }, + { /* sentinel */ }, +}; + +MODULE_DEVICE_TABLE(of, snd_pifi_40_of_match); + +static struct platform_driver snd_pifi_40_driver = { + .driver = { + .name = "snd-pifi-40", + .owner = THIS_MODULE, + .of_match_table = snd_pifi_40_of_match, + }, + .probe = snd_pifi_40_probe, + .remove = snd_pifi_40_remove, +}; + +module_platform_driver(snd_pifi_40_driver); + +MODULE_AUTHOR("David Knell "); +MODULE_DESCRIPTION("ALSA ASoC Machine Driver for PiFi-40"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/pisound.c b/sound/soc/bcm/pisound.c new file mode 100644 index 000000000000..72adf9f5aa87 --- /dev/null +++ b/sound/soc/bcm/pisound.c @@ -0,0 +1,1254 @@ +/* + * Pisound Linux kernel module. + * Copyright (C) 2016-2024 Vilniaus Blokas UAB, https://blokas.io/pisound + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 of the + * License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +static int pisnd_spi_init(struct device *dev); +static void pisnd_spi_uninit(void); + +static void pisnd_spi_flush(void); +static void pisnd_spi_start(void); +static uint8_t pisnd_spi_recv(uint8_t *buffer, uint8_t length); + +typedef void (*pisnd_spi_recv_cb)(void *data); +static void pisnd_spi_set_callback(pisnd_spi_recv_cb cb, void *data); + +static const char *pisnd_spi_get_serial(void); +static const char *pisnd_spi_get_id(void); +static const char *pisnd_spi_get_fw_version(void); +static const char *pisnd_spi_get_hw_version(void); + +static int pisnd_midi_init(struct snd_card *card); +static void pisnd_midi_uninit(void); + +enum task_e { + TASK_PROCESS = 0, +}; + +static void pisnd_schedule_process(enum task_e task); + +#define PISOUND_LOG_PREFIX "pisound: " + +#ifdef PISOUND_DEBUG +# define printd(...) pr_alert(PISOUND_LOG_PREFIX __VA_ARGS__) +#else +# define printd(...) do {} while (0) +#endif + +#define printe(...) pr_err(PISOUND_LOG_PREFIX __VA_ARGS__) +#define printi(...) pr_info(PISOUND_LOG_PREFIX __VA_ARGS__) + +static struct snd_rawmidi *g_rmidi; +static struct snd_rawmidi_substream *g_midi_output_substream; + +static int pisnd_output_open(struct snd_rawmidi_substream *substream) +{ + g_midi_output_substream = substream; + return 0; +} + +static int pisnd_output_close(struct snd_rawmidi_substream *substream) +{ + g_midi_output_substream = NULL; + return 0; +} + +static void pisnd_output_trigger( + struct snd_rawmidi_substream *substream, + int up + ) +{ + if (substream != g_midi_output_substream) { + printe("MIDI output trigger called for an unexpected stream!"); + return; + } + + if (!up) + return; + + pisnd_spi_start(); +} + +static void pisnd_output_drain(struct snd_rawmidi_substream *substream) +{ + pisnd_spi_flush(); +} + +static int pisnd_input_open(struct snd_rawmidi_substream *substream) +{ + return 0; +} + +static int pisnd_input_close(struct snd_rawmidi_substream *substream) +{ + return 0; +} + +static void pisnd_midi_recv_callback(void *substream) +{ + uint8_t data[128]; + uint8_t n = 0; + + while ((n = pisnd_spi_recv(data, sizeof(data)))) { + int res = snd_rawmidi_receive(substream, data, n); + (void)res; + printd("midi recv %u bytes, res = %d\n", n, res); + } +} + +static void pisnd_input_trigger(struct snd_rawmidi_substream *substream, int up) +{ + if (up) { + pisnd_spi_set_callback(pisnd_midi_recv_callback, substream); + pisnd_schedule_process(TASK_PROCESS); + } else { + pisnd_spi_set_callback(NULL, NULL); + } +} + +static const struct snd_rawmidi_ops pisnd_output_ops = { + .open = pisnd_output_open, + .close = pisnd_output_close, + .trigger = pisnd_output_trigger, + .drain = pisnd_output_drain, +}; + +static const struct snd_rawmidi_ops pisnd_input_ops = { + .open = pisnd_input_open, + .close = pisnd_input_close, + .trigger = pisnd_input_trigger, +}; + +static void pisnd_get_port_info( + struct snd_rawmidi *rmidi, + int number, + struct snd_seq_port_info *seq_port_info + ) +{ + seq_port_info->type = + SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | + SNDRV_SEQ_PORT_TYPE_HARDWARE | + SNDRV_SEQ_PORT_TYPE_PORT; + seq_port_info->midi_voices = 0; +} + +static struct snd_rawmidi_global_ops pisnd_global_ops = { + .get_port_info = pisnd_get_port_info, +}; + +static int pisnd_midi_init(struct snd_card *card) +{ + int err; + + g_midi_output_substream = NULL; + + err = snd_rawmidi_new(card, "pisound MIDI", 0, 1, 1, &g_rmidi); + + if (err < 0) { + printe("snd_rawmidi_new failed: %d\n", err); + return err; + } + + strcpy(g_rmidi->name, "pisound MIDI "); + strcat(g_rmidi->name, pisnd_spi_get_serial()); + + g_rmidi->info_flags = + SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + + g_rmidi->ops = &pisnd_global_ops; + + g_rmidi->private_data = (void *)0; + + snd_rawmidi_set_ops( + g_rmidi, + SNDRV_RAWMIDI_STREAM_OUTPUT, + &pisnd_output_ops + ); + + snd_rawmidi_set_ops( + g_rmidi, + SNDRV_RAWMIDI_STREAM_INPUT, + &pisnd_input_ops + ); + + return 0; +} + +static void pisnd_midi_uninit(void) +{ +} + +static void *g_recvData; +static pisnd_spi_recv_cb g_recvCallback; + +#define FIFO_SIZE 4096 + +static char g_serial_num[11]; +static char g_id[25]; +enum { MAX_VERSION_STR_LEN = 6 }; +static char g_fw_version[MAX_VERSION_STR_LEN]; +static char g_hw_version[MAX_VERSION_STR_LEN]; +static u32 g_spi_speed_hz; + +static uint8_t g_ledFlashDuration; +static bool g_ledFlashDurationChanged; + +DEFINE_KFIFO(spi_fifo_in, uint8_t, FIFO_SIZE); +DEFINE_KFIFO(spi_fifo_out, uint8_t, FIFO_SIZE); + +static struct gpio_desc *data_available; +static struct gpio_desc *spi_reset; + +static struct spi_device *pisnd_spi_device; + +static struct workqueue_struct *pisnd_workqueue; +static struct work_struct pisnd_work_process; + +static void pisnd_work_handler(struct work_struct *work); + +static void spi_transfer(const uint8_t *txbuf, uint8_t *rxbuf, int len); +static uint16_t spi_transfer16(uint16_t val); + +static int pisnd_init_workqueues(void) +{ + pisnd_workqueue = create_singlethread_workqueue("pisnd_workqueue"); + INIT_WORK(&pisnd_work_process, pisnd_work_handler); + + return 0; +} + +static void pisnd_uninit_workqueues(void) +{ + flush_workqueue(pisnd_workqueue); + destroy_workqueue(pisnd_workqueue); + + pisnd_workqueue = NULL; +} + +static bool pisnd_spi_has_more(void) +{ + return gpiod_get_value(data_available); +} + +static void pisnd_schedule_process(enum task_e task) +{ + if (pisnd_spi_device != NULL && + pisnd_workqueue != NULL && + !work_pending(&pisnd_work_process) + ) { + printd("schedule: has more = %d\n", pisnd_spi_has_more()); + if (task == TASK_PROCESS) + queue_work(pisnd_workqueue, &pisnd_work_process); + } +} + +static irqreturn_t data_available_interrupt_handler(int irq, void *dev_id) +{ + if (irq == gpiod_to_irq(data_available) && pisnd_spi_has_more()) { + printd("schedule from irq\n"); + pisnd_schedule_process(TASK_PROCESS); + } + + return IRQ_HANDLED; +} + +static uint16_t spi_transfer16(uint16_t val) +{ + uint8_t txbuf[2]; + uint8_t rxbuf[2]; + + if (!pisnd_spi_device) { + printe("pisnd_spi_device null, returning\n"); + return 0; + } + + txbuf[0] = val >> 8; + txbuf[1] = val & 0xff; + + spi_transfer(txbuf, rxbuf, sizeof(txbuf)); + + printd("received: %02x%02x\n", rxbuf[0], rxbuf[1]); + + return (rxbuf[0] << 8) | rxbuf[1]; +} + +static void spi_transfer(const uint8_t *txbuf, uint8_t *rxbuf, int len) +{ + int err; + struct spi_transfer transfer; + struct spi_message msg; + + memset(rxbuf, 0, len); + + if (!pisnd_spi_device) { + printe("pisnd_spi_device null, returning\n"); + return; + } + + spi_message_init(&msg); + + memset(&transfer, 0, sizeof(transfer)); + + transfer.tx_buf = txbuf; + transfer.rx_buf = rxbuf; + transfer.len = len; + transfer.speed_hz = g_spi_speed_hz; + transfer.delay.value = 10; + transfer.delay.unit = SPI_DELAY_UNIT_USECS; + + spi_message_add_tail(&transfer, &msg); + + err = spi_sync(pisnd_spi_device, &msg); + + if (err < 0) { + printe("spi_sync error %d\n", err); + return; + } + + printd("hasMore %d\n", pisnd_spi_has_more()); +} + +static int spi_read_bytes(char *dst, size_t length, uint8_t *bytesRead) +{ + uint16_t rx; + uint8_t size; + uint8_t i; + + memset(dst, 0, length); + *bytesRead = 0; + + rx = spi_transfer16(0); + if (!(rx >> 8)) + return -EINVAL; + + size = rx & 0xff; + + if (size > length) + return -EINVAL; + + for (i = 0; i < size; ++i) { + rx = spi_transfer16(0); + if (!(rx >> 8)) + return -EINVAL; + + dst[i] = rx & 0xff; + } + + *bytesRead = i; + + return 0; +} + +static int spi_device_match(struct device *dev, const void *data) +{ + struct spi_device *spi = container_of(dev, struct spi_device, dev); + + printd(" %s %s %dkHz %d bits mode=0x%02X\n", + spi->modalias, dev_name(dev), spi->max_speed_hz/1000, + spi->bits_per_word, spi->mode); + + if (strcmp("pisound-spi", spi->modalias) == 0) { + printi("\tFound!\n"); + return 1; + } + + printe("\tNot found!\n"); + return 0; +} + +static struct spi_device *pisnd_spi_find_device(void) +{ + struct device *dev; + + printi("Searching for spi device...\n"); + dev = bus_find_device(&spi_bus_type, NULL, NULL, spi_device_match); + if (dev != NULL) + return container_of(dev, struct spi_device, dev); + else + return NULL; +} + +static void pisnd_work_handler(struct work_struct *work) +{ + enum { TRANSFER_SIZE = 4 }; + enum { PISOUND_OUTPUT_BUFFER_SIZE_MILLIBYTES = 127 * 1000 }; + enum { MIDI_MILLIBYTES_PER_JIFFIE = (3125 * 1000) / HZ }; + int out_buffer_used_millibytes = 0; + unsigned long now; + uint8_t val; + uint8_t txbuf[TRANSFER_SIZE]; + uint8_t rxbuf[TRANSFER_SIZE]; + uint8_t midibuf[TRANSFER_SIZE]; + int i, n; + bool had_data; + + unsigned long last_transfer_at = jiffies; + + if (work == &pisnd_work_process) { + if (pisnd_spi_device == NULL) + return; + + do { + if (g_midi_output_substream && + kfifo_avail(&spi_fifo_out) >= sizeof(midibuf)) { + + n = snd_rawmidi_transmit_peek( + g_midi_output_substream, + midibuf, sizeof(midibuf) + ); + + if (n > 0) { + for (i = 0; i < n; ++i) + kfifo_put( + &spi_fifo_out, + midibuf[i] + ); + snd_rawmidi_transmit_ack( + g_midi_output_substream, + i + ); + } + } + + had_data = false; + memset(txbuf, 0, sizeof(txbuf)); + for (i = 0; i < sizeof(txbuf) && + ((out_buffer_used_millibytes+1000 < + PISOUND_OUTPUT_BUFFER_SIZE_MILLIBYTES) || + g_ledFlashDurationChanged); + i += 2) { + + val = 0; + + if (g_ledFlashDurationChanged) { + txbuf[i+0] = 0xf0; + txbuf[i+1] = g_ledFlashDuration; + g_ledFlashDuration = 0; + g_ledFlashDurationChanged = false; + } else if (kfifo_get(&spi_fifo_out, &val)) { + txbuf[i+0] = 0x0f; + txbuf[i+1] = val; + out_buffer_used_millibytes += 1000; + } + } + + spi_transfer(txbuf, rxbuf, sizeof(txbuf)); + /* Estimate the Pisound's MIDI output buffer usage, so + * that we don't overflow it. Space in the buffer should + * be becoming available at the UART MIDI byte transfer + * rate. + */ + now = jiffies; + if (now != last_transfer_at) { + out_buffer_used_millibytes -= + (now - last_transfer_at) * + MIDI_MILLIBYTES_PER_JIFFIE; + if (out_buffer_used_millibytes < 0) + out_buffer_used_millibytes = 0; + last_transfer_at = now; + } + + for (i = 0; i < sizeof(rxbuf); i += 2) { + if (rxbuf[i]) { + kfifo_put(&spi_fifo_in, rxbuf[i+1]); + if (kfifo_len(&spi_fifo_in) > 16 && + g_recvCallback) + g_recvCallback(g_recvData); + had_data = true; + } + } + } while (had_data + || !kfifo_is_empty(&spi_fifo_out) + || pisnd_spi_has_more() + || g_ledFlashDurationChanged + || out_buffer_used_millibytes != 0 + ); + + if (!kfifo_is_empty(&spi_fifo_in) && g_recvCallback) + g_recvCallback(g_recvData); + } +} + +static int pisnd_spi_gpio_init(struct device *dev) +{ + spi_reset = gpiod_get_index(dev, "reset", 1, GPIOD_ASIS); + data_available = gpiod_get_index(dev, "data_available", 0, GPIOD_ASIS); + + gpiod_direction_output(spi_reset, 1); + gpiod_direction_input(data_available); + + /* Reset the slave. */ + gpiod_set_value(spi_reset, false); + mdelay(1); + gpiod_set_value(spi_reset, true); + + /* Give time for spi slave to start. */ + mdelay(64); + + return 0; +} + +static void pisnd_spi_gpio_uninit(void) +{ + gpiod_set_value(spi_reset, false); + gpiod_put(spi_reset); + spi_reset = NULL; + + gpiod_put(data_available); + data_available = NULL; +} + +static int pisnd_spi_gpio_irq_init(struct device *dev) +{ + return request_threaded_irq( + gpiod_to_irq(data_available), NULL, + data_available_interrupt_handler, + IRQF_TIMER | IRQF_TRIGGER_RISING | IRQF_ONESHOT, + "data_available_int", + NULL + ); +} + +static void pisnd_spi_gpio_irq_uninit(void) +{ + free_irq(gpiod_to_irq(data_available), NULL); +} + +static int spi_read_info(void) +{ + uint16_t tmp; + uint8_t count; + uint8_t n; + uint8_t i; + uint8_t j; + char buffer[257]; + int ret; + char *p; + + memset(g_serial_num, 0, sizeof(g_serial_num)); + memset(g_fw_version, 0, sizeof(g_fw_version)); + strcpy(g_hw_version, "1.0"); // Assume 1.0 hw version. + memset(g_id, 0, sizeof(g_id)); + + tmp = spi_transfer16(0); + + if (!(tmp >> 8)) + return -EINVAL; + + count = tmp & 0xff; + + for (i = 0; i < count; ++i) { + memset(buffer, 0, sizeof(buffer)); + ret = spi_read_bytes(buffer, sizeof(buffer)-1, &n); + + if (ret < 0) + return ret; + + switch (i) { + case 0: + if (n != 2) + return -EINVAL; + + snprintf( + g_fw_version, + MAX_VERSION_STR_LEN, + "%x.%02x", + buffer[0], + buffer[1] + ); + + g_fw_version[MAX_VERSION_STR_LEN-1] = '\0'; + break; + case 3: + if (n != 2) + return -EINVAL; + + snprintf( + g_hw_version, + MAX_VERSION_STR_LEN, + "%x.%x", + buffer[0], + buffer[1] + ); + + g_hw_version[MAX_VERSION_STR_LEN-1] = '\0'; + break; + case 1: + if (n >= sizeof(g_serial_num)) + return -EINVAL; + + memcpy(g_serial_num, buffer, sizeof(g_serial_num)); + break; + case 2: + { + if (n*2 >= sizeof(g_id)) + return -EINVAL; + + p = g_id; + for (j = 0; j < n; ++j) + p += sprintf(p, "%02x", buffer[j]); + + *p = '\0'; + } + break; + default: + break; + } + } + + return 0; +} + +static int pisnd_spi_init(struct device *dev) +{ + int ret; + struct spi_device *spi; + + memset(g_serial_num, 0, sizeof(g_serial_num)); + memset(g_id, 0, sizeof(g_id)); + memset(g_fw_version, 0, sizeof(g_fw_version)); + memset(g_hw_version, 0, sizeof(g_hw_version)); + + g_spi_speed_hz = 150000; + if (dev->of_node) { + struct device_node *spi_node; + + spi_node = of_parse_phandle( + dev->of_node, + "spi-controller", + 0 + ); + + if (spi_node) { + ret = of_property_read_u32(spi_node, "spi-speed-hz", &g_spi_speed_hz); + if (ret != 0) + printe("Failed reading spi-speed-hz! (%d)\n", ret); + + of_node_put(spi_node); + } + } + printi("Using SPI speed: %u\n", g_spi_speed_hz); + + spi = pisnd_spi_find_device(); + + if (spi != NULL) { + printd("initializing spi!\n"); + pisnd_spi_device = spi; + ret = spi_setup(pisnd_spi_device); + } else { + printe("SPI device not found, deferring!\n"); + return -EPROBE_DEFER; + } + + ret = pisnd_spi_gpio_init(dev); + + if (ret < 0) { + printe("SPI GPIO init failed: %d\n", ret); + spi_dev_put(pisnd_spi_device); + pisnd_spi_device = NULL; + pisnd_spi_gpio_uninit(); + return ret; + } + + ret = spi_read_info(); + + if (ret < 0) { + printe("Reading card info failed: %d\n", ret); + spi_dev_put(pisnd_spi_device); + pisnd_spi_device = NULL; + pisnd_spi_gpio_uninit(); + return ret; + } + + /* Flash the LEDs. */ + spi_transfer16(0xf008); + + ret = pisnd_spi_gpio_irq_init(dev); + if (ret < 0) { + printe("SPI irq request failed: %d\n", ret); + spi_dev_put(pisnd_spi_device); + pisnd_spi_device = NULL; + pisnd_spi_gpio_irq_uninit(); + pisnd_spi_gpio_uninit(); + } + + ret = pisnd_init_workqueues(); + if (ret != 0) { + printe("Workqueue initialization failed: %d\n", ret); + spi_dev_put(pisnd_spi_device); + pisnd_spi_device = NULL; + pisnd_spi_gpio_irq_uninit(); + pisnd_spi_gpio_uninit(); + pisnd_uninit_workqueues(); + return ret; + } + + if (pisnd_spi_has_more()) { + printd("data is available, scheduling from init\n"); + pisnd_schedule_process(TASK_PROCESS); + } + + return 0; +} + +static void pisnd_spi_uninit(void) +{ + pisnd_uninit_workqueues(); + + spi_dev_put(pisnd_spi_device); + pisnd_spi_device = NULL; + + pisnd_spi_gpio_irq_uninit(); + pisnd_spi_gpio_uninit(); +} + +static void pisnd_spi_flash_leds(uint8_t duration) +{ + g_ledFlashDuration = duration; + g_ledFlashDurationChanged = true; + printd("schedule from spi_flash_leds\n"); + pisnd_schedule_process(TASK_PROCESS); +} + +static void pisnd_spi_flush(void) +{ + while (!kfifo_is_empty(&spi_fifo_out)) { + pisnd_spi_start(); + flush_workqueue(pisnd_workqueue); + } +} + +static void pisnd_spi_start(void) +{ + printd("schedule from spi_start\n"); + pisnd_schedule_process(TASK_PROCESS); +} + +static uint8_t pisnd_spi_recv(uint8_t *buffer, uint8_t length) +{ + return kfifo_out(&spi_fifo_in, buffer, length); +} + +static void pisnd_spi_set_callback(pisnd_spi_recv_cb cb, void *data) +{ + g_recvData = data; + g_recvCallback = cb; +} + +static const char *pisnd_spi_get_serial(void) +{ + return g_serial_num; +} + +static const char *pisnd_spi_get_id(void) +{ + return g_id; +} + +static const char *pisnd_spi_get_fw_version(void) +{ + return g_fw_version; +} + +static const char *pisnd_spi_get_hw_version(void) +{ + return g_hw_version; +} + +static const struct of_device_id pisound_of_match[] = { + { .compatible = "blokaslabs,pisound", }, + { .compatible = "blokaslabs,pisound-spi", }, + {}, +}; + +enum { + SWITCH = 0, + VOLUME = 1, +}; + +static int pisnd_ctl_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + if (kcontrol->private_value == SWITCH) { + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; + } else if (kcontrol->private_value == VOLUME) { + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 100; + return 0; + } + return -EINVAL; +} + +static int pisnd_ctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + if (kcontrol->private_value == SWITCH) { + ucontrol->value.integer.value[0] = 1; + return 0; + } else if (kcontrol->private_value == VOLUME) { + ucontrol->value.integer.value[0] = 100; + return 0; + } + + return -EINVAL; +} + +static struct snd_kcontrol_new pisnd_ctl[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Switch", + .index = 0, + .private_value = SWITCH, + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = pisnd_ctl_info, + .get = pisnd_ctl_get, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Volume", + .index = 0, + .private_value = VOLUME, + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = pisnd_ctl_info, + .get = pisnd_ctl_get, + }, +}; + +static int pisnd_ctl_init(struct snd_card *card) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(pisnd_ctl); ++i) { + err = snd_ctl_add(card, snd_ctl_new1(&pisnd_ctl[i], NULL)); + if (err < 0) + return err; + } + + return 0; +} + +static int pisnd_ctl_uninit(void) +{ + return 0; +} + +static struct gpio_desc *osr0, *osr1, *osr2; +static struct gpio_desc *reset; + +static int pisnd_hw_params( + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params + ) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + + /* Pisound runs on fixed 32 clock counts per channel, + * as generated by the master ADC. + */ + snd_soc_dai_set_bclk_ratio(cpu_dai, 32*2); + + printd("rate = %d\n", params_rate(params)); + printd("ch = %d\n", params_channels(params)); + printd("bits = %u\n", + snd_pcm_format_width(params_format(params))); + printd("format = %d\n", params_format(params)); + + gpiod_set_value(reset, false); + + switch (params_rate(params)) { + case 48000: + gpiod_set_value(osr0, true); + gpiod_set_value(osr1, false); + gpiod_set_value(osr2, false); + break; + case 96000: + gpiod_set_value(osr0, true); + gpiod_set_value(osr1, false); + gpiod_set_value(osr2, true); + break; + case 192000: + gpiod_set_value(osr0, true); + gpiod_set_value(osr1, true); + gpiod_set_value(osr2, true); + break; + default: + printe("Unsupported rate %u!\n", params_rate(params)); + return -EINVAL; + } + + gpiod_set_value(reset, true); + + return 0; +} + +static unsigned int rates[3] = { + 48000, 96000, 192000 +}; + +static struct snd_pcm_hw_constraint_list constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, +}; + +static int pisnd_startup(struct snd_pcm_substream *substream) +{ + int err = snd_pcm_hw_constraint_list( + substream->runtime, + 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_rates + ); + + if (err < 0) + return err; + + err = snd_pcm_hw_constraint_single( + substream->runtime, + SNDRV_PCM_HW_PARAM_CHANNELS, + 2 + ); + + if (err < 0) + return err; + + err = snd_pcm_hw_constraint_mask64( + substream->runtime, + SNDRV_PCM_HW_PARAM_FORMAT, + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE + ); + + if (err < 0) + return err; + + return 0; +} + +static const struct snd_soc_ops pisnd_ops = { + .startup = pisnd_startup, + .hw_params = pisnd_hw_params, +}; + +SND_SOC_DAILINK_DEFS(pisnd, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), + DAILINK_COMP_ARRAY(COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0"))); + +static struct snd_soc_dai_link pisnd_dai[] = { + { + .name = "pisound", + .stream_name = "pisound", + .dai_fmt = + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBP_CFP, + .ops = &pisnd_ops, + SND_SOC_DAILINK_REG(pisnd), + }, +}; + +static int pisnd_card_probe(struct snd_soc_card *card) +{ + int err = pisnd_midi_init(card->snd_card); + + if (err < 0) { + printe("pisnd_midi_init failed: %d\n", err); + return err; + } + + err = pisnd_ctl_init(card->snd_card); + if (err < 0) { + printe("pisnd_ctl_init failed: %d\n", err); + return err; + } + + return 0; +} + +static int pisnd_card_remove(struct snd_soc_card *card) +{ + pisnd_ctl_uninit(); + pisnd_midi_uninit(); + return 0; +} + +static struct snd_soc_card pisnd_card = { + .name = "pisound", + .owner = THIS_MODULE, + .dai_link = pisnd_dai, + .num_links = ARRAY_SIZE(pisnd_dai), + .probe = pisnd_card_probe, + .remove = pisnd_card_remove, +}; + +static int pisnd_init_gpio(struct device *dev) +{ + osr0 = gpiod_get_index(dev, "osr", 0, GPIOD_ASIS); + osr1 = gpiod_get_index(dev, "osr", 1, GPIOD_ASIS); + osr2 = gpiod_get_index(dev, "osr", 2, GPIOD_ASIS); + + reset = gpiod_get_index(dev, "reset", 0, GPIOD_ASIS); + + gpiod_direction_output(osr0, 1); + gpiod_direction_output(osr1, 1); + gpiod_direction_output(osr2, 1); + gpiod_direction_output(reset, 1); + + gpiod_set_value(reset, false); + gpiod_set_value(osr0, true); + gpiod_set_value(osr1, false); + gpiod_set_value(osr2, false); + gpiod_set_value(reset, true); + + return 0; +} + +static int pisnd_uninit_gpio(void) +{ + int i; + + struct gpio_desc **gpios[] = { + &osr0, &osr1, &osr2, &reset, + }; + + for (i = 0; i < ARRAY_SIZE(gpios); ++i) { + if (*gpios[i] == NULL) { + printd("weird, GPIO[%d] is NULL already\n", i); + continue; + } + + gpiod_put(*gpios[i]); + *gpios[i] = NULL; + } + + return 0; +} + +static struct kobject *pisnd_kobj; + +static ssize_t pisnd_serial_show( + struct kobject *kobj, + struct kobj_attribute *attr, + char *buf + ) +{ + return sprintf(buf, "%s\n", pisnd_spi_get_serial()); +} + +static ssize_t pisnd_id_show( + struct kobject *kobj, + struct kobj_attribute *attr, + char *buf + ) +{ + return sprintf(buf, "%s\n", pisnd_spi_get_id()); +} + +static ssize_t pisnd_fw_version_show( + struct kobject *kobj, + struct kobj_attribute *attr, + char *buf + ) +{ + return sprintf(buf, "%s\n", pisnd_spi_get_fw_version()); +} + +static ssize_t pisnd_hw_version_show( + struct kobject *kobj, + struct kobj_attribute *attr, + char *buf +) +{ + return sprintf(buf, "%s\n", pisnd_spi_get_hw_version()); +} + +static ssize_t pisnd_led_store( + struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, + size_t length + ) +{ + uint32_t timeout; + int err; + + err = kstrtou32(buf, 10, &timeout); + + if (err == 0 && timeout <= 255) + pisnd_spi_flash_leds(timeout); + + return length; +} + +static struct kobj_attribute pisnd_serial_attribute = + __ATTR(serial, 0444, pisnd_serial_show, NULL); +static struct kobj_attribute pisnd_id_attribute = + __ATTR(id, 0444, pisnd_id_show, NULL); +static struct kobj_attribute pisnd_fw_version_attribute = + __ATTR(version, 0444, pisnd_fw_version_show, NULL); +static struct kobj_attribute pisnd_hw_version_attribute = +__ATTR(hw_version, 0444, pisnd_hw_version_show, NULL); +static struct kobj_attribute pisnd_led_attribute = + __ATTR(led, 0644, NULL, pisnd_led_store); + +static struct attribute *attrs[] = { + &pisnd_serial_attribute.attr, + &pisnd_id_attribute.attr, + &pisnd_fw_version_attribute.attr, + &pisnd_hw_version_attribute.attr, + &pisnd_led_attribute.attr, + NULL +}; + +static struct attribute_group attr_group = { .attrs = attrs }; + +static int pisnd_probe(struct platform_device *pdev) +{ + int ret = 0; + int i; + + ret = pisnd_spi_init(&pdev->dev); + if (ret < 0) { + printe("pisnd_spi_init failed: %d\n", ret); + return ret; + } + + printi("Detected Pisound card:\n"); + printi("\tSerial: %s\n", pisnd_spi_get_serial()); + printi("\tFirmware Version: %s\n", pisnd_spi_get_fw_version()); + printi("\tHardware Version: %s\n", pisnd_spi_get_hw_version()); + printi("\tId: %s\n", pisnd_spi_get_id()); + + pisnd_kobj = kobject_create_and_add("pisound", kernel_kobj); + if (!pisnd_kobj) { + pisnd_spi_uninit(); + return -ENOMEM; + } + + ret = sysfs_create_group(pisnd_kobj, &attr_group); + if (ret < 0) { + pisnd_spi_uninit(); + kobject_put(pisnd_kobj); + return -ENOMEM; + } + + pisnd_init_gpio(&pdev->dev); + pisnd_card.dev = &pdev->dev; + + if (pdev->dev.of_node) { + struct device_node *i2s_node; + + i2s_node = of_parse_phandle( + pdev->dev.of_node, + "i2s-controller", + 0 + ); + + for (i = 0; i < pisnd_card.num_links; ++i) { + struct snd_soc_dai_link *dai = &pisnd_dai[i]; + + if (i2s_node) { + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + dai->stream_name = pisnd_spi_get_serial(); + } + } + } + + ret = snd_soc_register_card(&pisnd_card); + + if (ret < 0) { + if (ret != -EPROBE_DEFER) + printe("snd_soc_register_card() failed: %d\n", ret); + pisnd_uninit_gpio(); + kobject_put(pisnd_kobj); + pisnd_spi_uninit(); + } + + return ret; +} + +static void pisnd_remove(struct platform_device *pdev) +{ + printi("Unloading.\n"); + + if (pisnd_kobj) { + kobject_put(pisnd_kobj); + pisnd_kobj = NULL; + } + + pisnd_spi_uninit(); + + /* Turn off */ + gpiod_set_value(reset, false); + pisnd_uninit_gpio(); + + snd_soc_unregister_card(&pisnd_card); +} + +MODULE_DEVICE_TABLE(of, pisound_of_match); + +static struct platform_driver pisnd_driver = { + .driver = { + .name = "snd-rpi-pisound", + .owner = THIS_MODULE, + .of_match_table = pisound_of_match, + }, + .probe = pisnd_probe, + .remove = pisnd_remove, +}; + +module_platform_driver(pisnd_driver); + +MODULE_AUTHOR("Giedrius Trainavicius "); +MODULE_DESCRIPTION("ASoC Driver for Pisound, https://blokas.io/pisound"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/rpi-cirrus.c b/sound/soc/bcm/rpi-cirrus.c new file mode 100644 index 000000000000..09383e604346 --- /dev/null +++ b/sound/soc/bcm/rpi-cirrus.c @@ -0,0 +1,1027 @@ +/* + * ASoC machine driver for Cirrus Logic Audio Card + * (with WM5102 and WM8804 codecs) + * + * Copyright 2015-2017 Matthias Reichl + * + * Based on rpi-cirrus-sound-pi driver (c) Wolfson / Cirrus Logic Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include "../codecs/wm5102.h" +#include "../codecs/wm8804.h" + +#define WM8804_CLKOUT_HZ 12000000 + +#define RPI_CIRRUS_DEFAULT_RATE 44100 +#define WM5102_MAX_SYSCLK_1 49152000 /* max sysclk for 4K family */ +#define WM5102_MAX_SYSCLK_2 45158400 /* max sysclk for 11.025K family */ + +static inline unsigned int calc_sysclk(unsigned int rate) +{ + return (rate % 4000) ? WM5102_MAX_SYSCLK_2 : WM5102_MAX_SYSCLK_1; +} + +enum { + DAI_WM5102 = 0, + DAI_WM8804, +}; + +struct rpi_cirrus_priv { + /* mutex for synchronzing FLL1 access with DAPM */ + struct mutex lock; + unsigned int card_rate; + int sync_path_enable; + int fll1_freq; /* negative means RefClock in spdif rx case */ + + /* track hw params/free for substreams */ + unsigned int params_set; + unsigned int min_rate_idx, max_rate_idx; + unsigned char iec958_status[4]; +}; + +/* helper functions */ +static inline struct snd_soc_pcm_runtime *get_wm5102_runtime( + struct snd_soc_card *card) { + return snd_soc_get_pcm_runtime(card, &card->dai_link[DAI_WM5102]); +} + +static inline struct snd_soc_pcm_runtime *get_wm8804_runtime( + struct snd_soc_card *card) { + return snd_soc_get_pcm_runtime(card, &card->dai_link[DAI_WM8804]); +} + + +struct rate_info { + unsigned int value; + char *text; +}; + +static struct rate_info min_rates[] = { + { 0, "off"}, + { 32000, "32kHz"}, + { 44100, "44.1kHz"} +}; + +#define NUM_MIN_RATES ARRAY_SIZE(min_rates) + +static int rpi_cirrus_min_rate_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = NUM_MIN_RATES; + + if (uinfo->value.enumerated.item >= NUM_MIN_RATES) + uinfo->value.enumerated.item = NUM_MIN_RATES - 1; + strcpy(uinfo->value.enumerated.name, + min_rates[uinfo->value.enumerated.item].text); + return 0; +} + +static int rpi_cirrus_min_rate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card); + + ucontrol->value.enumerated.item[0] = priv->min_rate_idx; + return 0; +} + +static int rpi_cirrus_min_rate_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card); + int changed = 0; + + if (priv->min_rate_idx != ucontrol->value.enumerated.item[0]) { + changed = 1; + priv->min_rate_idx = ucontrol->value.enumerated.item[0]; + } + + return changed; +} + +static struct rate_info max_rates[] = { + { 0, "off"}, + { 48000, "48kHz"}, + { 96000, "96kHz"} +}; + +#define NUM_MAX_RATES ARRAY_SIZE(max_rates) + +static int rpi_cirrus_max_rate_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = NUM_MAX_RATES; + if (uinfo->value.enumerated.item >= NUM_MAX_RATES) + uinfo->value.enumerated.item = NUM_MAX_RATES - 1; + strcpy(uinfo->value.enumerated.name, + max_rates[uinfo->value.enumerated.item].text); + return 0; +} + +static int rpi_cirrus_max_rate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card); + + ucontrol->value.enumerated.item[0] = priv->max_rate_idx; + return 0; +} + +static int rpi_cirrus_max_rate_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card); + int changed = 0; + + if (priv->max_rate_idx != ucontrol->value.enumerated.item[0]) { + changed = 1; + priv->max_rate_idx = ucontrol->value.enumerated.item[0]; + } + + return changed; +} + +static int rpi_cirrus_spdif_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int rpi_cirrus_spdif_playback_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card); + int i; + + for (i = 0; i < 4; i++) + ucontrol->value.iec958.status[i] = priv->iec958_status[i]; + + return 0; +} + +static int rpi_cirrus_spdif_playback_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct snd_soc_component *wm8804_component = + snd_soc_rtd_to_codec(get_wm8804_runtime(card), 0)->component; + struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card); + unsigned char *stat = priv->iec958_status; + unsigned char *ctrl_stat = ucontrol->value.iec958.status; + unsigned int mask; + int i, changed = 0; + + for (i = 0; i < 4; i++) { + mask = (i == 3) ? 0x3f : 0xff; + if ((ctrl_stat[i] & mask) != (stat[i] & mask)) { + changed = 1; + stat[i] = ctrl_stat[i] & mask; + snd_soc_component_update_bits(wm8804_component, + WM8804_SPDTX1 + i, mask, stat[i]); + } + } + + return changed; +} + +static int rpi_cirrus_spdif_mask_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.iec958.status[0] = 0xff; + ucontrol->value.iec958.status[1] = 0xff; + ucontrol->value.iec958.status[2] = 0xff; + ucontrol->value.iec958.status[3] = 0x3f; + + return 0; +} + +static int rpi_cirrus_spdif_capture_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct snd_soc_component *wm8804_component = + snd_soc_rtd_to_codec(get_wm8804_runtime(card), 0)->component; + unsigned int val, mask; + int i; + + for (i = 0; i < 4; i++) { + val = snd_soc_component_read(wm8804_component, + WM8804_RXCHAN1 + i); + mask = (i == 3) ? 0x3f : 0xff; + ucontrol->value.iec958.status[i] = val & mask; + } + + return 0; +} + +#define SPDIF_FLAG_CTRL(desc, reg, bit, invert) \ +{ \ + .access = SNDRV_CTL_ELEM_ACCESS_READ \ + | SNDRV_CTL_ELEM_ACCESS_VOLATILE, \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, NONE) \ + desc " Flag", \ + .info = snd_ctl_boolean_mono_info, \ + .get = rpi_cirrus_spdif_status_flag_get, \ + .private_value = \ + (bit) | ((reg) << 8) | ((invert) << 16) \ +} + +static int rpi_cirrus_spdif_status_flag_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct snd_soc_component *wm8804_component = + snd_soc_rtd_to_codec(get_wm8804_runtime(card), 0)->component; + + unsigned int bit = kcontrol->private_value & 0xff; + unsigned int reg = (kcontrol->private_value >> 8) & 0xff; + unsigned int invert = (kcontrol->private_value >> 16) & 0xff; + unsigned int val; + bool flag; + + val = snd_soc_component_read(wm8804_component, reg); + + flag = val & (1 << bit); + + ucontrol->value.integer.value[0] = invert ? !flag : flag; + + return 0; +} + +static const char * const recovered_frequency_texts[] = { + "176.4/192 kHz", + "88.2/96 kHz", + "44.1/48 kHz", + "32 kHz" +}; + +#define NUM_RECOVERED_FREQUENCIES \ + ARRAY_SIZE(recovered_frequency_texts) + +static int rpi_cirrus_recovered_frequency_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = NUM_RECOVERED_FREQUENCIES; + if (uinfo->value.enumerated.item >= NUM_RECOVERED_FREQUENCIES) + uinfo->value.enumerated.item = NUM_RECOVERED_FREQUENCIES - 1; + strcpy(uinfo->value.enumerated.name, + recovered_frequency_texts[uinfo->value.enumerated.item]); + return 0; +} + +static int rpi_cirrus_recovered_frequency_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct snd_soc_component *wm8804_component = + snd_soc_rtd_to_codec(get_wm8804_runtime(card), 0)->component; + unsigned int val; + + val = snd_soc_component_read(wm8804_component, WM8804_SPDSTAT); + + ucontrol->value.enumerated.item[0] = (val >> 4) & 0x03; + return 0; +} + +static const struct snd_kcontrol_new rpi_cirrus_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Min Sample Rate", + .info = rpi_cirrus_min_rate_info, + .get = rpi_cirrus_min_rate_get, + .put = rpi_cirrus_min_rate_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Max Sample Rate", + .info = rpi_cirrus_max_rate_info, + .get = rpi_cirrus_max_rate_get, + .put = rpi_cirrus_max_rate_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), + .info = rpi_cirrus_spdif_info, + .get = rpi_cirrus_spdif_playback_get, + .put = rpi_cirrus_spdif_playback_put, + }, + { + .access = SNDRV_CTL_ELEM_ACCESS_READ + | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, DEFAULT), + .info = rpi_cirrus_spdif_info, + .get = rpi_cirrus_spdif_capture_get, + }, + { + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, MASK), + .info = rpi_cirrus_spdif_info, + .get = rpi_cirrus_spdif_mask_get, + }, + { + .access = SNDRV_CTL_ELEM_ACCESS_READ + | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, NONE) + "Recovered Frequency", + .info = rpi_cirrus_recovered_frequency_info, + .get = rpi_cirrus_recovered_frequency_get, + }, + SPDIF_FLAG_CTRL("Audio", WM8804_SPDSTAT, 0, 1), + SPDIF_FLAG_CTRL("Non-PCM", WM8804_SPDSTAT, 1, 0), + SPDIF_FLAG_CTRL("Copyright", WM8804_SPDSTAT, 2, 1), + SPDIF_FLAG_CTRL("De-Emphasis", WM8804_SPDSTAT, 3, 0), + SPDIF_FLAG_CTRL("Lock", WM8804_SPDSTAT, 6, 1), + SPDIF_FLAG_CTRL("Invalid", WM8804_INTSTAT, 1, 0), + SPDIF_FLAG_CTRL("TransErr", WM8804_INTSTAT, 3, 0), +}; + +static const char * const linein_micbias_texts[] = { + "off", "on", +}; + +static SOC_ENUM_SINGLE_VIRT_DECL(linein_micbias_enum, + linein_micbias_texts); + +static const struct snd_kcontrol_new linein_micbias_mux = + SOC_DAPM_ENUM("Route", linein_micbias_enum); + +static int rpi_cirrus_spdif_rx_enable_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event); + +const struct snd_soc_dapm_widget rpi_cirrus_dapm_widgets[] = { + SND_SOC_DAPM_MIC("DMIC", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_INPUT("Line Input"), + SND_SOC_DAPM_MIC("Line Input with Micbias", NULL), + SND_SOC_DAPM_MUX("Line Input Micbias", SND_SOC_NOPM, 0, 0, + &linein_micbias_mux), + SND_SOC_DAPM_INPUT("dummy SPDIF in"), + SND_SOC_DAPM_PGA_E("dummy SPDIFRX", SND_SOC_NOPM, 0, 0, NULL, 0, + rpi_cirrus_spdif_rx_enable_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_INPUT("Dummy Input"), + SND_SOC_DAPM_OUTPUT("Dummy Output"), +}; + +const struct snd_soc_dapm_route rpi_cirrus_dapm_routes[] = { + { "IN1L", NULL, "Headset Mic" }, + { "IN1R", NULL, "Headset Mic" }, + { "Headset Mic", NULL, "MICBIAS1" }, + + { "IN2L", NULL, "DMIC" }, + { "IN2R", NULL, "DMIC" }, + { "DMIC", NULL, "MICBIAS2" }, + + { "IN3L", NULL, "Line Input Micbias" }, + { "IN3R", NULL, "Line Input Micbias" }, + + { "Line Input Micbias", "off", "Line Input" }, + { "Line Input Micbias", "on", "Line Input with Micbias" }, + + /* Make sure MICVDD is enabled, otherwise we get noise */ + { "Line Input", NULL, "MICVDD" }, + { "Line Input with Micbias", NULL, "MICBIAS3" }, + + /* Dummy routes to check whether SPDIF RX is enabled or not */ + {"dummy SPDIFRX", NULL, "dummy SPDIF in"}, + {"AIFTX", NULL, "dummy SPDIFRX"}, + + /* + * Dummy routes to keep wm5102 from staying off on + * playback/capture if all mixers are off. + */ + { "Dummy Output", NULL, "AIF1RX1" }, + { "Dummy Output", NULL, "AIF1RX2" }, + { "AIF1TX1", NULL, "Dummy Input" }, + { "AIF1TX2", NULL, "Dummy Input" }, +}; + +static int rpi_cirrus_clear_flls(struct snd_soc_card *card, + struct snd_soc_component *wm5102_component) { + + int ret1, ret2; + + ret1 = snd_soc_component_set_pll(wm5102_component, + WM5102_FLL1, ARIZONA_FLL_SRC_NONE, 0, 0); + ret2 = snd_soc_component_set_pll(wm5102_component, + WM5102_FLL1_REFCLK, ARIZONA_FLL_SRC_NONE, 0, 0); + + if (ret1) { + dev_warn(card->dev, + "setting FLL1 to zero failed: %d\n", ret1); + return ret1; + } + if (ret2) { + dev_warn(card->dev, + "setting FLL1_REFCLK to zero failed: %d\n", ret2); + return ret2; + } + return 0; +} + +static int rpi_cirrus_set_fll(struct snd_soc_card *card, + struct snd_soc_component *wm5102_component, unsigned int clk_freq) +{ + int ret = snd_soc_component_set_pll(wm5102_component, + WM5102_FLL1, + ARIZONA_CLK_SRC_MCLK1, + WM8804_CLKOUT_HZ, + clk_freq); + if (ret) + dev_err(card->dev, "Failed to set FLL1 to %d: %d\n", + clk_freq, ret); + + usleep_range(1000, 2000); + return ret; +} + +static int rpi_cirrus_set_fll_refclk(struct snd_soc_card *card, + struct snd_soc_component *wm5102_component, + unsigned int clk_freq, unsigned int aif2_freq) +{ + int ret = snd_soc_component_set_pll(wm5102_component, + WM5102_FLL1_REFCLK, + ARIZONA_CLK_SRC_MCLK1, + WM8804_CLKOUT_HZ, + clk_freq); + if (ret) { + dev_err(card->dev, + "Failed to set FLL1_REFCLK to %d: %d\n", + clk_freq, ret); + return ret; + } + + ret = snd_soc_component_set_pll(wm5102_component, + WM5102_FLL1, + ARIZONA_CLK_SRC_AIF2BCLK, + aif2_freq, clk_freq); + if (ret) + dev_err(card->dev, + "Failed to set FLL1 with Sync Clock %d to %d: %d\n", + aif2_freq, clk_freq, ret); + + usleep_range(1000, 2000); + return ret; +} + +static int rpi_cirrus_spdif_rx_enable_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_card *card = w->dapm->card; + struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_component *wm5102_component = + snd_soc_rtd_to_codec(get_wm5102_runtime(card), 0)->component; + + unsigned int clk_freq, aif2_freq; + int ret = 0; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + mutex_lock(&priv->lock); + + /* Enable sync path in case of SPDIF capture use case */ + + clk_freq = calc_sysclk(priv->card_rate); + aif2_freq = 64 * priv->card_rate; + + dev_dbg(card->dev, + "spdif_rx: changing FLL1 to use Ref Clock clk: %d spdif: %d\n", + clk_freq, aif2_freq); + + ret = rpi_cirrus_clear_flls(card, wm5102_component); + if (ret) { + dev_err(card->dev, "spdif_rx: failed to clear FLLs\n"); + goto out; + } + + ret = rpi_cirrus_set_fll_refclk(card, wm5102_component, + clk_freq, aif2_freq); + + if (ret) { + dev_err(card->dev, "spdif_rx: failed to set FLLs\n"); + goto out; + } + + /* set to negative to indicate we're doing spdif rx */ + priv->fll1_freq = -clk_freq; + priv->sync_path_enable = 1; + break; + + case SND_SOC_DAPM_POST_PMD: + mutex_lock(&priv->lock); + priv->sync_path_enable = 0; + break; + + default: + return 0; + } + +out: + mutex_unlock(&priv->lock); + return ret; +} + +static int rpi_cirrus_set_bias_level(struct snd_soc_card *card, + struct snd_soc_dapm_context *dapm, + enum snd_soc_bias_level level) +{ + struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_pcm_runtime *wm5102_runtime = get_wm5102_runtime(card); + struct snd_soc_component *wm5102_component = + snd_soc_rtd_to_codec(wm5102_runtime, 0)->component; + + int ret = 0; + unsigned int clk_freq; + + if (dapm->dev != snd_soc_rtd_to_codec(wm5102_runtime, 0)->dev) + return 0; + + switch (level) { + case SND_SOC_BIAS_PREPARE: + if (dapm->bias_level == SND_SOC_BIAS_ON) + break; + + mutex_lock(&priv->lock); + + if (!priv->sync_path_enable) { + clk_freq = calc_sysclk(priv->card_rate); + + dev_dbg(card->dev, + "set_bias: changing FLL1 from %d to %d\n", + priv->fll1_freq, clk_freq); + + ret = rpi_cirrus_set_fll(card, + wm5102_component, clk_freq); + if (ret) + dev_err(card->dev, + "set_bias: Failed to set FLL1\n"); + else + priv->fll1_freq = clk_freq; + } + mutex_unlock(&priv->lock); + break; + default: + break; + } + + return ret; +} + +static int rpi_cirrus_set_bias_level_post(struct snd_soc_card *card, + struct snd_soc_dapm_context *dapm, + enum snd_soc_bias_level level) +{ + struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_pcm_runtime *wm5102_runtime = get_wm5102_runtime(card); + struct snd_soc_component *wm5102_component = + snd_soc_rtd_to_codec(wm5102_runtime, 0)->component; + + if (dapm->dev != snd_soc_rtd_to_codec(wm5102_runtime, 0)->dev) + return 0; + + switch (level) { + case SND_SOC_BIAS_STANDBY: + mutex_lock(&priv->lock); + + dev_dbg(card->dev, + "set_bias_post: changing FLL1 from %d to off\n", + priv->fll1_freq); + + if (rpi_cirrus_clear_flls(card, wm5102_component)) + dev_err(card->dev, + "set_bias_post: failed to clear FLLs\n"); + else + priv->fll1_freq = 0; + + mutex_unlock(&priv->lock); + + break; + default: + break; + } + + return 0; +} + +static int rpi_cirrus_set_wm8804_pll(struct snd_soc_card *card, + struct snd_soc_dai *wm8804_dai, unsigned int rate) +{ + int ret; + + /* use 256fs */ + unsigned int clk_freq = rate * 256; + + ret = snd_soc_dai_set_pll(wm8804_dai, 0, 0, + WM8804_CLKOUT_HZ, clk_freq); + if (ret) { + dev_err(card->dev, + "Failed to set WM8804 PLL to %d: %d\n", clk_freq, ret); + return ret; + } + + /* Set MCLK as PLL Output */ + ret = snd_soc_dai_set_sysclk(wm8804_dai, + WM8804_TX_CLKSRC_PLL, clk_freq, 0); + if (ret) { + dev_err(card->dev, + "Failed to set MCLK as PLL Output: %d\n", ret); + return ret; + } + + return ret; +} + +static int rpi_cirrus_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card); + unsigned int min_rate = min_rates[priv->min_rate_idx].value; + unsigned int max_rate = max_rates[priv->max_rate_idx].value; + + if (min_rate || max_rate) { + if (max_rate == 0) + max_rate = UINT_MAX; + + dev_dbg(card->dev, + "startup: limiting rate to %u-%u\n", + min_rate, max_rate); + + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, min_rate, max_rate); + } + + return 0; +} + +static struct snd_soc_pcm_stream rpi_cirrus_dai_link2_params = { + .formats = SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 2, + .channels_max = 2, + .rate_min = RPI_CIRRUS_DEFAULT_RATE, + .rate_max = RPI_CIRRUS_DEFAULT_RATE, +}; + +static int rpi_cirrus_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_dai *bcm_i2s_dai = snd_soc_rtd_to_cpu(rtd, 0); + struct snd_soc_component *wm5102_component = snd_soc_rtd_to_codec(rtd, 0)->component; + struct snd_soc_dai *wm8804_dai = snd_soc_rtd_to_codec(get_wm8804_runtime(card), 0); + + int ret; + + unsigned int width = snd_pcm_format_width(params_format(params)); + unsigned int rate = params_rate(params); + unsigned int clk_freq = calc_sysclk(rate); + + /* Using powers of 2 allows for an integer clock divisor */ + width = width <= 16 ? 16 : 32; + + mutex_lock(&priv->lock); + + dev_dbg(card->dev, "hw_params: setting rate to %d\n", rate); + + ret = snd_soc_dai_set_bclk_ratio(bcm_i2s_dai, 2 * width); + if (ret) { + dev_err(card->dev, "set_bclk_ratio failed: %d\n", ret); + goto out; + } + + ret = snd_soc_dai_set_tdm_slot(snd_soc_rtd_to_codec(rtd, 0), 0x03, 0x03, 2, width); + if (ret) { + dev_err(card->dev, "set_tdm_slot failed: %d\n", ret); + goto out; + } + + /* WM8804 supports sample rates from 32k only */ + if (rate >= 32000) { + ret = rpi_cirrus_set_wm8804_pll(card, wm8804_dai, rate); + if (ret) + goto out; + } + + ret = snd_soc_component_set_sysclk(wm5102_component, + ARIZONA_CLK_SYSCLK, + ARIZONA_CLK_SRC_FLL1, + clk_freq, + SND_SOC_CLOCK_IN); + if (ret) { + dev_err(card->dev, "Failed to set SYSCLK: %d\n", ret); + goto out; + } + + if ((priv->fll1_freq > 0) && (priv->fll1_freq != clk_freq)) { + dev_dbg(card->dev, + "hw_params: changing FLL1 from %d to %d\n", + priv->fll1_freq, clk_freq); + + if (rpi_cirrus_clear_flls(card, wm5102_component)) { + dev_err(card->dev, "hw_params: failed to clear FLLs\n"); + goto out; + } + + if (rpi_cirrus_set_fll(card, wm5102_component, clk_freq)) { + dev_err(card->dev, "hw_params: failed to set FLL\n"); + goto out; + } + + priv->fll1_freq = clk_freq; + } + + priv->card_rate = rate; + rpi_cirrus_dai_link2_params.rate_min = rate; + rpi_cirrus_dai_link2_params.rate_max = rate; + + priv->params_set |= 1 << substream->stream; + +out: + mutex_unlock(&priv->lock); + + return ret; +} + +static int rpi_cirrus_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_component *wm5102_component = snd_soc_rtd_to_codec(rtd, 0)->component; + int ret; + unsigned int old_params_set = priv->params_set; + + priv->params_set &= ~(1 << substream->stream); + + /* disable sysclk if this was the last open stream */ + if (priv->params_set == 0 && old_params_set) { + dev_dbg(card->dev, + "hw_free: Setting SYSCLK to Zero\n"); + + ret = snd_soc_component_set_sysclk(wm5102_component, + ARIZONA_CLK_SYSCLK, + ARIZONA_CLK_SRC_FLL1, + 0, + SND_SOC_CLOCK_IN); + if (ret) + dev_err(card->dev, + "hw_free: Failed to set SYSCLK to Zero: %d\n", + ret); + } + return 0; +} + +static int rpi_cirrus_init_wm5102(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + int ret; + + /* no 32kHz input, derive it from sysclk if needed */ + snd_soc_component_update_bits(component, + ARIZONA_CLOCK_32K_1, ARIZONA_CLK_32K_SRC_MASK, 2); + + if (rpi_cirrus_clear_flls(rtd->card, component)) + dev_warn(rtd->card->dev, + "init_wm5102: failed to clear FLLs\n"); + + ret = snd_soc_component_set_sysclk(component, + ARIZONA_CLK_SYSCLK, ARIZONA_CLK_SRC_FLL1, + 0, SND_SOC_CLOCK_IN); + if (ret) { + dev_err(rtd->card->dev, + "Failed to set SYSCLK to Zero: %d\n", ret); + return ret; + } + + return 0; +} + +static int rpi_cirrus_init_wm8804(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + struct snd_soc_component *component = codec_dai->component; + struct snd_soc_card *card = rtd->card; + struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card); + unsigned int val, mask; + int i, ret; + + for (i = 0; i < 4; i++) { + val = snd_soc_component_read(component, + WM8804_SPDTX1 + i); + mask = (i == 3) ? 0x3f : 0xff; + priv->iec958_status[i] = val & mask; + } + + /* Setup for 256fs */ + ret = snd_soc_dai_set_clkdiv(codec_dai, + WM8804_MCLK_DIV, WM8804_MCLKDIV_256FS); + if (ret) { + dev_err(card->dev, + "init_wm8804: Failed to set MCLK_DIV to 256fs: %d\n", + ret); + return ret; + } + + /* Output OSC on CLKOUT */ + ret = snd_soc_dai_set_sysclk(codec_dai, + WM8804_CLKOUT_SRC_OSCCLK, WM8804_CLKOUT_HZ, 0); + if (ret) + dev_err(card->dev, + "init_wm8804: Failed to set CLKOUT as OSC Frequency: %d\n", + ret); + + /* Init PLL with default samplerate */ + ret = rpi_cirrus_set_wm8804_pll(card, codec_dai, + RPI_CIRRUS_DEFAULT_RATE); + if (ret) + dev_err(card->dev, + "init_wm8804: Failed to setup PLL for %dHz: %d\n", + RPI_CIRRUS_DEFAULT_RATE, ret); + + return ret; +} + +static struct snd_soc_ops rpi_cirrus_ops = { + .startup = rpi_cirrus_startup, + .hw_params = rpi_cirrus_hw_params, + .hw_free = rpi_cirrus_hw_free, +}; + +SND_SOC_DAILINK_DEFS(wm5102, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC("wm5102-codec", "wm5102-aif1")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(wm8804, + DAILINK_COMP_ARRAY(COMP_CPU("wm5102-aif2")), + DAILINK_COMP_ARRAY(COMP_CODEC("wm8804.1-003b", "wm8804-spdif"))); + +static struct snd_soc_dai_link rpi_cirrus_dai[] = { + [DAI_WM5102] = { + .name = "WM5102", + .stream_name = "WM5102 AiFi", + .dai_fmt = SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBP_CFP, + .ops = &rpi_cirrus_ops, + .init = rpi_cirrus_init_wm5102, + SND_SOC_DAILINK_REG(wm5102), + }, + [DAI_WM8804] = { + .name = "WM5102 SPDIF", + .stream_name = "SPDIF Tx/Rx", + .dai_fmt = SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBP_CFP, + .ignore_suspend = 1, + .c2c_params = &rpi_cirrus_dai_link2_params, + .init = rpi_cirrus_init_wm8804, + SND_SOC_DAILINK_REG(wm8804), + }, +}; + + +static int rpi_cirrus_late_probe(struct snd_soc_card *card) +{ + struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_pcm_runtime *wm5102_runtime = get_wm5102_runtime(card); + struct snd_soc_pcm_runtime *wm8804_runtime = get_wm8804_runtime(card); + int ret; + + dev_dbg(card->dev, "iec958_bits: %02x %02x %02x %02x\n", + priv->iec958_status[0], + priv->iec958_status[1], + priv->iec958_status[2], + priv->iec958_status[3]); + + ret = snd_soc_dai_set_sysclk( + snd_soc_rtd_to_codec(wm5102_runtime, 0), ARIZONA_CLK_SYSCLK, 0, 0); + if (ret) { + dev_err(card->dev, + "Failed to set WM5102 codec dai clk domain: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk( + snd_soc_rtd_to_cpu(wm8804_runtime, 0), ARIZONA_CLK_SYSCLK, 0, 0); + if (ret) + dev_err(card->dev, + "Failed to set WM8804 codec dai clk domain: %d\n", ret); + + return ret; +} + +/* audio machine driver */ +static struct snd_soc_card rpi_cirrus_card = { + .name = "RPi-Cirrus", + .driver_name = "RPiCirrus", + .owner = THIS_MODULE, + .dai_link = rpi_cirrus_dai, + .num_links = ARRAY_SIZE(rpi_cirrus_dai), + .late_probe = rpi_cirrus_late_probe, + .controls = rpi_cirrus_controls, + .num_controls = ARRAY_SIZE(rpi_cirrus_controls), + .dapm_widgets = rpi_cirrus_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(rpi_cirrus_dapm_widgets), + .dapm_routes = rpi_cirrus_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(rpi_cirrus_dapm_routes), + .set_bias_level = rpi_cirrus_set_bias_level, + .set_bias_level_post = rpi_cirrus_set_bias_level_post, +}; + +static int rpi_cirrus_probe(struct platform_device *pdev) +{ + int ret = 0; + struct rpi_cirrus_priv *priv; + struct device_node *i2s_node; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->min_rate_idx = 1; /* min samplerate 32kHz */ + priv->card_rate = RPI_CIRRUS_DEFAULT_RATE; + + mutex_init(&priv->lock); + + snd_soc_card_set_drvdata(&rpi_cirrus_card, priv); + + if (!pdev->dev.of_node) + return -ENODEV; + + i2s_node = of_parse_phandle( + pdev->dev.of_node, "i2s-controller", 0); + if (!i2s_node) { + dev_err(&pdev->dev, "i2s-controller missing in DT\n"); + return -ENODEV; + } + + rpi_cirrus_dai[DAI_WM5102].cpus->of_node = i2s_node; + rpi_cirrus_dai[DAI_WM5102].platforms->of_node = i2s_node; + + rpi_cirrus_card.dev = &pdev->dev; + + ret = devm_snd_soc_register_card(&pdev->dev, &rpi_cirrus_card); + if (ret) { + if (ret == -EPROBE_DEFER) + dev_dbg(&pdev->dev, + "register card requested probe deferral\n"); + else + dev_err(&pdev->dev, + "Failed to register card: %d\n", ret); + } + + return ret; +} + +static const struct of_device_id rpi_cirrus_of_match[] = { + { .compatible = "wlf,rpi-cirrus", }, + {}, +}; +MODULE_DEVICE_TABLE(of, rpi_cirrus_of_match); + +static struct platform_driver rpi_cirrus_driver = { + .driver = { + .name = "snd-rpi-cirrus", + .of_match_table = of_match_ptr(rpi_cirrus_of_match), + }, + .probe = rpi_cirrus_probe, +}; + +module_platform_driver(rpi_cirrus_driver); + +MODULE_AUTHOR("Matthias Reichl "); +MODULE_DESCRIPTION("ASoC driver for Cirrus Logic Audio Card"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/bcm/rpi-proto.c b/sound/soc/bcm/rpi-proto.c new file mode 100644 index 000000000000..39a0e7c0975d --- /dev/null +++ b/sound/soc/bcm/rpi-proto.c @@ -0,0 +1,147 @@ +/* + * ASoC driver for PROTO AudioCODEC (with a WM8731) + * connected to a Raspberry Pi + * + * Author: Florian Meier, + * Copyright 2013 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include + +#include +#include +#include +#include + +#include "../codecs/wm8731.h" + +static const unsigned int wm8731_rates_12288000[] = { + 8000, 32000, 48000, 96000, +}; + +static struct snd_pcm_hw_constraint_list wm8731_constraints_12288000 = { + .list = wm8731_rates_12288000, + .count = ARRAY_SIZE(wm8731_rates_12288000), +}; + +static int snd_rpi_proto_startup(struct snd_pcm_substream *substream) +{ + /* Setup constraints, because there is a 12.288 MHz XTAL on the board */ + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &wm8731_constraints_12288000); + return 0; +} + +static int snd_rpi_proto_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + int sysclk = 12288000; /* This is fixed on this board */ + + /* Set proto bclk */ + int ret = snd_soc_dai_set_bclk_ratio(cpu_dai,32*2); + if (ret < 0){ + dev_err(rtd->card->dev, + "Failed to set BCLK ratio %d\n", ret); + return ret; + } + + /* Set proto sysclk */ + ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK_XTAL, + sysclk, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(rtd->card->dev, + "Failed to set WM8731 SYSCLK: %d\n", ret); + return ret; + } + + return 0; +} + +/* machine stream operations */ +static struct snd_soc_ops snd_rpi_proto_ops = { + .startup = snd_rpi_proto_startup, + .hw_params = snd_rpi_proto_hw_params, +}; + +SND_SOC_DAILINK_DEFS(rpi_proto, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), + DAILINK_COMP_ARRAY(COMP_CODEC("wm8731.1-001a", "wm8731-hifi")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0"))); + +static struct snd_soc_dai_link snd_rpi_proto_dai[] = { +{ + .name = "WM8731", + .stream_name = "WM8731 HiFi", + .dai_fmt = SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBP_CFP, + .ops = &snd_rpi_proto_ops, + SND_SOC_DAILINK_REG(rpi_proto), +}, +}; + +/* audio machine driver */ +static struct snd_soc_card snd_rpi_proto = { + .name = "snd_rpi_proto", + .owner = THIS_MODULE, + .dai_link = snd_rpi_proto_dai, + .num_links = ARRAY_SIZE(snd_rpi_proto_dai), +}; + +static int snd_rpi_proto_probe(struct platform_device *pdev) +{ + int ret = 0; + + snd_rpi_proto.dev = &pdev->dev; + + if (pdev->dev.of_node) { + struct device_node *i2s_node; + struct snd_soc_dai_link *dai = &snd_rpi_proto_dai[0]; + i2s_node = of_parse_phandle(pdev->dev.of_node, + "i2s-controller", 0); + + if (i2s_node) { + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + } + } + + ret = devm_snd_soc_register_card(&pdev->dev, &snd_rpi_proto); + if (ret && ret != -EPROBE_DEFER) + dev_err(&pdev->dev, + "snd_soc_register_card() failed: %d\n", ret); + + return ret; +} + +static const struct of_device_id snd_rpi_proto_of_match[] = { + { .compatible = "rpi,rpi-proto", }, + {}, +}; +MODULE_DEVICE_TABLE(of, snd_rpi_proto_of_match); + +static struct platform_driver snd_rpi_proto_driver = { + .driver = { + .name = "snd-rpi-proto", + .owner = THIS_MODULE, + .of_match_table = snd_rpi_proto_of_match, + }, + .probe = snd_rpi_proto_probe, +}; + +module_platform_driver(snd_rpi_proto_driver); + +MODULE_AUTHOR("Florian Meier"); +MODULE_DESCRIPTION("ASoC Driver for Raspberry Pi connected to PROTO board (WM8731)"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/bcm/rpi-simple-soundcard.c b/sound/soc/bcm/rpi-simple-soundcard.c new file mode 100644 index 000000000000..91252697800f --- /dev/null +++ b/sound/soc/bcm/rpi-simple-soundcard.c @@ -0,0 +1,590 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * rpi-simple-soundcard.c -- ALSA SoC Raspberry Pi soundcard. + * + * Copyright (C) 2018 Raspberry Pi. + * + * Authors: Tim Gover + * + * Based on code: + * hifiberry_amp.c, hifiberry_dac.c, rpi-dac.c + * by Florian Meier + * + * googlevoicehat-soundcard.c + * by Peter Malkin + * + * adau1977-adc.c + * by Andrey Grodzovsky + * + * merus-amp.c + * by Ariel Muszkat + * Jorgen Kragh Jakobsen + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include + +#include +#include +#include +#include + +/* Parameters for generic RPI functions */ +struct snd_rpi_simple_drvdata { + struct snd_soc_dai_link *dai; + const char* card_name; + unsigned int fixed_bclk_ratio; +}; + +static struct snd_soc_card snd_rpi_simple = { + .driver_name = "RPi-simple", + .owner = THIS_MODULE, + .dai_link = NULL, + .num_links = 1, /* Only a single DAI supported at the moment */ +}; + +static int snd_rpi_simple_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_rpi_simple_drvdata *drvdata = + snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + + if (drvdata->fixed_bclk_ratio > 0) + return snd_soc_dai_set_bclk_ratio(cpu_dai, + drvdata->fixed_bclk_ratio); + + return 0; +} + +static int pifi_mini_210_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *dac; + struct gpio_desc *pdn_gpio, *rst_gpio; + struct snd_soc_dai *codec_dai; + int ret; + + snd_rpi_simple_init(rtd); + codec_dai = snd_soc_rtd_to_codec(rtd, 0); + + dac = codec_dai[0].component; + + pdn_gpio = devm_gpiod_get_optional(snd_rpi_simple.dev, "pdn", + GPIOD_OUT_LOW); + if (IS_ERR(pdn_gpio)) { + ret = PTR_ERR(pdn_gpio); + dev_err(snd_rpi_simple.dev, "failed to get pdn gpio: %d\n", ret); + return ret; + } + + rst_gpio = devm_gpiod_get_optional(snd_rpi_simple.dev, "rst", + GPIOD_OUT_LOW); + if (IS_ERR(rst_gpio)) { + ret = PTR_ERR(rst_gpio); + dev_err(snd_rpi_simple.dev, "failed to get rst gpio: %d\n", ret); + return ret; + } + + // Set up cards - pulse power down and reset first, then + // set up according to datasheet + gpiod_set_value_cansleep(pdn_gpio, 1); + gpiod_set_value_cansleep(rst_gpio, 1); + usleep_range(1000, 10000); + gpiod_set_value_cansleep(pdn_gpio, 0); + usleep_range(20000, 30000); + gpiod_set_value_cansleep(rst_gpio, 0); + usleep_range(20000, 30000); + + // Oscillator trim + snd_soc_component_write(dac, 0x1b, 0); + usleep_range(60000, 80000); + + // MCLK at 64fs, sample rate 44.1 or 48kHz + snd_soc_component_write(dac, 0x00, 0x60); + + // Set up for BTL - AD/BD mode - AD is 0x00107772, BD is 0x00987772 + snd_soc_component_write(dac, 0x20, 0x00107772); + + // End mute + snd_soc_component_write(dac, 0x05, 0x00); + + return 0; +} + +static int snd_rpi_simple_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + struct snd_rpi_simple_drvdata *drvdata; + unsigned int sample_bits; + + drvdata = snd_soc_card_get_drvdata(rtd->card); + + if (drvdata->fixed_bclk_ratio > 0) + return 0; // BCLK is configured in .init + + /* The simple drivers just set the bclk_ratio to sample_bits * 2 so + * hard-code this for now, but sticking to powers of 2 to allow for + * integer clock divisors. More complex drivers could just replace + * the hw_params routine. + */ + sample_bits = snd_pcm_format_width(params_format(params)); + sample_bits = sample_bits <= 16 ? 16 : 32; + + return snd_soc_dai_set_bclk_ratio(cpu_dai, sample_bits * 2); +} + +static struct snd_soc_ops snd_rpi_simple_ops = { + .hw_params = snd_rpi_simple_hw_params, +}; + +static int snd_merus_amp_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + int rate; + + rate = params_rate(params); + if (rate > 48000) { + dev_err(rtd->card->dev, + "Unsupported samplerate %d\n", + rate); + return -EINVAL; + } + return 0; +} + +static struct snd_soc_ops snd_merus_amp_ops = { + .hw_params = snd_merus_amp_hw_params, +}; + +enum adau1977_clk_id { + ADAU1977_SYSCLK, +}; + +enum adau1977_sysclk_src { + ADAU1977_SYSCLK_SRC_MCLK, + ADAU1977_SYSCLK_SRC_LRCLK, +}; + +static int adau1977_init(struct snd_soc_pcm_runtime *rtd) +{ + int ret; + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + + ret = snd_soc_dai_set_tdm_slot(codec_dai, 0, 0, 0, 0); + if (ret < 0) + return ret; + + return snd_soc_component_set_sysclk(codec_dai->component, + ADAU1977_SYSCLK, ADAU1977_SYSCLK_SRC_MCLK, + 11289600, SND_SOC_CLOCK_IN); +} + +SND_SOC_DAILINK_DEFS(adau1977, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC("adau1977.1-0011", "adau1977-hifi")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link snd_rpi_adau1977_dai[] = { + { + .name = "adau1977", + .stream_name = "ADAU1977", + .init = adau1977_init, + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBP_CFP, + SND_SOC_DAILINK_REG(adau1977), + }, +}; + +static struct snd_rpi_simple_drvdata drvdata_adau1977 = { + .card_name = "snd_rpi_adau1977_adc", + .dai = snd_rpi_adau1977_dai, +}; + +SND_SOC_DAILINK_DEFS(gvchat, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC("voicehat-codec", "voicehat-hifi")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link snd_googlevoicehat_soundcard_dai[] = { +{ + .name = "Google voiceHAT SoundCard", + .stream_name = "Google voiceHAT SoundCard HiFi", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBC_CFC, + SND_SOC_DAILINK_REG(gvchat), +}, +}; + +static struct snd_rpi_simple_drvdata drvdata_googlevoicehat = { + .card_name = "snd_rpi_googlevoicehat_soundcard", + .dai = snd_googlevoicehat_soundcard_dai, +}; + +SND_SOC_DAILINK_DEFS(hifiberry_dacplusdsp, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC("dacplusdsp-codec", "dacplusdsp-hifi")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link snd_hifiberrydacplusdsp_soundcard_dai[] = { +{ + .name = "Hifiberry DAC+DSP SoundCard", + .stream_name = "Hifiberry DAC+DSP SoundCard HiFi", + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBC_CFC, + SND_SOC_DAILINK_REG(hifiberry_dacplusdsp), +}, +}; + +static struct snd_rpi_simple_drvdata drvdata_hifiberrydacplusdsp = { + .card_name = "snd_rpi_hifiberrydacplusdsp_soundcard", + .dai = snd_hifiberrydacplusdsp_soundcard_dai, +}; + +SND_SOC_DAILINK_DEFS(hifiberry_adc, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC("snd-soc-dummy", "snd-soc-dummy-dai")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static int hifiberry_adc8x_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + + /* set limits of 8 channels and 192ksps sample rate + */ + codec_dai->driver->capture.channels_max = 8; + codec_dai->driver->capture.rates = SNDRV_PCM_RATE_8000_192000; + + return 0; +} + +static struct snd_soc_dai_link snd_hifiberry_adc8x_dai[] = { + { + .name = "HifiBerry ADC8x", + .stream_name = "HifiBerry ADC8x HiFi", + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBC_CFC, + .init = hifiberry_adc8x_init, + SND_SOC_DAILINK_REG(hifiberry_adc), + }, +}; + +static struct snd_rpi_simple_drvdata drvdata_hifiberry_adc8x = { + .card_name = "snd_rpi_hifiberry_adc8x", + .dai = snd_hifiberry_adc8x_dai, + .fixed_bclk_ratio = 64, +}; + +SND_SOC_DAILINK_DEFS(hifiberry_amp, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC("tas5713.1-001b", "tas5713-hifi")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link snd_hifiberry_amp_dai[] = { + { + .name = "HifiBerry AMP", + .stream_name = "HifiBerry AMP HiFi", + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBC_CFC, + SND_SOC_DAILINK_REG(hifiberry_amp), + }, +}; + +static struct snd_rpi_simple_drvdata drvdata_hifiberry_amp = { + .card_name = "snd_rpi_hifiberry_amp", + .dai = snd_hifiberry_amp_dai, + .fixed_bclk_ratio = 64, +}; + +SND_SOC_DAILINK_DEFS(hifiberry_amp3, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC("ma120x0p.1-0020", "ma120x0p-amp")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link snd_hifiberry_amp3_dai[] = { + { + .name = "HifiberryAmp3", + .stream_name = "Hifiberry Amp3", + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBC_CFC, + SND_SOC_DAILINK_REG(hifiberry_amp3), + }, +}; + +static struct snd_rpi_simple_drvdata drvdata_hifiberry_amp3 = { + .card_name = "snd_rpi_hifiberry_amp3", + .dai = snd_hifiberry_amp3_dai, + .fixed_bclk_ratio = 64, +}; + +SND_SOC_DAILINK_DEFS(hifiberry_dac, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC("pcm5102a-codec", "pcm5102a-hifi")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link snd_hifiberry_dac_dai[] = { + { + .name = "HifiBerry DAC", + .stream_name = "HifiBerry DAC HiFi", + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBC_CFC, + SND_SOC_DAILINK_REG(hifiberry_dac), + }, +}; + +static struct snd_rpi_simple_drvdata drvdata_hifiberry_dac = { + .card_name = "snd_rpi_hifiberry_dac", + .dai = snd_hifiberry_dac_dai, +}; + +SND_SOC_DAILINK_DEFS(hifiberry_dac8x, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC("snd-soc-dummy", "snd-soc-dummy-dai")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static int hifiberry_dac8x_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + struct snd_soc_card *card = rtd->card; + struct gpio_desc *gpio_desc; + bool has_adc; + + /* Configure the codec for 8 channel playback */ + codec_dai->driver->playback.channels_max = 8; + codec_dai->driver->playback.rates = SNDRV_PCM_RATE_8000_192000; + + /* Activate capture based on ADC8x detection */ + gpio_desc = devm_gpiod_get(card->dev, "hasadc", GPIOD_IN); + if (IS_ERR(gpio_desc)) { + dev_err(card->dev, "Failed to get GPIO: %ld\n", PTR_ERR(gpio_desc)); + return PTR_ERR(gpio_desc); + } + + has_adc = gpiod_get_value(gpio_desc); + + if (has_adc) { + struct snd_soc_dai_link *dai = rtd->dai_link; + + dev_info(card->dev, "ADC8x detected: capture enabled\n"); + codec_dai->driver->symmetric_rate = 1; + codec_dai->driver->symmetric_channels = 1; + codec_dai->driver->symmetric_sample_bits = 1; + codec_dai->driver->capture.rates = SNDRV_PCM_RATE_8000_192000; + dai->name = "HiFiBerry DAC8xADC8x"; + dai->stream_name = "HiFiBerry DAC8xADC8x HiFi"; + } else { + dev_info(card->dev, "no ADC8x detected\n"); + rtd->dai_link->playback_only = 1; // Disable capture + } + + return 0; +} + +static struct snd_soc_dai_link snd_hifiberry_dac8x_dai[] = { + { + .name = "HifiBerry DAC8x", + .stream_name = "HifiBerry DAC8x HiFi", + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBC_CFC, + .init = hifiberry_dac8x_init, + SND_SOC_DAILINK_REG(hifiberry_dac8x), + }, +}; + +static struct snd_rpi_simple_drvdata drvdata_hifiberry_dac8x = { + .card_name = "snd_rpi_hifiberry_dac8x", + .dai = snd_hifiberry_dac8x_dai, + .fixed_bclk_ratio = 64, +}; + +SND_SOC_DAILINK_DEFS(dionaudio_kiwi, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC("pcm1794a-codec", "pcm1794a-hifi")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link snd_dionaudio_kiwi_dai[] = { +{ + .name = "DionAudio KIWI", + .stream_name = "DionAudio KIWI STREAMER", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBC_CFC, + SND_SOC_DAILINK_REG(dionaudio_kiwi), +}, +}; + +static struct snd_rpi_simple_drvdata drvdata_dionaudio_kiwi = { + .card_name = "snd_rpi_dionaudio_kiwi", + .dai = snd_dionaudio_kiwi_dai, + .fixed_bclk_ratio = 64, +}; + +SND_SOC_DAILINK_DEFS(rpi_dac, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC("pcm1794a-codec", "pcm1794a-hifi")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link snd_rpi_dac_dai[] = { +{ + .name = "RPi-DAC", + .stream_name = "RPi-DAC HiFi", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBC_CFC, + SND_SOC_DAILINK_REG(rpi_dac), +}, +}; + +static struct snd_rpi_simple_drvdata drvdata_rpi_dac = { + .card_name = "snd_rpi_rpi_dac", + .dai = snd_rpi_dac_dai, + .fixed_bclk_ratio = 64, +}; + +SND_SOC_DAILINK_DEFS(merus_amp, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC("ma120x0p.1-0020", "ma120x0p-amp")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link snd_merus_amp_dai[] = { + { + .name = "MerusAmp", + .stream_name = "Merus Audio Amp", + .ops = &snd_merus_amp_ops, + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBC_CFC, + SND_SOC_DAILINK_REG(merus_amp), + }, +}; + +static struct snd_rpi_simple_drvdata drvdata_merus_amp = { + .card_name = "snd_rpi_merus_amp", + .dai = snd_merus_amp_dai, + .fixed_bclk_ratio = 64, +}; + +SND_SOC_DAILINK_DEFS(pifi_mini_210, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC("tas571x.1-001a", "tas571x-hifi")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link snd_pifi_mini_210_dai[] = { + { + .name = "PiFi Mini 210", + .stream_name = "PiFi Mini 210 HiFi", + .init = pifi_mini_210_init, + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBC_CFC, + SND_SOC_DAILINK_REG(pifi_mini_210), + }, +}; + +static struct snd_rpi_simple_drvdata drvdata_pifi_mini_210 = { + .card_name = "snd_pifi_mini_210", + .dai = snd_pifi_mini_210_dai, + .fixed_bclk_ratio = 64, +}; + +static const struct of_device_id snd_rpi_simple_of_match[] = { + { .compatible = "adi,adau1977-adc", + .data = (void *) &drvdata_adau1977 }, + { .compatible = "googlevoicehat,googlevoicehat-soundcard", + .data = (void *) &drvdata_googlevoicehat }, + { .compatible = "hifiberrydacplusdsp,hifiberrydacplusdsp-soundcard", + .data = (void *) &drvdata_hifiberrydacplusdsp }, + { .compatible = "hifiberry,hifiberry-adc8x", + .data = (void *) &drvdata_hifiberry_adc8x }, + { .compatible = "hifiberry,hifiberry-amp", + .data = (void *) &drvdata_hifiberry_amp }, + { .compatible = "hifiberry,hifiberry-amp3", + .data = (void *) &drvdata_hifiberry_amp3 }, + { .compatible = "hifiberry,hifiberry-dac", + .data = (void *) &drvdata_hifiberry_dac }, + { .compatible = "hifiberry,hifiberry-dac8x", + .data = (void *) &drvdata_hifiberry_dac8x }, + { .compatible = "dionaudio,dionaudio-kiwi", + .data = (void *) &drvdata_dionaudio_kiwi }, + { .compatible = "rpi,rpi-dac", &drvdata_rpi_dac}, + { .compatible = "merus,merus-amp", + .data = (void *) &drvdata_merus_amp }, + { .compatible = "pifi,pifi-mini-210", + .data = (void *) &drvdata_pifi_mini_210 }, + {}, +}; + +static int snd_rpi_simple_probe(struct platform_device *pdev) +{ + int ret = 0; + const struct of_device_id *of_id; + + snd_rpi_simple.dev = &pdev->dev; + of_id = of_match_node(snd_rpi_simple_of_match, pdev->dev.of_node); + + if (pdev->dev.of_node && of_id->data) { + struct device_node *i2s_node; + struct snd_rpi_simple_drvdata *drvdata = + (struct snd_rpi_simple_drvdata *) of_id->data; + struct snd_soc_dai_link *dai = drvdata->dai; + + snd_soc_card_set_drvdata(&snd_rpi_simple, drvdata); + + /* More complex drivers might override individual functions */ + if (!dai->init) + dai->init = snd_rpi_simple_init; + if (!dai->ops) + dai->ops = &snd_rpi_simple_ops; + + snd_rpi_simple.name = drvdata->card_name; + + snd_rpi_simple.dai_link = dai; + i2s_node = of_parse_phandle(pdev->dev.of_node, + "i2s-controller", 0); + if (!i2s_node) { + pr_err("Failed to find i2s-controller DT node\n"); + return -ENODEV; + } + + dai->cpus->of_node = i2s_node; + dai->platforms->of_node = i2s_node; + } + + ret = devm_snd_soc_register_card(&pdev->dev, &snd_rpi_simple); + if (ret && ret != -EPROBE_DEFER) + dev_err(&pdev->dev, "Failed to register card %d\n", ret); + + return ret; +} + +static struct platform_driver snd_rpi_simple_driver = { + .driver = { + .name = "snd-rpi-simple", + .owner = THIS_MODULE, + .of_match_table = snd_rpi_simple_of_match, + }, + .probe = snd_rpi_simple_probe, +}; +MODULE_DEVICE_TABLE(of, snd_rpi_simple_of_match); + +module_platform_driver(snd_rpi_simple_driver); + +MODULE_AUTHOR("Tim Gover "); +MODULE_DESCRIPTION("ASoC Raspberry Pi simple soundcard driver "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/rpi-wm8804-soundcard.c b/sound/soc/bcm/rpi-wm8804-soundcard.c new file mode 100644 index 000000000000..9e09047b6d1b --- /dev/null +++ b/sound/soc/bcm/rpi-wm8804-soundcard.c @@ -0,0 +1,549 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * rpi--wm8804.c -- ALSA SoC Raspberry Pi soundcard. + * + * Copyright (C) 2018 Raspberry Pi. + * + * Authors: Tim Gover + * + * Generic driver for Pi Hat WM8804 digi sounds cards + * + * Based upon code from: + * justboom-digi.c + * by Milan Neskovic + * + * iqaudio_digi.c + * by Daniel Matuschek + * + * allo-digione.c + * by Baswaraj + * + * hifiberry-digi.c + * Daniel Matuschek + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "../codecs/wm8804.h" + +struct wm8804_clk_cfg { + unsigned int sysclk_freq; + unsigned int mclk_freq; + unsigned int mclk_div; +}; + +/* Parameters for generic functions */ +struct snd_rpi_wm8804_drvdata { + /* Required - pointer to the DAI structure */ + struct snd_soc_dai_link *dai; + /* Required - snd_soc_card name */ + const char *card_name; + /* Optional DT node names if card info is defined in DT */ + const char *card_name_dt; + const char *dai_name_dt; + const char *dai_stream_name_dt; + /* Optional probe extension - called prior to register_card */ + int (*probe)(struct platform_device *pdev); +}; + +static struct gpio_desc *snd_clk44gpio; +static struct gpio_desc *snd_clk48gpio; +static int wm8804_samplerate = 0; +static struct gpio_desc *led_gpio_1; +static struct gpio_desc *led_gpio_2; +static struct gpio_desc *led_gpio_3; +static struct gpio_desc *custom_reset; + +/* Forward declarations */ +static struct snd_soc_dai_link snd_allo_digione_dai[]; +static struct snd_soc_card snd_rpi_wm8804; + + +#define CLK_44EN_RATE 22579200UL +#define CLK_48EN_RATE 24576000UL + +static const char * const wm8805_input_select_text[] = { + "Rx 0", + "Rx 1", + "Rx 2", + "Rx 3", + "Rx 4", + "Rx 5", + "Rx 6", + "Rx 7" +}; + +static const unsigned int wm8805_input_channel_select_value[] = { + 0, 1, 2, 3, 4, 5, 6, 7 +}; + +static const struct soc_enum wm8805_input_channel_sel[] = { + SOC_VALUE_ENUM_SINGLE(WM8804_PLL6, 0, 7, ARRAY_SIZE(wm8805_input_select_text), + wm8805_input_select_text, wm8805_input_channel_select_value), +}; + +static const struct snd_kcontrol_new wm8805_input_controls_card[] = { + SOC_ENUM("Select Input Channel", wm8805_input_channel_sel[0]), +}; + +static int wm8805_add_input_controls(struct snd_soc_component *component) +{ + snd_soc_add_component_controls(component, wm8805_input_controls_card, + ARRAY_SIZE(wm8805_input_controls_card)); + return 0; +} + +static unsigned int snd_rpi_wm8804_enable_clock(unsigned int samplerate) +{ + switch (samplerate) { + case 11025: + case 22050: + case 44100: + case 88200: + case 176400: + gpiod_set_value_cansleep(snd_clk44gpio, 1); + gpiod_set_value_cansleep(snd_clk48gpio, 0); + return CLK_44EN_RATE; + default: + gpiod_set_value_cansleep(snd_clk48gpio, 1); + gpiod_set_value_cansleep(snd_clk44gpio, 0); + return CLK_48EN_RATE; + } +} + +static void snd_rpi_wm8804_clk_cfg(unsigned int samplerate, + struct wm8804_clk_cfg *clk_cfg) +{ + clk_cfg->sysclk_freq = 27000000; + + if (samplerate <= 96000 || + snd_rpi_wm8804.dai_link == snd_allo_digione_dai) { + clk_cfg->mclk_freq = samplerate * 256; + clk_cfg->mclk_div = WM8804_MCLKDIV_256FS; + } else { + clk_cfg->mclk_freq = samplerate * 128; + clk_cfg->mclk_div = WM8804_MCLKDIV_128FS; + } + + if (!(IS_ERR(snd_clk44gpio) || IS_ERR(snd_clk48gpio))) + clk_cfg->sysclk_freq = snd_rpi_wm8804_enable_clock(samplerate); +} + +static int snd_rpi_wm8804_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + int sampling_freq = 1; + int ret; + struct wm8804_clk_cfg clk_cfg; + int samplerate = params_rate(params); + + if (samplerate == wm8804_samplerate) + return 0; + + /* clear until all clocks are setup properly */ + wm8804_samplerate = 0; + + snd_rpi_wm8804_clk_cfg(samplerate, &clk_cfg); + + pr_debug("%s samplerate: %d mclk_freq: %u mclk_div: %u sysclk: %u\n", + __func__, samplerate, clk_cfg.mclk_freq, + clk_cfg.mclk_div, clk_cfg.sysclk_freq); + + switch (samplerate) { + case 32000: + sampling_freq = 0x03; + break; + case 44100: + sampling_freq = 0x00; + break; + case 48000: + sampling_freq = 0x02; + break; + case 88200: + sampling_freq = 0x08; + break; + case 96000: + sampling_freq = 0x0a; + break; + case 176400: + sampling_freq = 0x0c; + break; + case 192000: + sampling_freq = 0x0e; + break; + default: + dev_err(rtd->card->dev, + "Failed to set WM8804 SYSCLK, unsupported samplerate %d\n", + samplerate); + } + + snd_soc_dai_set_clkdiv(codec_dai, WM8804_MCLK_DIV, clk_cfg.mclk_div); + snd_soc_dai_set_pll(codec_dai, 0, 0, + clk_cfg.sysclk_freq, clk_cfg.mclk_freq); + + ret = snd_soc_dai_set_sysclk(codec_dai, WM8804_TX_CLKSRC_PLL, + clk_cfg.sysclk_freq, SND_SOC_CLOCK_OUT); + if (ret < 0) { + dev_err(rtd->card->dev, + "Failed to set WM8804 SYSCLK: %d\n", ret); + return ret; + } + + wm8804_samplerate = samplerate; + + /* set sampling frequency status bits */ + snd_soc_component_update_bits(component, WM8804_SPDTX4, 0x0f, + sampling_freq); + + return snd_soc_dai_set_bclk_ratio(cpu_dai, 64); +} + +static struct snd_soc_ops snd_rpi_wm8804_ops = { + .hw_params = snd_rpi_wm8804_hw_params, +}; + +static int snd_interlude_audio_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + int ret = snd_rpi_wm8804_hw_params(substream, params); + int samplerate = params_rate(params); + + switch (samplerate) { + case 44100: + gpiod_set_value_cansleep(led_gpio_1, 1); + gpiod_set_value_cansleep(led_gpio_2, 0); + gpiod_set_value_cansleep(led_gpio_3, 0); + break; + case 48000: + gpiod_set_value_cansleep(led_gpio_1, 1); + gpiod_set_value_cansleep(led_gpio_2, 0); + gpiod_set_value_cansleep(led_gpio_3, 0); + break; + case 88200: + gpiod_set_value_cansleep(led_gpio_1, 0); + gpiod_set_value_cansleep(led_gpio_2, 1); + gpiod_set_value_cansleep(led_gpio_3, 0); + break; + case 96000: + gpiod_set_value_cansleep(led_gpio_1, 0); + gpiod_set_value_cansleep(led_gpio_2, 1); + gpiod_set_value_cansleep(led_gpio_3, 0); + break; + case 176400: + gpiod_set_value_cansleep(led_gpio_1, 0); + gpiod_set_value_cansleep(led_gpio_2, 0); + gpiod_set_value_cansleep(led_gpio_3, 1); + break; + case 192000: + gpiod_set_value_cansleep(led_gpio_1, 0); + gpiod_set_value_cansleep(led_gpio_2, 0); + gpiod_set_value_cansleep(led_gpio_3, 1); + break; + default: + break; + } + return ret; +} + +const struct snd_soc_ops interlude_audio_digital_dai_ops = { + .hw_params = snd_interlude_audio_hw_params, +}; + +SND_SOC_DAILINK_DEFS(justboom_digi, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link snd_justboom_digi_dai[] = { +{ + .name = "JustBoom Digi", + .stream_name = "JustBoom Digi HiFi", + SND_SOC_DAILINK_REG(justboom_digi), +}, +}; + +static struct snd_rpi_wm8804_drvdata drvdata_justboom_digi = { + .card_name = "snd_rpi_justboom_digi", + .dai = snd_justboom_digi_dai, +}; + +SND_SOC_DAILINK_DEFS(iqaudio_digi, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link snd_iqaudio_digi_dai[] = { +{ + .name = "IQAudIO Digi", + .stream_name = "IQAudIO Digi HiFi", + SND_SOC_DAILINK_REG(iqaudio_digi), +}, +}; + +static struct snd_rpi_wm8804_drvdata drvdata_iqaudio_digi = { + .card_name = "IQAudIODigi", + .dai = snd_iqaudio_digi_dai, + .card_name_dt = "wm8804-digi,card-name", + .dai_name_dt = "wm8804-digi,dai-name", + .dai_stream_name_dt = "wm8804-digi,dai-stream-name", +}; + +static int snd_allo_digione_probe(struct platform_device *pdev) +{ + pr_debug("%s\n", __func__); + + if (IS_ERR(snd_clk44gpio) || IS_ERR(snd_clk48gpio)) { + dev_err(&pdev->dev, "devm_gpiod_get() failed\n"); + return -EINVAL; + } + return 0; +} + +SND_SOC_DAILINK_DEFS(allo_digione, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link snd_allo_digione_dai[] = { +{ + .name = "Allo DigiOne", + .stream_name = "Allo DigiOne HiFi", + SND_SOC_DAILINK_REG(allo_digione), +}, +}; + +static struct snd_rpi_wm8804_drvdata drvdata_allo_digione = { + .card_name = "snd_allo_digione", + .dai = snd_allo_digione_dai, + .probe = snd_allo_digione_probe, +}; + +SND_SOC_DAILINK_DEFS(hifiberry_digi, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link snd_hifiberry_digi_dai[] = { +{ + .name = "HifiBerry Digi", + .stream_name = "HifiBerry Digi HiFi", + SND_SOC_DAILINK_REG(hifiberry_digi), +}, +}; + +static int snd_hifiberry_digi_probe(struct platform_device *pdev) +{ + pr_debug("%s\n", __func__); + + if (IS_ERR(snd_clk44gpio) || IS_ERR(snd_clk48gpio)) + return 0; + + snd_hifiberry_digi_dai->name = "HiFiBerry Digi+ Pro"; + snd_hifiberry_digi_dai->stream_name = "HiFiBerry Digi+ Pro HiFi"; + return 0; +} + +static struct snd_rpi_wm8804_drvdata drvdata_hifiberry_digi = { + .card_name = "snd_rpi_hifiberry_digi", + .dai = snd_hifiberry_digi_dai, + .probe = snd_hifiberry_digi_probe, +}; + +SND_SOC_DAILINK_DEFS(interlude_audio_digital, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static int snd_interlude_audio_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + int ret; + + ret = wm8805_add_input_controls(component); + if (ret != 0) + pr_err("failed to add input controls"); + + return 0; +} + + +static struct snd_soc_dai_link snd_interlude_audio_digital_dai[] = { +{ + .name = "Interlude Audio Digital", + .stream_name = "Interlude Audio Digital HiFi", + .init = snd_interlude_audio_init, + .ops = &interlude_audio_digital_dai_ops, + SND_SOC_DAILINK_REG(interlude_audio_digital), +}, +}; + + +static int snd_interlude_audio_digital_probe(struct platform_device *pdev) +{ + if (IS_ERR(snd_clk44gpio) || IS_ERR(snd_clk48gpio)) + return 0; + + custom_reset = devm_gpiod_get(&pdev->dev, "reset", GPIOD_OUT_LOW); + gpiod_set_value_cansleep(custom_reset, 0); + mdelay(10); + gpiod_set_value_cansleep(custom_reset, 1); + + snd_interlude_audio_digital_dai->name = "Interlude Audio Digital"; + snd_interlude_audio_digital_dai->stream_name = "Interlude Audio Digital HiFi"; + led_gpio_1 = devm_gpiod_get(&pdev->dev, "led1", GPIOD_OUT_LOW); + led_gpio_2 = devm_gpiod_get(&pdev->dev, "led2", GPIOD_OUT_LOW); + led_gpio_3 = devm_gpiod_get(&pdev->dev, "led3", GPIOD_OUT_LOW); + return 0; +} + + +static struct snd_rpi_wm8804_drvdata drvdata_interlude_audio_digital = { + .card_name = "snd_IA_Digital_Hat", + .dai = snd_interlude_audio_digital_dai, + .probe = snd_interlude_audio_digital_probe, +}; + +static const struct of_device_id snd_rpi_wm8804_of_match[] = { + { .compatible = "justboom,justboom-digi", + .data = (void *) &drvdata_justboom_digi }, + { .compatible = "iqaudio,wm8804-digi", + .data = (void *) &drvdata_iqaudio_digi }, + { .compatible = "allo,allo-digione", + .data = (void *) &drvdata_allo_digione }, + { .compatible = "hifiberry,hifiberry-digi", + .data = (void *) &drvdata_hifiberry_digi }, + { .compatible = "interludeaudio,interludeaudio-digital", + .data = (void *) &drvdata_interlude_audio_digital }, + {}, +}; + +static struct snd_soc_card snd_rpi_wm8804 = { + .driver_name = "RPi-WM8804", + .owner = THIS_MODULE, + .dai_link = NULL, + .num_links = 1, +}; + +static int snd_rpi_wm8804_probe(struct platform_device *pdev) +{ + int ret = 0; + const struct of_device_id *of_id; + + snd_rpi_wm8804.dev = &pdev->dev; + of_id = of_match_node(snd_rpi_wm8804_of_match, pdev->dev.of_node); + + if (pdev->dev.of_node && of_id->data) { + struct device_node *i2s_node; + struct snd_rpi_wm8804_drvdata *drvdata = + (struct snd_rpi_wm8804_drvdata *) of_id->data; + struct snd_soc_dai_link *dai = drvdata->dai; + + snd_soc_card_set_drvdata(&snd_rpi_wm8804, drvdata); + + if (!dai->ops) + dai->ops = &snd_rpi_wm8804_ops; + if (!dai->codecs->dai_name) + dai->codecs->dai_name = "wm8804-spdif"; + if (!dai->codecs->name) + dai->codecs->name = "wm8804.1-003b"; + if (!dai->dai_fmt) + dai->dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBP_CFP; + + snd_rpi_wm8804.dai_link = dai; + i2s_node = of_parse_phandle(pdev->dev.of_node, + "i2s-controller", 0); + if (!i2s_node) { + pr_err("Failed to find i2s-controller DT node\n"); + return -ENODEV; + } + + snd_rpi_wm8804.name = drvdata->card_name; + + /* If requested by in drvdata get card & DAI names from DT */ + if (drvdata->card_name_dt) + of_property_read_string(i2s_node, + drvdata->card_name_dt, + &snd_rpi_wm8804.name); + + if (drvdata->dai_name_dt) + of_property_read_string(i2s_node, + drvdata->dai_name_dt, + &dai->name); + + if (drvdata->dai_stream_name_dt) + of_property_read_string(i2s_node, + drvdata->dai_stream_name_dt, + &dai->stream_name); + + dai->cpus->of_node = i2s_node; + dai->platforms->of_node = i2s_node; + + /* + * clk44gpio and clk48gpio are not required by all cards so + * don't check the error status. + */ + snd_clk44gpio = + devm_gpiod_get(&pdev->dev, "clock44", GPIOD_OUT_LOW); + + snd_clk48gpio = + devm_gpiod_get(&pdev->dev, "clock48", GPIOD_OUT_LOW); + + if (drvdata->probe) { + ret = drvdata->probe(pdev); + if (ret < 0) { + dev_err(&pdev->dev, "Custom probe failed %d\n", + ret); + return ret; + } + } + + pr_debug("%s card: %s dai: %s stream: %s\n", __func__, + snd_rpi_wm8804.name, + dai->name, dai->stream_name); + } + + ret = devm_snd_soc_register_card(&pdev->dev, &snd_rpi_wm8804); + if (ret && ret != -EPROBE_DEFER) + dev_err(&pdev->dev, "Failed to register card %d\n", ret); + + return ret; +} + +static struct platform_driver snd_rpi_wm8804_driver = { + .driver = { + .name = "snd-rpi-wm8804", + .owner = THIS_MODULE, + .of_match_table = snd_rpi_wm8804_of_match, + }, + .probe = snd_rpi_wm8804_probe, +}; +MODULE_DEVICE_TABLE(of, snd_rpi_wm8804_of_match); + +module_platform_driver(snd_rpi_wm8804_driver); + +MODULE_AUTHOR("Tim Gover "); +MODULE_DESCRIPTION("ASoC Raspberry Pi Hat generic digi driver for WM8804 based cards"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 6d7e4725d89c..b67989efc885 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -132,6 +132,7 @@ config SND_SOC_ALL_CODECS imply SND_SOC_IDT821034 imply SND_SOC_INNO_RK3036 imply SND_SOC_ISABELLE + imply SND_SOC_I_SABRE_CODEC imply SND_SOC_JZ4740_CODEC imply SND_SOC_JZ4725B_CODEC imply SND_SOC_JZ4760_CODEC @@ -139,6 +140,7 @@ config SND_SOC_ALL_CODECS imply SND_SOC_LM4857 imply SND_SOC_LM49453 imply SND_SOC_LOCHNAGAR_SC + imply SND_SOC_MA120X0P imply SND_SOC_MAX98088 imply SND_SOC_MAX98090 imply SND_SOC_MAX98095 @@ -182,6 +184,7 @@ config SND_SOC_ALL_CODECS imply SND_SOC_PCM179X_SPI imply SND_SOC_PCM186X_I2C imply SND_SOC_PCM186X_SPI + imply SND_SOC_PCM1794A imply SND_SOC_PCM3008 imply SND_SOC_PCM3060_I2C imply SND_SOC_PCM3060_SPI @@ -276,6 +279,7 @@ config SND_SOC_ALL_CODECS imply SND_SOC_TLV320ADCX140 imply SND_SOC_TLV320AIC23_I2C imply SND_SOC_TLV320AIC23_SPI + imply SND_SOC_TAS5713 imply SND_SOC_TLV320AIC26 imply SND_SOC_TLV320AIC31XX imply SND_SOC_TLV320AIC32X4_I2C @@ -436,12 +440,12 @@ config SND_SOC_AD193X tristate config SND_SOC_AD193X_SPI - tristate + tristate "Analog Devices AU193X CODEC - SPI" depends on SPI_MASTER select SND_SOC_AD193X config SND_SOC_AD193X_I2C - tristate + tristate "Analog Devices AU193X CODEC - I2C" depends on I2C select SND_SOC_AD193X @@ -1289,6 +1293,13 @@ config SND_SOC_LOCHNAGAR_SC This driver support the sound card functionality of the Cirrus Logic Lochnagar audio development board. +config SND_SOC_MA120X0P + tristate "Infineon Merus(TM) MA120X0P Multilevel Class-D Audio amplifiers" + depends on I2C + help + Enable support for Infineon MA120X0P Multilevel Class-D audio power + amplifiers. + config SND_SOC_MADERA tristate default y if SND_SOC_CS47L15=y @@ -1703,6 +1714,10 @@ config SND_SOC_RT5616 tristate "Realtek RT5616 CODEC" depends on I2C +config SND_SOC_PCM1794A + tristate + depends on I2C + config SND_SOC_RT5631 tristate "Realtek ALC5631/RT5631 CODEC" depends on I2C @@ -2101,6 +2116,9 @@ config SND_SOC_TFA9879 tristate "NXP Semiconductors TFA9879 amplifier" depends on I2C +config SND_SOC_TAS5713 + tristate + config SND_SOC_TFA989X tristate "NXP/Goodix TFA989X (TFA1) amplifiers" depends on I2C @@ -2723,4 +2741,8 @@ config SND_SOC_LPASS_TX_MACRO select SND_SOC_LPASS_MACRO_COMMON tristate "Qualcomm TX Macro in LPASS(Low Power Audio SubSystem)" +config SND_SOC_I_SABRE_CODEC + tristate "Audiophonics I-SABRE Codec" + depends on I2C + endmenu diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index a68c3d192a1b..bc5706d21034 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -856,3 +856,12 @@ obj-$(CONFIG_SND_SOC_LPASS_TX_MACRO) += snd-soc-lpass-tx-macro.o # Mux obj-$(CONFIG_SND_SOC_SIMPLE_MUX) += snd-soc-simple-mux.o + +snd-soc-i-sabre-codec-objs := i-sabre-codec.o +snd-soc-ma120x0p-objs := ma120x0p.o +snd-soc-pcm1794a-objs := pcm1794a.o +snd-soc-tas5713-objs := tas5713.o +obj-$(CONFIG_SND_SOC_I_SABRE_CODEC) += snd-soc-i-sabre-codec.o +obj-$(CONFIG_SND_SOC_MA120X0P) += snd-soc-ma120x0p.o +obj-$(CONFIG_SND_SOC_PCM1794A) += snd-soc-pcm1794a.o +obj-$(CONFIG_SND_SOC_TAS5713) += snd-soc-tas5713.o diff --git a/sound/soc/codecs/adau1977-i2c.c b/sound/soc/codecs/adau1977-i2c.c index 441c8079246a..034965008891 100644 --- a/sound/soc/codecs/adau1977-i2c.c +++ b/sound/soc/codecs/adau1977-i2c.c @@ -35,9 +35,19 @@ static const struct i2c_device_id adau1977_i2c_ids[] = { }; MODULE_DEVICE_TABLE(i2c, adau1977_i2c_ids); +static const struct of_device_id adau1977_of_ids[] = { + { .compatible = "adi,adau1977", }, + { .compatible = "adi,adau1978", }, + { .compatible = "adi,adau1979", }, + { } +}; +MODULE_DEVICE_TABLE(of, adau1977_of_ids); + + static struct i2c_driver adau1977_i2c_driver = { .driver = { .name = "adau1977", + .of_match_table = adau1977_of_ids, }, .probe = adau1977_i2c_probe, .id_table = adau1977_i2c_ids, diff --git a/sound/soc/codecs/cs42xx8-i2c.c b/sound/soc/codecs/cs42xx8-i2c.c index 039b3ecb3b9b..d1d11473066d 100644 --- a/sound/soc/codecs/cs42xx8-i2c.c +++ b/sound/soc/codecs/cs42xx8-i2c.c @@ -58,11 +58,18 @@ static const struct i2c_device_id cs42xx8_i2c_id[] = { }; MODULE_DEVICE_TABLE(i2c, cs42xx8_i2c_id); +const struct of_device_id cs42xx8_i2c_of_match[] = { + { .compatible = "cirrus,cs42448", .data = &cs42448_data, }, + { .compatible = "cirrus,cs42888", .data = &cs42888_data, }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, cs42xx8_i2c_of_match); + static struct i2c_driver cs42xx8_i2c_driver = { .driver = { .name = "cs42xx8", .pm = pm_ptr(&cs42xx8_pm), - .of_match_table = cs42xx8_of_match, + .of_match_table = cs42xx8_i2c_of_match, }, .probe = cs42xx8_i2c_probe, .remove = cs42xx8_i2c_remove, diff --git a/sound/soc/codecs/cs42xx8.c b/sound/soc/codecs/cs42xx8.c index 6a925f3f7137..b905e9a3538c 100644 --- a/sound/soc/codecs/cs42xx8.c +++ b/sound/soc/codecs/cs42xx8.c @@ -510,6 +510,16 @@ const struct cs42xx8_driver_data cs42888_data = { }; EXPORT_SYMBOL_GPL(cs42888_data); +const struct of_device_id cs42xx8_of_match[] = { + { .compatible = "cirrus,cs42448", .data = &cs42448_data, }, + { .compatible = "cirrus,cs42888", .data = &cs42888_data, }, + { /* sentinel */ } +}; +#if !IS_ENABLED(CONFIG_SND_SOC_CS42XX8_I2C) +MODULE_DEVICE_TABLE(of, cs42xx8_of_match); +EXPORT_SYMBOL_GPL(cs42xx8_of_match); +#endif + int cs42xx8_probe(struct device *dev, struct regmap *regmap, struct cs42xx8_driver_data *drvdata) { struct cs42xx8_priv *cs42xx8; diff --git a/sound/soc/codecs/da7213.c b/sound/soc/codecs/da7213.c index 3420011da444..96bb581ae631 100644 --- a/sound/soc/codecs/da7213.c +++ b/sound/soc/codecs/da7213.c @@ -1347,6 +1347,8 @@ static int da7213_hw_params(struct snd_pcm_substream *substream, switch (params_width(params)) { case 16: dai_ctrl |= DA7213_DAI_WORD_LENGTH_S16_LE; + if (da7213->bclk_ratio == 64) + break; dai_clk_mode = DA7213_DAI_BCLKS_PER_WCLK_32; /* 32bit for 1ch and 2ch */ break; case 20: @@ -1362,6 +1364,9 @@ static int da7213_hw_params(struct snd_pcm_substream *substream, return -EINVAL; } + if (da7213->bclk_ratio == 32 && params_width(params) != 16) + return -EINVAL; + /* Set sampling rate */ switch (params_rate(params)) { case 8000: @@ -1524,6 +1529,21 @@ static int da7213_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) return 0; } +static int da7213_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio) +{ + struct snd_soc_component *component = dai->component; + struct da7213_priv *da7213 = snd_soc_component_get_drvdata(component); + + if (ratio != 32 && ratio != 64) { + dev_err(component->dev, "Invalid bclk ratio %d\n", ratio); + return -EINVAL; + } + + da7213->bclk_ratio = ratio; + + return 0; +} + static int da7213_mute(struct snd_soc_dai *dai, int mute, int direction) { struct snd_soc_component *component = dai->component; @@ -1740,6 +1760,7 @@ static const u64 da7213_dai_formats = static const struct snd_soc_dai_ops da7213_dai_ops = { .hw_params = da7213_hw_params, .set_fmt = da7213_set_dai_fmt, + .set_bclk_ratio = da7213_set_bclk_ratio, .mute_stream = da7213_mute, .no_capture_mute = 1, .auto_selectable_formats = &da7213_dai_formats, diff --git a/sound/soc/codecs/da7213.h b/sound/soc/codecs/da7213.h index 29cbf0eb6124..ebff7be55d43 100644 --- a/sound/soc/codecs/da7213.h +++ b/sound/soc/codecs/da7213.h @@ -602,6 +602,7 @@ struct da7213_priv { unsigned int mclk_rate; unsigned int out_rate; unsigned int fin_min_rate; + unsigned int bclk_ratio; int clk_src; bool master; bool alc_calib_auto; diff --git a/sound/soc/codecs/i-sabre-codec.c b/sound/soc/codecs/i-sabre-codec.c new file mode 100644 index 000000000000..9903c6c9ef5f --- /dev/null +++ b/sound/soc/codecs/i-sabre-codec.c @@ -0,0 +1,389 @@ +/* + * Driver for I-Sabre Q2M + * + * Author: Satoru Kawase + * Modified by: Xiao Qingyong + * Modified by: JC BARBAUD (Mute) + * Update kernel v4.18+ by : Audiophonics + * Copyright 2018 Audiophonics + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + + +#include +#include +#include +#include +#include +#include +#include + +#include "i-sabre-codec.h" + + +/* I-Sabre Q2M Codec Private Data */ +struct i_sabre_codec_priv { + struct regmap *regmap; + unsigned int fmt; +}; + + +/* I-Sabre Q2M Codec Default Register Value */ +static const struct reg_default i_sabre_codec_reg_defaults[] = { + { ISABRECODEC_REG_10, 0x00 }, + { ISABRECODEC_REG_20, 0x00 }, + { ISABRECODEC_REG_21, 0x00 }, + { ISABRECODEC_REG_22, 0x00 }, + { ISABRECODEC_REG_24, 0x00 }, +}; + + +static bool i_sabre_codec_writeable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ISABRECODEC_REG_10: + case ISABRECODEC_REG_20: + case ISABRECODEC_REG_21: + case ISABRECODEC_REG_22: + case ISABRECODEC_REG_24: + return true; + + default: + return false; + } +} + +static bool i_sabre_codec_readable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ISABRECODEC_REG_01: + case ISABRECODEC_REG_02: + case ISABRECODEC_REG_10: + case ISABRECODEC_REG_20: + case ISABRECODEC_REG_21: + case ISABRECODEC_REG_22: + case ISABRECODEC_REG_24: + return true; + + default: + return false; + } +} + +static bool i_sabre_codec_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ISABRECODEC_REG_01: + case ISABRECODEC_REG_02: + return true; + + default: + return false; + } +} + + +/* Volume Scale */ +static const DECLARE_TLV_DB_SCALE(volume_tlv, -10000, 100, 0); + + +/* Filter Type */ +static const char * const fir_filter_type_texts[] = { + "brick wall", + "corrected minimum phase fast", + "minimum phase slow", + "minimum phase fast", + "linear phase slow", + "linear phase fast", + "apodizing fast", +}; + +static SOC_ENUM_SINGLE_DECL(i_sabre_fir_filter_type_enum, + ISABRECODEC_REG_22, 0, fir_filter_type_texts); + + +/* I2S / SPDIF Select */ +static const char * const iis_spdif_sel_texts[] = { + "I2S", + "SPDIF", +}; + +static SOC_ENUM_SINGLE_DECL(i_sabre_iis_spdif_sel_enum, + ISABRECODEC_REG_24, 0, iis_spdif_sel_texts); + + +/* Control */ +static const struct snd_kcontrol_new i_sabre_codec_controls[] = { +SOC_SINGLE_RANGE_TLV("Digital Playback Volume", ISABRECODEC_REG_20, 0, 0, 100, 1, volume_tlv), +SOC_SINGLE("Digital Playback Switch", ISABRECODEC_REG_21, 0, 1, 1), +SOC_ENUM("FIR Filter Type", i_sabre_fir_filter_type_enum), +SOC_ENUM("I2S/SPDIF Select", i_sabre_iis_spdif_sel_enum), +}; + + +static const u32 i_sabre_codec_dai_rates_slave[] = { + 8000, 11025, 16000, 22050, 32000, + 44100, 48000, 64000, 88200, 96000, + 176400, 192000, 352800, 384000, + 705600, 768000, 1411200, 1536000 +}; + +static const struct snd_pcm_hw_constraint_list constraints_slave = { + .list = i_sabre_codec_dai_rates_slave, + .count = ARRAY_SIZE(i_sabre_codec_dai_rates_slave), +}; + +static int i_sabre_codec_dai_startup_slave( + struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + int ret; + + ret = snd_pcm_hw_constraint_list(substream->runtime, + 0, SNDRV_PCM_HW_PARAM_RATE, &constraints_slave); + if (ret != 0) { + dev_err(component->card->dev, "Failed to setup rates constraints: %d\n", ret); + } + + return ret; +} + +static int i_sabre_codec_dai_startup( + struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct i_sabre_codec_priv *i_sabre_codec + = snd_soc_component_get_drvdata(component); + + switch (i_sabre_codec->fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBC_CFC: + return i_sabre_codec_dai_startup_slave(substream, dai); + + default: + return (-EINVAL); + } +} + +static int i_sabre_codec_hw_params( + struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct i_sabre_codec_priv *i_sabre_codec + = snd_soc_component_get_drvdata(component); + unsigned int daifmt; + int format_width; + + dev_dbg(component->card->dev, "hw_params %u Hz, %u channels\n", + params_rate(params), params_channels(params)); + + /* Check I2S Format (Bit Size) */ + format_width = snd_pcm_format_width(params_format(params)); + if ((format_width != 32) && (format_width != 16)) { + dev_err(component->card->dev, "Bad frame size: %d\n", + snd_pcm_format_width(params_format(params))); + return (-EINVAL); + } + + /* Check Slave Mode */ + daifmt = i_sabre_codec->fmt & SND_SOC_DAIFMT_MASTER_MASK; + if (daifmt != SND_SOC_DAIFMT_CBC_CFC) { + return (-EINVAL); + } + + /* Notify Sampling Frequency */ + switch (params_rate(params)) + { + case 44100: + case 48000: + case 88200: + case 96000: + case 176400: + case 192000: + snd_soc_component_update_bits(component, ISABRECODEC_REG_10, 0x01, 0x00); + break; + + case 352800: + case 384000: + case 705600: + case 768000: + case 1411200: + case 1536000: + snd_soc_component_update_bits(component, ISABRECODEC_REG_10, 0x01, 0x01); + break; + } + + return 0; +} + +static int i_sabre_codec_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + struct i_sabre_codec_priv *i_sabre_codec + = snd_soc_component_get_drvdata(component); + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + + case SND_SOC_DAIFMT_RIGHT_J: + case SND_SOC_DAIFMT_LEFT_J: + default: + return (-EINVAL); + } + + /* clock inversion */ + if ((fmt & SND_SOC_DAIFMT_INV_MASK) != SND_SOC_DAIFMT_NB_NF) { + return (-EINVAL); + } + + /* Set Audio Data Format */ + i_sabre_codec->fmt = fmt; + + return 0; +} + +static int i_sabre_codec_dac_mute(struct snd_soc_dai *dai, int mute, int direction) +{ + struct snd_soc_component *component = dai->component; + + if (mute) { + snd_soc_component_update_bits(component, ISABRECODEC_REG_21, 0x01, 0x01); + } else { + snd_soc_component_update_bits(component, ISABRECODEC_REG_21, 0x01, 0x00); + } + + return 0; +} + + +static const struct snd_soc_dai_ops i_sabre_codec_dai_ops = { + .startup = i_sabre_codec_dai_startup, + .hw_params = i_sabre_codec_hw_params, + .set_fmt = i_sabre_codec_set_fmt, + .mute_stream = i_sabre_codec_dac_mute, +}; + +static struct snd_soc_dai_driver i_sabre_codec_dai = { + .name = "i-sabre-codec-dai", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 8000, + .rate_max = 1536000, + .formats = SNDRV_PCM_FMTBIT_S16_LE + | SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &i_sabre_codec_dai_ops, +}; + +static struct snd_soc_component_driver i_sabre_codec_codec_driver = { + .controls = i_sabre_codec_controls, + .num_controls = ARRAY_SIZE(i_sabre_codec_controls), +}; + + +static const struct regmap_config i_sabre_codec_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = ISABRECODEC_MAX_REG, + + .reg_defaults = i_sabre_codec_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(i_sabre_codec_reg_defaults), + + .writeable_reg = i_sabre_codec_writeable, + .readable_reg = i_sabre_codec_readable, + .volatile_reg = i_sabre_codec_volatile, + + .cache_type = REGCACHE_RBTREE, +}; + + +static int i_sabre_codec_probe(struct device *dev, struct regmap *regmap) +{ + struct i_sabre_codec_priv *i_sabre_codec; + int ret; + + i_sabre_codec = devm_kzalloc(dev, sizeof(*i_sabre_codec), GFP_KERNEL); + if (!i_sabre_codec) { + dev_err(dev, "devm_kzalloc"); + return (-ENOMEM); + } + + i_sabre_codec->regmap = regmap; + + dev_set_drvdata(dev, i_sabre_codec); + + ret = snd_soc_register_component(dev, + &i_sabre_codec_codec_driver, &i_sabre_codec_dai, 1); + if (ret != 0) { + dev_err(dev, "Failed to register CODEC: %d\n", ret); + return ret; + } + + return 0; +} + +static void i_sabre_codec_remove(struct device *dev) +{ + snd_soc_unregister_component(dev); +} + + +static int i_sabre_codec_i2c_probe(struct i2c_client *i2c) +{ + struct regmap *regmap; + + regmap = devm_regmap_init_i2c(i2c, &i_sabre_codec_regmap); + if (IS_ERR(regmap)) { + return PTR_ERR(regmap); + } + + return i_sabre_codec_probe(&i2c->dev, regmap); +} + +static void i_sabre_codec_i2c_remove(struct i2c_client *i2c) +{ + i_sabre_codec_remove(&i2c->dev); +} + + +static const struct i2c_device_id i_sabre_codec_i2c_id[] = { + { "i-sabre-codec", }, + { } +}; +MODULE_DEVICE_TABLE(i2c, i_sabre_codec_i2c_id); + +static const struct of_device_id i_sabre_codec_of_match[] = { + { .compatible = "audiophonics,i-sabre-codec", }, + { } +}; +MODULE_DEVICE_TABLE(of, i_sabre_codec_of_match); + +static struct i2c_driver i_sabre_codec_i2c_driver = { + .driver = { + .name = "i-sabre-codec-i2c", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(i_sabre_codec_of_match), + }, + .probe = i_sabre_codec_i2c_probe, + .remove = i_sabre_codec_i2c_remove, + .id_table = i_sabre_codec_i2c_id, +}; +module_i2c_driver(i_sabre_codec_i2c_driver); + + +MODULE_DESCRIPTION("ASoC I-Sabre Q2M codec driver"); +MODULE_AUTHOR("Audiophonics "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/i-sabre-codec.h b/sound/soc/codecs/i-sabre-codec.h new file mode 100644 index 000000000000..9cac5a2446b9 --- /dev/null +++ b/sound/soc/codecs/i-sabre-codec.h @@ -0,0 +1,42 @@ +/* + * Driver for I-Sabre Q2M + * + * Author: Satoru Kawase + * Modified by: Xiao Qingyong + * Copyright 2018 Audiophonics + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#ifndef _SND_SOC_ISABRECODEC +#define _SND_SOC_ISABRECODEC + + +/* ISABRECODEC Register Address */ +#define ISABRECODEC_REG_01 0x01 /* Virtual Device ID : 0x01 = es9038q2m */ +#define ISABRECODEC_REG_02 0x02 /* API revision : 0x01 = Revision 01 */ +#define ISABRECODEC_REG_10 0x10 /* 0x01 = above 192kHz, 0x00 = otherwise */ +#define ISABRECODEC_REG_20 0x20 /* 0 - 100 (decimal value, 0 = min., 100 = max.) */ +#define ISABRECODEC_REG_21 0x21 /* 0x00 = Mute OFF, 0x01 = Mute ON */ +#define ISABRECODEC_REG_22 0x22 +/* + 0x00 = brick wall, + 0x01 = corrected minimum phase fast, + 0x02 = minimum phase slow, + 0x03 = minimum phase fast, + 0x04 = linear phase slow, + 0x05 = linear phase fast, + 0x06 = apodizing fast, +*/ +//#define ISABRECODEC_REG_23 0x23 /* reserved */ +#define ISABRECODEC_REG_24 0x24 /* 0x00 = I2S, 0x01 = SPDIF */ +#define ISABRECODEC_MAX_REG 0x24 /* Maximum Register Number */ + +#endif /* _SND_SOC_ISABRECODEC */ diff --git a/sound/soc/codecs/ma120x0p.c b/sound/soc/codecs/ma120x0p.c new file mode 100644 index 000000000000..3df7e759ace1 --- /dev/null +++ b/sound/soc/codecs/ma120x0p.c @@ -0,0 +1,1380 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ASoC Driver for Infineon Merus(TM) ma120x0p multi-level class-D amplifier + * + * Authors: Ariel Muszkat + * Jorgen Kragh Jakobsen + * + * Copyright (C) 2019 Infineon Technologies AG + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#ifndef _MA120X0P_ +#define _MA120X0P_ +//------------------------------------------------------------------manualPM--- +// Select Manual PowerMode control +#define ma_manualpm__a 0 +#define ma_manualpm__len 1 +#define ma_manualpm__mask 0x40 +#define ma_manualpm__shift 0x06 +#define ma_manualpm__reset 0x00 +//--------------------------------------------------------------------pm_man--- +// manual selected power mode +#define ma_pm_man__a 0 +#define ma_pm_man__len 2 +#define ma_pm_man__mask 0x30 +#define ma_pm_man__shift 0x04 +#define ma_pm_man__reset 0x03 +//------------------------------------------ ----------------------mthr_1to2--- +// mod. index threshold value for pm1=>pm2 change. +#define ma_mthr_1to2__a 1 +#define ma_mthr_1to2__len 8 +#define ma_mthr_1to2__mask 0xff +#define ma_mthr_1to2__shift 0x00 +#define ma_mthr_1to2__reset 0x3c +//-----------------------------------------------------------------mthr_2to1--- +// mod. index threshold value for pm2=>pm1 change. +#define ma_mthr_2to1__a 2 +#define ma_mthr_2to1__len 8 +#define ma_mthr_2to1__mask 0xff +#define ma_mthr_2to1__shift 0x00 +#define ma_mthr_2to1__reset 0x32 +//-----------------------------------------------------------------mthr_2to3--- +// mod. index threshold value for pm2=>pm3 change. +#define ma_mthr_2to3__a 3 +#define ma_mthr_2to3__len 8 +#define ma_mthr_2to3__mask 0xff +#define ma_mthr_2to3__shift 0x00 +#define ma_mthr_2to3__reset 0x5a +//-----------------------------------------------------------------mthr_3to2--- +// mod. index threshold value for pm3=>pm2 change. +#define ma_mthr_3to2__a 4 +#define ma_mthr_3to2__len 8 +#define ma_mthr_3to2__mask 0xff +#define ma_mthr_3to2__shift 0x00 +#define ma_mthr_3to2__reset 0x50 +//-------------------------------------------------------------pwmclkdiv_nom--- +// pwm default clock divider value +#define ma_pwmclkdiv_nom__a 8 +#define ma_pwmclkdiv_nom__len 8 +#define ma_pwmclkdiv_nom__mask 0xff +#define ma_pwmclkdiv_nom__shift 0x00 +#define ma_pwmclkdiv_nom__reset 0x26 +//--------- ----------------------------------------------------ocp_latch_en--- +// high to use permanently latching level-2 ocp +#define ma_ocp_latch_en__a 10 +#define ma_ocp_latch_en__len 1 +#define ma_ocp_latch_en__mask 0x02 +#define ma_ocp_latch_en__shift 0x01 +#define ma_ocp_latch_en__reset 0x00 +//---------------------------------------------------------------lf_clamp_en--- +// high (default) to enable lf int2+3 clamping on clip +#define ma_lf_clamp_en__a 10 +#define ma_lf_clamp_en__len 1 +#define ma_lf_clamp_en__mask 0x80 +#define ma_lf_clamp_en__shift 0x07 +#define ma_lf_clamp_en__reset 0x00 +//-------------------------------------------------------pmcfg_btl_b.modtype--- +// +#define ma_pmcfg_btl_b__modtype__a 18 +#define ma_pmcfg_btl_b__modtype__len 2 +#define ma_pmcfg_btl_b__modtype__mask 0x18 +#define ma_pmcfg_btl_b__modtype__shift 0x03 +#define ma_pmcfg_btl_b__modtype__reset 0x02 +//-------------------------------------------------------pmcfg_btl_b.freqdiv--- +#define ma_pmcfg_btl_b__freqdiv__a 18 +#define ma_pmcfg_btl_b__freqdiv__len 2 +#define ma_pmcfg_btl_b__freqdiv__mask 0x06 +#define ma_pmcfg_btl_b__freqdiv__shift 0x01 +#define ma_pmcfg_btl_b__freqdiv__reset 0x01 +//----------------------------------------------------pmcfg_btl_b.lf_gain_ol--- +// +#define ma_pmcfg_btl_b__lf_gain_ol__a 18 +#define ma_pmcfg_btl_b__lf_gain_ol__len 1 +#define ma_pmcfg_btl_b__lf_gain_ol__mask 0x01 +#define ma_pmcfg_btl_b__lf_gain_ol__shift 0x00 +#define ma_pmcfg_btl_b__lf_gain_ol__reset 0x01 +//-------------------------------------------------------pmcfg_btl_c.freqdiv--- +// +#define ma_pmcfg_btl_c__freqdiv__a 19 +#define ma_pmcfg_btl_c__freqdiv__len 2 +#define ma_pmcfg_btl_c__freqdiv__mask 0x06 +#define ma_pmcfg_btl_c__freqdiv__shift 0x01 +#define ma_pmcfg_btl_c__freqdiv__reset 0x01 +//-------------------------------------------------------pmcfg_btl_c.modtype--- +// +#define ma_pmcfg_btl_c__modtype__a 19 +#define ma_pmcfg_btl_c__modtype__len 2 +#define ma_pmcfg_btl_c__modtype__mask 0x18 +#define ma_pmcfg_btl_c__modtype__shift 0x03 +#define ma_pmcfg_btl_c__modtype__reset 0x01 +//----------------------------------------------------pmcfg_btl_c.lf_gain_ol--- +// +#define ma_pmcfg_btl_c__lf_gain_ol__a 19 +#define ma_pmcfg_btl_c__lf_gain_ol__len 1 +#define ma_pmcfg_btl_c__lf_gain_ol__mask 0x01 +#define ma_pmcfg_btl_c__lf_gain_ol__shift 0x00 +#define ma_pmcfg_btl_c__lf_gain_ol__reset 0x00 +//-------------------------------------------------------pmcfg_btl_d.modtype--- +// +#define ma_pmcfg_btl_d__modtype__a 20 +#define ma_pmcfg_btl_d__modtype__len 2 +#define ma_pmcfg_btl_d__modtype__mask 0x18 +#define ma_pmcfg_btl_d__modtype__shift 0x03 +#define ma_pmcfg_btl_d__modtype__reset 0x02 +//-------------------------------------------------------pmcfg_btl_d.freqdiv--- +// +#define ma_pmcfg_btl_d__freqdiv__a 20 +#define ma_pmcfg_btl_d__freqdiv__len 2 +#define ma_pmcfg_btl_d__freqdiv__mask 0x06 +#define ma_pmcfg_btl_d__freqdiv__shift 0x01 +#define ma_pmcfg_btl_d__freqdiv__reset 0x02 +//----------------------------------------------------pmcfg_btl_d.lf_gain_ol--- +// +#define ma_pmcfg_btl_d__lf_gain_ol__a 20 +#define ma_pmcfg_btl_d__lf_gain_ol__len 1 +#define ma_pmcfg_btl_d__lf_gain_ol__mask 0x01 +#define ma_pmcfg_btl_d__lf_gain_ol__shift 0x00 +#define ma_pmcfg_btl_d__lf_gain_ol__reset 0x00 +//------------ -------------------------------------------pmcfg_se_a.modtype--- +// +#define ma_pmcfg_se_a__modtype__a 21 +#define ma_pmcfg_se_a__modtype__len 2 +#define ma_pmcfg_se_a__modtype__mask 0x18 +#define ma_pmcfg_se_a__modtype__shift 0x03 +#define ma_pmcfg_se_a__modtype__reset 0x01 +//--------------------------------------------------------pmcfg_se_a.freqdiv--- +// +#define ma_pmcfg_se_a__freqdiv__a 21 +#define ma_pmcfg_se_a__freqdiv__len 2 +#define ma_pmcfg_se_a__freqdiv__mask 0x06 +#define ma_pmcfg_se_a__freqdiv__shift 0x01 +#define ma_pmcfg_se_a__freqdiv__reset 0x00 +//-----------------------------------------------------pmcfg_se_a.lf_gain_ol--- +// +#define ma_pmcfg_se_a__lf_gain_ol__a 21 +#define ma_pmcfg_se_a__lf_gain_ol__len 1 +#define ma_pmcfg_se_a__lf_gain_ol__mask 0x01 +#define ma_pmcfg_se_a__lf_gain_ol__shift 0x00 +#define ma_pmcfg_se_a__lf_gain_ol__reset 0x01 +//-----------------------------------------------------pmcfg_se_b.lf_gain_ol--- +// +#define ma_pmcfg_se_b__lf_gain_ol__a 22 +#define ma_pmcfg_se_b__lf_gain_ol__len 1 +#define ma_pmcfg_se_b__lf_gain_ol__mask 0x01 +#define ma_pmcfg_se_b__lf_gain_ol__shift 0x00 +#define ma_pmcfg_se_b__lf_gain_ol__reset 0x00 +//--------------------------------------------------------pmcfg_se_b.freqdiv--- +// +#define ma_pmcfg_se_b__freqdiv__a 22 +#define ma_pmcfg_se_b__freqdiv__len 2 +#define ma_pmcfg_se_b__freqdiv__mask 0x06 +#define ma_pmcfg_se_b__freqdiv__shift 0x01 +#define ma_pmcfg_se_b__freqdiv__reset 0x01 +//--------------------------------------------------------pmcfg_se_b.modtype--- +// +#define ma_pmcfg_se_b__modtype__a 22 +#define ma_pmcfg_se_b__modtype__len 2 +#define ma_pmcfg_se_b__modtype__mask 0x18 +#define ma_pmcfg_se_b__modtype__shift 0x03 +#define ma_pmcfg_se_b__modtype__reset 0x01 +//----------------------------------------------------------balwaitcount_pm1--- +// pm1 balancing period. +#define ma_balwaitcount_pm1__a 23 +#define ma_balwaitcount_pm1__len 8 +#define ma_balwaitcount_pm1__mask 0xff +#define ma_balwaitcount_pm1__shift 0x00 +#define ma_balwaitcount_pm1__reset 0x14 +//----------------------------------------------------------balwaitcount_pm2--- +// pm2 balancing period. +#define ma_balwaitcount_pm2__a 24 +#define ma_balwaitcount_pm2__len 8 +#define ma_balwaitcount_pm2__mask 0xff +#define ma_balwaitcount_pm2__shift 0x00 +#define ma_balwaitcount_pm2__reset 0x14 +//----------------------------------------------------------balwaitcount_pm3--- +// pm3 balancing period. +#define ma_balwaitcount_pm3__a 25 +#define ma_balwaitcount_pm3__len 8 +#define ma_balwaitcount_pm3__mask 0xff +#define ma_balwaitcount_pm3__shift 0x00 +#define ma_balwaitcount_pm3__reset 0x1a +//-------------------------------------------------------------usespread_pm1--- +// pm1 pwm spread-spectrum mode on/off. +#define ma_usespread_pm1__a 26 +#define ma_usespread_pm1__len 1 +#define ma_usespread_pm1__mask 0x40 +#define ma_usespread_pm1__shift 0x06 +#define ma_usespread_pm1__reset 0x00 +//---------------------------------------------------------------dtsteps_pm1--- +// pm1 dead time setting [10ns steps]. +#define ma_dtsteps_pm1__a 26 +#define ma_dtsteps_pm1__len 3 +#define ma_dtsteps_pm1__mask 0x38 +#define ma_dtsteps_pm1__shift 0x03 +#define ma_dtsteps_pm1__reset 0x04 +//---------------------------------------------------------------baltype_pm1--- +// pm1 balancing sensor scheme. +#define ma_baltype_pm1__a 26 +#define ma_baltype_pm1__len 3 +#define ma_baltype_pm1__mask 0x07 +#define ma_baltype_pm1__shift 0x00 +#define ma_baltype_pm1__reset 0x00 +//-------------------------------------------------------------usespread_pm2--- +// pm2 pwm spread-spectrum mode on/off. +#define ma_usespread_pm2__a 27 +#define ma_usespread_pm2__len 1 +#define ma_usespread_pm2__mask 0x40 +#define ma_usespread_pm2__shift 0x06 +#define ma_usespread_pm2__reset 0x00 +//---------------------------------------------------------------dtsteps_pm2--- +// pm2 dead time setting [10ns steps]. +#define ma_dtsteps_pm2__a 27 +#define ma_dtsteps_pm2__len 3 +#define ma_dtsteps_pm2__mask 0x38 +#define ma_dtsteps_pm2__shift 0x03 +#define ma_dtsteps_pm2__reset 0x03 +//---------------------------------------------------------------baltype_pm2--- +// pm2 balancing sensor scheme. +#define ma_baltype_pm2__a 27 +#define ma_baltype_pm2__len 3 +#define ma_baltype_pm2__mask 0x07 +#define ma_baltype_pm2__shift 0x00 +#define ma_baltype_pm2__reset 0x01 +//-------------------------------------------------------------usespread_pm3--- +// pm3 pwm spread-spectrum mode on/off. +#define ma_usespread_pm3__a 28 +#define ma_usespread_pm3__len 1 +#define ma_usespread_pm3__mask 0x40 +#define ma_usespread_pm3__shift 0x06 +#define ma_usespread_pm3__reset 0x00 +//---------------------------------------------------------------dtsteps_pm3--- +// pm3 dead time setting [10ns steps]. +#define ma_dtsteps_pm3__a 28 +#define ma_dtsteps_pm3__len 3 +#define ma_dtsteps_pm3__mask 0x38 +#define ma_dtsteps_pm3__shift 0x03 +#define ma_dtsteps_pm3__reset 0x01 +//---------------------------------------------------------------baltype_pm3--- +// pm3 balancing sensor scheme. +#define ma_baltype_pm3__a 28 +#define ma_baltype_pm3__len 3 +#define ma_baltype_pm3__mask 0x07 +#define ma_baltype_pm3__shift 0x00 +#define ma_baltype_pm3__reset 0x03 +//-----------------------------------------------------------------pmprofile--- +// pm profile select. valid presets: 0-1-2-3-4. 5=> custom profile. +#define ma_pmprofile__a 29 +#define ma_pmprofile__len 3 +#define ma_pmprofile__mask 0x07 +#define ma_pmprofile__shift 0x00 +#define ma_pmprofile__reset 0x00 +//-------------------------------------------------------------------pm3_man--- +// custom profile pm3 contents. 0=>a, 1=>b, 2=>c, 3=>d +#define ma_pm3_man__a 30 +#define ma_pm3_man__len 2 +#define ma_pm3_man__mask 0x30 +#define ma_pm3_man__shift 0x04 +#define ma_pm3_man__reset 0x02 +//-------------------------------------------------------------------pm2_man--- +// custom profile pm2 contents. 0=>a, 1=>b, 2=>c, 3=>d +#define ma_pm2_man__a 30 +#define ma_pm2_man__len 2 +#define ma_pm2_man__mask 0x0c +#define ma_pm2_man__shift 0x02 +#define ma_pm2_man__reset 0x03 +//-------------------------------------------------------------------pm1_man--- +// custom profile pm1 contents. 0=>a, 1=>b, 2=>c, 3=>d +#define ma_pm1_man__a 30 +#define ma_pm1_man__len 2 +#define ma_pm1_man__mask 0x03 +#define ma_pm1_man__shift 0x00 +#define ma_pm1_man__reset 0x03 +//-----------------------------------------------------------ocp_latch_clear--- +// low-high clears current ocp latched condition. +#define ma_ocp_latch_clear__a 32 +#define ma_ocp_latch_clear__len 1 +#define ma_ocp_latch_clear__mask 0x80 +#define ma_ocp_latch_clear__shift 0x07 +#define ma_ocp_latch_clear__reset 0x00 +//-------------------------------------------------------------audio_in_mode--- +// audio input mode; 0-1-2-3-4-5 +#define ma_audio_in_mode__a 37 +#define ma_audio_in_mode__len 3 +#define ma_audio_in_mode__mask 0xe0 +#define ma_audio_in_mode__shift 0x05 +#define ma_audio_in_mode__reset 0x00 +//-----------------------------------------------------------------eh_dcshdn--- +// high to enable dc protection +#define ma_eh_dcshdn__a 38 +#define ma_eh_dcshdn__len 1 +#define ma_eh_dcshdn__mask 0x04 +#define ma_eh_dcshdn__shift 0x02 +#define ma_eh_dcshdn__reset 0x01 +//---------------------------------------------------------audio_in_mode_ext--- +// if set, audio_in_mode is controlled from audio_in_mode register. if not set +//audio_in_mode is set from fuse bank setting +#define ma_audio_in_mode_ext__a 39 +#define ma_audio_in_mode_ext__len 1 +#define ma_audio_in_mode_ext__mask 0x20 +#define ma_audio_in_mode_ext__shift 0x05 +#define ma_audio_in_mode_ext__reset 0x00 +//------------------------------------------------------------------eh_clear--- +// flip to clear error registers +#define ma_eh_clear__a 45 +#define ma_eh_clear__len 1 +#define ma_eh_clear__mask 0x04 +#define ma_eh_clear__shift 0x02 +#define ma_eh_clear__reset 0x00 +//----------------------------------------------------------thermal_compr_en--- +// enable otw-contr. input compression? +#define ma_thermal_compr_en__a 45 +#define ma_thermal_compr_en__len 1 +#define ma_thermal_compr_en__mask 0x20 +#define ma_thermal_compr_en__shift 0x05 +#define ma_thermal_compr_en__reset 0x01 +//---------------------------------------------------------------system_mute--- +// 1 = mute system, 0 = normal operation +#define ma_system_mute__a 45 +#define ma_system_mute__len 1 +#define ma_system_mute__mask 0x40 +#define ma_system_mute__shift 0x06 +#define ma_system_mute__reset 0x00 +//------------------------------------------------------thermal_compr_max_db--- +// audio limiter max thermal reduction +#define ma_thermal_compr_max_db__a 46 +#define ma_thermal_compr_max_db__len 3 +#define ma_thermal_compr_max_db__mask 0x07 +#define ma_thermal_compr_max_db__shift 0x00 +#define ma_thermal_compr_max_db__reset 0x04 +//---------------------------------------------------------audio_proc_enable--- +// enable audio proc, bypass if not enabled +#define ma_audio_proc_enable__a 53 +#define ma_audio_proc_enable__len 1 +#define ma_audio_proc_enable__mask 0x08 +#define ma_audio_proc_enable__shift 0x03 +#define ma_audio_proc_enable__reset 0x00 +//--------------------------------------------------------audio_proc_release--- +// 00:slow, 01:normal, 10:fast +#define ma_audio_proc_release__a 53 +#define ma_audio_proc_release__len 2 +#define ma_audio_proc_release__mask 0x30 +#define ma_audio_proc_release__shift 0x04 +#define ma_audio_proc_release__reset 0x00 +//---------------------------------------------------------audio_proc_attack--- +// 00:slow, 01:normal, 10:fast +#define ma_audio_proc_attack__a 53 +#define ma_audio_proc_attack__len 2 +#define ma_audio_proc_attack__mask 0xc0 +#define ma_audio_proc_attack__shift 0x06 +#define ma_audio_proc_attack__reset 0x00 +//----------------------------------------------------------------i2s_format--- +// i2s basic data format, 000 = std. i2s, 001 = left justified (default) +#define ma_i2s_format__a 53 +#define ma_i2s_format__len 3 +#define ma_i2s_format__mask 0x07 +#define ma_i2s_format__shift 0x00 +#define ma_i2s_format__reset 0x01 +//--------------------------------------------------audio_proc_limiterenable--- +// 1: enable limiter, 0: disable limiter +#define ma_audio_proc_limiterenable__a 54 +#define ma_audio_proc_limiterenable__len 1 +#define ma_audio_proc_limiterenable__mask 0x40 +#define ma_audio_proc_limiterenable__shift 0x06 +#define ma_audio_proc_limiterenable__reset 0x00 +//-----------------------------------------------------------audio_proc_mute--- +// 1: mute, 0: unmute +#define ma_audio_proc_mute__a 54 +#define ma_audio_proc_mute__len 1 +#define ma_audio_proc_mute__mask 0x80 +#define ma_audio_proc_mute__shift 0x07 +#define ma_audio_proc_mute__reset 0x00 +//---------------------------------------------------------------i2s_sck_pol--- +// i2s sck polarity cfg. 0 = rising edge data change +#define ma_i2s_sck_pol__a 54 +#define ma_i2s_sck_pol__len 1 +#define ma_i2s_sck_pol__mask 0x01 +#define ma_i2s_sck_pol__shift 0x00 +#define ma_i2s_sck_pol__reset 0x01 +//-------------------------------------------------------------i2s_framesize--- +// i2s word length. 00 = 32bit, 01 = 24bit +#define ma_i2s_framesize__a 54 +#define ma_i2s_framesize__len 2 +#define ma_i2s_framesize__mask 0x18 +#define ma_i2s_framesize__shift 0x03 +#define ma_i2s_framesize__reset 0x00 +//----------------------------------------------------------------i2s_ws_pol--- +// i2s ws polarity. 0 = low first +#define ma_i2s_ws_pol__a 54 +#define ma_i2s_ws_pol__len 1 +#define ma_i2s_ws_pol__mask 0x02 +#define ma_i2s_ws_pol__shift 0x01 +#define ma_i2s_ws_pol__reset 0x00 +//-----------------------------------------------------------------i2s_order--- +// i2s word bit order. 0 = msb first +#define ma_i2s_order__a 54 +#define ma_i2s_order__len 1 +#define ma_i2s_order__mask 0x04 +#define ma_i2s_order__shift 0x02 +#define ma_i2s_order__reset 0x00 +//------------------------------------------------------------i2s_rightfirst--- +// i2s l/r word order; 0 = left first +#define ma_i2s_rightfirst__a 54 +#define ma_i2s_rightfirst__len 1 +#define ma_i2s_rightfirst__mask 0x20 +#define ma_i2s_rightfirst__shift 0x05 +#define ma_i2s_rightfirst__reset 0x00 +//-------------------------------------------------------------vol_db_master--- +// master volume db +#define ma_vol_db_master__a 64 +#define ma_vol_db_master__len 8 +#define ma_vol_db_master__mask 0xff +#define ma_vol_db_master__shift 0x00 +#define ma_vol_db_master__reset 0x18 +//------------------------------------------------------------vol_lsb_master--- +// master volume lsb 1/4 steps +#define ma_vol_lsb_master__a 65 +#define ma_vol_lsb_master__len 2 +#define ma_vol_lsb_master__mask 0x03 +#define ma_vol_lsb_master__shift 0x00 +#define ma_vol_lsb_master__reset 0x00 +//----------------------------------------------------------------vol_db_ch0--- +// volume channel 0 +#define ma_vol_db_ch0__a 66 +#define ma_vol_db_ch0__len 8 +#define ma_vol_db_ch0__mask 0xff +#define ma_vol_db_ch0__shift 0x00 +#define ma_vol_db_ch0__reset 0x18 +//----------------------------------------------------------------vol_db_ch1--- +// volume channel 1 +#define ma_vol_db_ch1__a 67 +#define ma_vol_db_ch1__len 8 +#define ma_vol_db_ch1__mask 0xff +#define ma_vol_db_ch1__shift 0x00 +#define ma_vol_db_ch1__reset 0x18 +//----------------------------------------------------------------vol_db_ch2--- +// volume channel 2 +#define ma_vol_db_ch2__a 68 +#define ma_vol_db_ch2__len 8 +#define ma_vol_db_ch2__mask 0xff +#define ma_vol_db_ch2__shift 0x00 +#define ma_vol_db_ch2__reset 0x18 +//----------------------------------------------------------------vol_db_ch3--- +// volume channel 3 +#define ma_vol_db_ch3__a 69 +#define ma_vol_db_ch3__len 8 +#define ma_vol_db_ch3__mask 0xff +#define ma_vol_db_ch3__shift 0x00 +#define ma_vol_db_ch3__reset 0x18 +//---------------------------------------------------------------vol_lsb_ch0--- +// volume channel 1 - 1/4 steps +#define ma_vol_lsb_ch0__a 70 +#define ma_vol_lsb_ch0__len 2 +#define ma_vol_lsb_ch0__mask 0x03 +#define ma_vol_lsb_ch0__shift 0x00 +#define ma_vol_lsb_ch0__reset 0x00 +//---------------------------------------------------------------vol_lsb_ch1--- +// volume channel 3 - 1/4 steps +#define ma_vol_lsb_ch1__a 70 +#define ma_vol_lsb_ch1__len 2 +#define ma_vol_lsb_ch1__mask 0x0c +#define ma_vol_lsb_ch1__shift 0x02 +#define ma_vol_lsb_ch1__reset 0x00 +//---------------------------------------------------------------vol_lsb_ch2--- +// volume channel 2 - 1/4 steps +#define ma_vol_lsb_ch2__a 70 +#define ma_vol_lsb_ch2__len 2 +#define ma_vol_lsb_ch2__mask 0x30 +#define ma_vol_lsb_ch2__shift 0x04 +#define ma_vol_lsb_ch2__reset 0x00 +//---------------------------------------------------------------vol_lsb_ch3--- +// volume channel 3 - 1/4 steps +#define ma_vol_lsb_ch3__a 70 +#define ma_vol_lsb_ch3__len 2 +#define ma_vol_lsb_ch3__mask 0xc0 +#define ma_vol_lsb_ch3__shift 0x06 +#define ma_vol_lsb_ch3__reset 0x00 +//----------------------------------------------------------------thr_db_ch0--- +// thr_db channel 0 +#define ma_thr_db_ch0__a 71 +#define ma_thr_db_ch0__len 8 +#define ma_thr_db_ch0__mask 0xff +#define ma_thr_db_ch0__shift 0x00 +#define ma_thr_db_ch0__reset 0x18 +//----------------------------------------------------------------thr_db_ch1--- +// thr db ch1 +#define ma_thr_db_ch1__a 72 +#define ma_thr_db_ch1__len 8 +#define ma_thr_db_ch1__mask 0xff +#define ma_thr_db_ch1__shift 0x00 +#define ma_thr_db_ch1__reset 0x18 +//----------------------------------------------------------------thr_db_ch2--- +// thr db ch2 +#define ma_thr_db_ch2__a 73 +#define ma_thr_db_ch2__len 8 +#define ma_thr_db_ch2__mask 0xff +#define ma_thr_db_ch2__shift 0x00 +#define ma_thr_db_ch2__reset 0x18 +//----------------------------------------------------------------thr_db_ch3--- +// threshold db ch3 +#define ma_thr_db_ch3__a 74 +#define ma_thr_db_ch3__len 8 +#define ma_thr_db_ch3__mask 0xff +#define ma_thr_db_ch3__shift 0x00 +#define ma_thr_db_ch3__reset 0x18 +//---------------------------------------------------------------thr_lsb_ch0--- +// thr lsb ch0 +#define ma_thr_lsb_ch0__a 75 +#define ma_thr_lsb_ch0__len 2 +#define ma_thr_lsb_ch0__mask 0x03 +#define ma_thr_lsb_ch0__shift 0x00 +#define ma_thr_lsb_ch0__reset 0x00 +//---------------------------------------------------------------thr_lsb_ch1--- +// thr lsb ch1 +#define ma_thr_lsb_ch1__a 75 +#define ma_thr_lsb_ch1__len 2 +#define ma_thr_lsb_ch1__mask 0x0c +#define ma_thr_lsb_ch1__shift 0x02 +#define ma_thr_lsb_ch1__reset 0x00 +//---------------------------------------------------------------thr_lsb_ch2--- +// thr lsb ch2 1/4 db step +#define ma_thr_lsb_ch2__a 75 +#define ma_thr_lsb_ch2__len 2 +#define ma_thr_lsb_ch2__mask 0x30 +#define ma_thr_lsb_ch2__shift 0x04 +#define ma_thr_lsb_ch2__reset 0x00 +//---------------------------------------------------------------thr_lsb_ch3--- +// threshold lsb ch3 +#define ma_thr_lsb_ch3__a 75 +#define ma_thr_lsb_ch3__len 2 +#define ma_thr_lsb_ch3__mask 0xc0 +#define ma_thr_lsb_ch3__shift 0x06 +#define ma_thr_lsb_ch3__reset 0x00 +//-----------------------------------------------------------dcu_mon0.pm_mon--- +// power mode monitor channel 0 +#define ma_dcu_mon0__pm_mon__a 96 +#define ma_dcu_mon0__pm_mon__len 2 +#define ma_dcu_mon0__pm_mon__mask 0x03 +#define ma_dcu_mon0__pm_mon__shift 0x00 +#define ma_dcu_mon0__pm_mon__reset 0x00 +//-----------------------------------------------------dcu_mon0.freqmode_mon--- +// frequence mode monitor channel 0 +#define ma_dcu_mon0__freqmode_mon__a 96 +#define ma_dcu_mon0__freqmode_mon__len 3 +#define ma_dcu_mon0__freqmode_mon__mask 0x70 +#define ma_dcu_mon0__freqmode_mon__shift 0x04 +#define ma_dcu_mon0__freqmode_mon__reset 0x00 +//-------------------------------------------------------dcu_mon0.pps_passed--- +// dcu0 pps completion indicator +#define ma_dcu_mon0__pps_passed__a 96 +#define ma_dcu_mon0__pps_passed__len 1 +#define ma_dcu_mon0__pps_passed__mask 0x80 +#define ma_dcu_mon0__pps_passed__shift 0x07 +#define ma_dcu_mon0__pps_passed__reset 0x00 +//----------------------------------------------------------dcu_mon0.ocp_mon--- +// ocp monitor channel 0 +#define ma_dcu_mon0__ocp_mon__a 97 +#define ma_dcu_mon0__ocp_mon__len 1 +#define ma_dcu_mon0__ocp_mon__mask 0x01 +#define ma_dcu_mon0__ocp_mon__shift 0x00 +#define ma_dcu_mon0__ocp_mon__reset 0x00 +//--------------------------------------------------------dcu_mon0.vcfly1_ok--- +// cfly1 protection monitor channel 0. +#define ma_dcu_mon0__vcfly1_ok__a 97 +#define ma_dcu_mon0__vcfly1_ok__len 1 +#define ma_dcu_mon0__vcfly1_ok__mask 0x02 +#define ma_dcu_mon0__vcfly1_ok__shift 0x01 +#define ma_dcu_mon0__vcfly1_ok__reset 0x00 +//--------------------------------------------------------dcu_mon0.vcfly2_ok--- +// cfly2 protection monitor channel 0. +#define ma_dcu_mon0__vcfly2_ok__a 97 +#define ma_dcu_mon0__vcfly2_ok__len 1 +#define ma_dcu_mon0__vcfly2_ok__mask 0x04 +#define ma_dcu_mon0__vcfly2_ok__shift 0x02 +#define ma_dcu_mon0__vcfly2_ok__reset 0x00 +//----------------------------------------------------------dcu_mon0.pvdd_ok--- +// dcu0 pvdd monitor +#define ma_dcu_mon0__pvdd_ok__a 97 +#define ma_dcu_mon0__pvdd_ok__len 1 +#define ma_dcu_mon0__pvdd_ok__mask 0x08 +#define ma_dcu_mon0__pvdd_ok__shift 0x03 +#define ma_dcu_mon0__pvdd_ok__reset 0x00 +//-----------------------------------------------------------dcu_mon0.vdd_ok--- +// dcu0 vdd monitor +#define ma_dcu_mon0__vdd_ok__a 97 +#define ma_dcu_mon0__vdd_ok__len 1 +#define ma_dcu_mon0__vdd_ok__mask 0x10 +#define ma_dcu_mon0__vdd_ok__shift 0x04 +#define ma_dcu_mon0__vdd_ok__reset 0x00 +//-------------------------------------------------------------dcu_mon0.mute--- +// dcu0 mute monitor +#define ma_dcu_mon0__mute__a 97 +#define ma_dcu_mon0__mute__len 1 +#define ma_dcu_mon0__mute__mask 0x20 +#define ma_dcu_mon0__mute__shift 0x05 +#define ma_dcu_mon0__mute__reset 0x00 +//------------------------------------------------------------dcu_mon0.m_mon--- +// m sense monitor channel 0 +#define ma_dcu_mon0__m_mon__a 98 +#define ma_dcu_mon0__m_mon__len 8 +#define ma_dcu_mon0__m_mon__mask 0xff +#define ma_dcu_mon0__m_mon__shift 0x00 +#define ma_dcu_mon0__m_mon__reset 0x00 +//-----------------------------------------------------------dcu_mon1.pm_mon--- +// power mode monitor channel 1 +#define ma_dcu_mon1__pm_mon__a 100 +#define ma_dcu_mon1__pm_mon__len 2 +#define ma_dcu_mon1__pm_mon__mask 0x03 +#define ma_dcu_mon1__pm_mon__shift 0x00 +#define ma_dcu_mon1__pm_mon__reset 0x00 +//-----------------------------------------------------dcu_mon1.freqmode_mon--- +// frequence mode monitor channel 1 +#define ma_dcu_mon1__freqmode_mon__a 100 +#define ma_dcu_mon1__freqmode_mon__len 3 +#define ma_dcu_mon1__freqmode_mon__mask 0x70 +#define ma_dcu_mon1__freqmode_mon__shift 0x04 +#define ma_dcu_mon1__freqmode_mon__reset 0x00 +//-------------------------------------------------------dcu_mon1.pps_passed--- +// dcu1 pps completion indicator +#define ma_dcu_mon1__pps_passed__a 100 +#define ma_dcu_mon1__pps_passed__len 1 +#define ma_dcu_mon1__pps_passed__mask 0x80 +#define ma_dcu_mon1__pps_passed__shift 0x07 +#define ma_dcu_mon1__pps_passed__reset 0x00 +//----------------------------------------------------------dcu_mon1.ocp_mon--- +// ocp monitor channel 1 +#define ma_dcu_mon1__ocp_mon__a 101 +#define ma_dcu_mon1__ocp_mon__len 1 +#define ma_dcu_mon1__ocp_mon__mask 0x01 +#define ma_dcu_mon1__ocp_mon__shift 0x00 +#define ma_dcu_mon1__ocp_mon__reset 0x00 +//--------------------------------------------------------dcu_mon1.vcfly1_ok--- +// cfly1 protcetion monitor channel 1 +#define ma_dcu_mon1__vcfly1_ok__a 101 +#define ma_dcu_mon1__vcfly1_ok__len 1 +#define ma_dcu_mon1__vcfly1_ok__mask 0x02 +#define ma_dcu_mon1__vcfly1_ok__shift 0x01 +#define ma_dcu_mon1__vcfly1_ok__reset 0x00 +//--------------------------------------------------------dcu_mon1.vcfly2_ok--- +// cfly2 protection monitor channel 1 +#define ma_dcu_mon1__vcfly2_ok__a 101 +#define ma_dcu_mon1__vcfly2_ok__len 1 +#define ma_dcu_mon1__vcfly2_ok__mask 0x04 +#define ma_dcu_mon1__vcfly2_ok__shift 0x02 +#define ma_dcu_mon1__vcfly2_ok__reset 0x00 +//----------------------------------------------------------dcu_mon1.pvdd_ok--- +// dcu1 pvdd monitor +#define ma_dcu_mon1__pvdd_ok__a 101 +#define ma_dcu_mon1__pvdd_ok__len 1 +#define ma_dcu_mon1__pvdd_ok__mask 0x08 +#define ma_dcu_mon1__pvdd_ok__shift 0x03 +#define ma_dcu_mon1__pvdd_ok__reset 0x00 +//-----------------------------------------------------------dcu_mon1.vdd_ok--- +// dcu1 vdd monitor +#define ma_dcu_mon1__vdd_ok__a 101 +#define ma_dcu_mon1__vdd_ok__len 1 +#define ma_dcu_mon1__vdd_ok__mask 0x10 +#define ma_dcu_mon1__vdd_ok__shift 0x04 +#define ma_dcu_mon1__vdd_ok__reset 0x00 +//-------------------------------------------------------------dcu_mon1.mute--- +// dcu1 mute monitor +#define ma_dcu_mon1__mute__a 101 +#define ma_dcu_mon1__mute__len 1 +#define ma_dcu_mon1__mute__mask 0x20 +#define ma_dcu_mon1__mute__shift 0x05 +#define ma_dcu_mon1__mute__reset 0x00 +//------------------------------------------------------------dcu_mon1.m_mon--- +// m sense monitor channel 1 +#define ma_dcu_mon1__m_mon__a 102 +#define ma_dcu_mon1__m_mon__len 8 +#define ma_dcu_mon1__m_mon__mask 0xff +#define ma_dcu_mon1__m_mon__shift 0x00 +#define ma_dcu_mon1__m_mon__reset 0x00 +//--------------------------------------------------------dcu_mon0.sw_enable--- +// dcu0 switch enable monitor +#define ma_dcu_mon0__sw_enable__a 104 +#define ma_dcu_mon0__sw_enable__len 1 +#define ma_dcu_mon0__sw_enable__mask 0x40 +#define ma_dcu_mon0__sw_enable__shift 0x06 +#define ma_dcu_mon0__sw_enable__reset 0x00 +//--------------------------------------------------------dcu_mon1.sw_enable--- +// dcu1 switch enable monitor +#define ma_dcu_mon1__sw_enable__a 104 +#define ma_dcu_mon1__sw_enable__len 1 +#define ma_dcu_mon1__sw_enable__mask 0x80 +#define ma_dcu_mon1__sw_enable__shift 0x07 +#define ma_dcu_mon1__sw_enable__reset 0x00 +//------------------------------------------------------------hvboot0_ok_mon--- +// hvboot0_ok for test/debug +#define ma_hvboot0_ok_mon__a 105 +#define ma_hvboot0_ok_mon__len 1 +#define ma_hvboot0_ok_mon__mask 0x40 +#define ma_hvboot0_ok_mon__shift 0x06 +#define ma_hvboot0_ok_mon__reset 0x00 +//------------------------------------------------------------hvboot1_ok_mon--- +// hvboot1_ok for test/debug +#define ma_hvboot1_ok_mon__a 105 +#define ma_hvboot1_ok_mon__len 1 +#define ma_hvboot1_ok_mon__mask 0x80 +#define ma_hvboot1_ok_mon__shift 0x07 +#define ma_hvboot1_ok_mon__reset 0x00 +//-----------------------------------------------------------------error_acc--- +// accumulated errors, at and after triggering +#define ma_error_acc__a 109 +#define ma_error_acc__len 8 +#define ma_error_acc__mask 0xff +#define ma_error_acc__shift 0x00 +#define ma_error_acc__reset 0x00 +//-------------------------------------------------------------i2s_data_rate--- +// detected i2s data rate: 00/01/10 = x1/x2/x4 +#define ma_i2s_data_rate__a 116 +#define ma_i2s_data_rate__len 2 +#define ma_i2s_data_rate__mask 0x03 +#define ma_i2s_data_rate__shift 0x00 +#define ma_i2s_data_rate__reset 0x00 +//---------------------------------------------------------audio_in_mode_mon--- +// audio input mode monitor +#define ma_audio_in_mode_mon__a 116 +#define ma_audio_in_mode_mon__len 3 +#define ma_audio_in_mode_mon__mask 0x1c +#define ma_audio_in_mode_mon__shift 0x02 +#define ma_audio_in_mode_mon__reset 0x00 +//------------------------------------------------------------------msel_mon--- +// msel[2:0] monitor register +#define ma_msel_mon__a 117 +#define ma_msel_mon__len 3 +#define ma_msel_mon__mask 0x07 +#define ma_msel_mon__shift 0x00 +#define ma_msel_mon__reset 0x00 +//---------------------------------------------------------------------error--- +// current error flag monitor reg - for app. ctrl. +#define ma_error__a 124 +#define ma_error__len 8 +#define ma_error__mask 0xff +#define ma_error__shift 0x00 +#define ma_error__reset 0x00 +//----------------------------------------------------audio_proc_limiter_mon--- +// b7-b4: channel 3-0 limiter active +#define ma_audio_proc_limiter_mon__a 126 +#define ma_audio_proc_limiter_mon__len 4 +#define ma_audio_proc_limiter_mon__mask 0xf0 +#define ma_audio_proc_limiter_mon__shift 0x04 +#define ma_audio_proc_limiter_mon__reset 0x00 +//-------------------------------------------------------audio_proc_clip_mon--- +// b3-b0: channel 3-0 clipping monitor +#define ma_audio_proc_clip_mon__a 126 +#define ma_audio_proc_clip_mon__len 4 +#define ma_audio_proc_clip_mon__mask 0x0f +#define ma_audio_proc_clip_mon__shift 0x00 +#define ma_audio_proc_clip_mon__reset 0x00 +#endif + +#define SOC_ENUM_ERR(xname, xenum)\ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\ + .access = SNDRV_CTL_ELEM_ACCESS_READ,\ + .info = snd_soc_info_enum_double,\ + .get = snd_soc_get_enum_double, .put = snd_soc_put_enum_double,\ + .private_value = (unsigned long)&(xenum) } + +static struct i2c_client *i2c; + +struct ma120x0p_priv { + struct regmap *regmap; + int mclk_div; + struct snd_soc_component *component; + struct gpio_desc *enable_gpio; + struct gpio_desc *mute_gpio; + struct gpio_desc *booster_gpio; + struct gpio_desc *error_gpio; +}; + +static struct ma120x0p_priv *priv_data; + +//Used to share the IRQ number within this file +static unsigned int irqNumber; + +// Function prototype for the custom IRQ handler function +static irqreturn_t ma120x0p_irq_handler(int irq, void *data); + +//Alsa Controls +static const char * const limenable_text[] = {"Bypassed", "Enabled"}; +static const char * const limatack_text[] = {"Slow", "Normal", "Fast"}; +static const char * const limrelease_text[] = {"Slow", "Normal", "Fast"}; + +static const char * const err_flycap_text[] = {"Ok", "Error"}; +static const char * const err_overcurr_text[] = {"Ok", "Error"}; +static const char * const err_pllerr_text[] = {"Ok", "Error"}; +static const char * const err_pvddunder_text[] = {"Ok", "Error"}; +static const char * const err_overtempw_text[] = {"Ok", "Error"}; +static const char * const err_overtempe_text[] = {"Ok", "Error"}; +static const char * const err_pinlowimp_text[] = {"Ok", "Error"}; +static const char * const err_dcprot_text[] = {"Ok", "Error"}; + +static const char * const pwr_mode_prof_text[] = {"PMF0", "PMF1", "PMF2", +"PMF3", "PMF4"}; + +static const struct soc_enum lim_enable_ctrl = + SOC_ENUM_SINGLE(ma_audio_proc_limiterenable__a, + ma_audio_proc_limiterenable__shift, + ma_audio_proc_limiterenable__len + 1, + limenable_text); +static const struct soc_enum limatack_ctrl = + SOC_ENUM_SINGLE(ma_audio_proc_attack__a, + ma_audio_proc_attack__shift, + ma_audio_proc_attack__len + 1, + limatack_text); +static const struct soc_enum limrelease_ctrl = + SOC_ENUM_SINGLE(ma_audio_proc_release__a, + ma_audio_proc_release__shift, + ma_audio_proc_release__len + 1, + limrelease_text); +static const struct soc_enum err_flycap_ctrl = + SOC_ENUM_SINGLE(ma_error__a, 0, 3, err_flycap_text); +static const struct soc_enum err_overcurr_ctrl = + SOC_ENUM_SINGLE(ma_error__a, 1, 3, err_overcurr_text); +static const struct soc_enum err_pllerr_ctrl = + SOC_ENUM_SINGLE(ma_error__a, 2, 3, err_pllerr_text); +static const struct soc_enum err_pvddunder_ctrl = + SOC_ENUM_SINGLE(ma_error__a, 3, 3, err_pvddunder_text); +static const struct soc_enum err_overtempw_ctrl = + SOC_ENUM_SINGLE(ma_error__a, 4, 3, err_overtempw_text); +static const struct soc_enum err_overtempe_ctrl = + SOC_ENUM_SINGLE(ma_error__a, 5, 3, err_overtempe_text); +static const struct soc_enum err_pinlowimp_ctrl = + SOC_ENUM_SINGLE(ma_error__a, 6, 3, err_pinlowimp_text); +static const struct soc_enum err_dcprot_ctrl = + SOC_ENUM_SINGLE(ma_error__a, 7, 3, err_dcprot_text); +static const struct soc_enum pwr_mode_prof_ctrl = + SOC_ENUM_SINGLE(ma_pmprofile__a, ma_pmprofile__shift, 5, + pwr_mode_prof_text); + +static const char * const pwr_mode_texts[] = { + "Dynamic power mode", + "Power mode 1", + "Power mode 2", + "Power mode 3", + }; + +static const int pwr_mode_values[] = { + 0x10, + 0x50, + 0x60, + 0x70, + }; + +static SOC_VALUE_ENUM_SINGLE_DECL(pwr_mode_ctrl, + ma_pm_man__a, 0, 0x70, + pwr_mode_texts, + pwr_mode_values); + +static const DECLARE_TLV_DB_SCALE(ma120x0p_vol_tlv, -14400, 100, 0); +static const DECLARE_TLV_DB_SCALE(ma120x0p_lim_tlv, -5000, 100, 0); +static const DECLARE_TLV_DB_SCALE(ma120x0p_lr_tlv, -5000, 100, 0); + +static const struct snd_kcontrol_new ma120x0p_snd_controls[] = { + //Master Volume + SOC_SINGLE_RANGE_TLV("A.Mstr Vol Volume", + ma_vol_db_master__a, 0, 0x18, 0xa8, 1, ma120x0p_vol_tlv), + + //L-R Volume ch0 + SOC_SINGLE_RANGE_TLV("B.L Vol Volume", + ma_vol_db_ch0__a, 0, 0x18, 0x4a, 1, ma120x0p_lr_tlv), + SOC_SINGLE_RANGE_TLV("C.R Vol Volume", + ma_vol_db_ch1__a, 0, 0x18, 0x4a, 1, ma120x0p_lr_tlv), + + //L-R Limiter Threshold ch0-ch1 + SOC_DOUBLE_R_RANGE_TLV("D.Lim thresh Volume", + ma_thr_db_ch0__a, ma_thr_db_ch1__a, 0, 0x0e, 0x4a, 1, + ma120x0p_lim_tlv), + + //Enum Switches/Selectors + //SOC_ENUM("E.AudioProc Mute", audioproc_mute_ctrl), + SOC_ENUM("F.Limiter Enable", lim_enable_ctrl), + SOC_ENUM("G.Limiter Attck", limatack_ctrl), + SOC_ENUM("H.Limiter Rls", limrelease_ctrl), + + //Enum Error Monitor (read-only) + SOC_ENUM_ERR("I.Err flycap", err_flycap_ctrl), + SOC_ENUM_ERR("J.Err overcurr", err_overcurr_ctrl), + SOC_ENUM_ERR("K.Err pllerr", err_pllerr_ctrl), + SOC_ENUM_ERR("L.Err pvddunder", err_pvddunder_ctrl), + SOC_ENUM_ERR("M.Err overtempw", err_overtempw_ctrl), + SOC_ENUM_ERR("N.Err overtempe", err_overtempe_ctrl), + SOC_ENUM_ERR("O.Err pinlowimp", err_pinlowimp_ctrl), + SOC_ENUM_ERR("P.Err dcprot", err_dcprot_ctrl), + + //Power modes profiles + SOC_ENUM("Q.PM Prof", pwr_mode_prof_ctrl), + + // Power mode selection (Dynamic,1,2,3) + SOC_ENUM("R.Power Mode", pwr_mode_ctrl), +}; + +//Machine Driver +static int ma120x0p_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + u16 blen = 0x00; + + struct snd_soc_component *component = dai->component; + + priv_data->component = component; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + blen = 0x10; + break; + case SNDRV_PCM_FORMAT_S24_LE: + blen = 0x00; + break; + case SNDRV_PCM_FORMAT_S32_LE: + blen = 0x00; + break; + default: + dev_err(dai->dev, "Unsupported word length: %u\n", + params_format(params)); + return -EINVAL; + } + + // set word length + snd_soc_component_update_bits(component, ma_i2s_framesize__a, + ma_i2s_framesize__mask, blen); + + return 0; +} + +static int ma120x0p_mute_stream(struct snd_soc_dai *dai, int mute, int stream) +{ + int val = 0; + + struct ma120x0p_priv *ma120x0p; + + struct snd_soc_component *component = dai->component; + + ma120x0p = snd_soc_component_get_drvdata(component); + + if (mute) + val = 0; + else + val = 1; + + gpiod_set_value_cansleep(priv_data->mute_gpio, val); + + return 0; +} + +static const struct snd_soc_dai_ops ma120x0p_dai_ops = { + .hw_params = ma120x0p_hw_params, + .mute_stream = ma120x0p_mute_stream, +}; + +static struct snd_soc_dai_driver ma120x0p_dai = { + .name = "ma120x0p-amp", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 44100, + .rate_max = 192000, + .formats = SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE + }, + .ops = &ma120x0p_dai_ops, +}; + +//Codec Driver +static int ma120x0p_clear_err(struct snd_soc_component *component) +{ + int ret = 0; + + struct ma120x0p_priv *ma120x0p; + + ma120x0p = snd_soc_component_get_drvdata(component); + + ret = snd_soc_component_update_bits(component, + ma_eh_clear__a, ma_eh_clear__mask, 0x00); + if (ret < 0) + return ret; + + ret = snd_soc_component_update_bits(component, + ma_eh_clear__a, ma_eh_clear__mask, 0x04); + if (ret < 0) + return ret; + + ret = snd_soc_component_update_bits(component, + ma_eh_clear__a, ma_eh_clear__mask, 0x00); + if (ret < 0) + return ret; + + return 0; +} + +static void ma120x0p_remove(struct snd_soc_component *component) +{ + struct ma120x0p_priv *ma120x0p; + + ma120x0p = snd_soc_component_get_drvdata(component); +} + +static int ma120x0p_probe(struct snd_soc_component *component) +{ + struct ma120x0p_priv *ma120x0p; + + int ret = 0; + + i2c = container_of(component->dev, struct i2c_client, dev); + + ma120x0p = snd_soc_component_get_drvdata(component); + + //Reset error + ma120x0p_clear_err(component); + if (ret < 0) + return ret; + + // set serial audio format I2S and enable audio processor + ret = snd_soc_component_write(component, ma_i2s_format__a, 0x08); + if (ret < 0) + return ret; + + // Enable audio limiter + ret = snd_soc_component_update_bits(component, + ma_audio_proc_limiterenable__a, + ma_audio_proc_limiterenable__mask, 0x40); + if (ret < 0) + return ret; + + // Set lim attack to fast + ret = snd_soc_component_update_bits(component, + ma_audio_proc_attack__a, ma_audio_proc_attack__mask, 0x80); + if (ret < 0) + return ret; + + // Set lim attack to low + ret = snd_soc_component_update_bits(component, + ma_audio_proc_release__a, ma_audio_proc_release__mask, 0x00); + if (ret < 0) + return ret; + + // set volume to 0dB + ret = snd_soc_component_write(component, ma_vol_db_master__a, 0x18); + if (ret < 0) + return ret; + + // set ch0 lim thresh to -15dB + ret = snd_soc_component_write(component, ma_thr_db_ch0__a, 0x27); + if (ret < 0) + return ret; + + // set ch1 lim thresh to -15dB + ret = snd_soc_component_write(component, ma_thr_db_ch1__a, 0x27); + if (ret < 0) + return ret; + + //Check for errors + ret = snd_soc_component_test_bits(component, ma_error_acc__a, 0x00, 0); + if (ret < 0) + return ret; + ret = snd_soc_component_test_bits(component, ma_error_acc__a, 0x01, 0); + if (ret < 0) + return ret; + ret = snd_soc_component_test_bits(component, ma_error_acc__a, 0x02, 0); + if (ret < 0) + return ret; + ret = snd_soc_component_test_bits(component, ma_error_acc__a, 0x08, 0); + if (ret < 0) + return ret; + ret = snd_soc_component_test_bits(component, ma_error_acc__a, 0x10, 0); + if (ret < 0) + return ret; + ret = snd_soc_component_test_bits(component, ma_error_acc__a, 0x20, 0); + if (ret < 0) + return ret; + ret = snd_soc_component_test_bits(component, ma_error_acc__a, 0x40, 0); + if (ret < 0) + return ret; + ret = snd_soc_component_test_bits(component, ma_error_acc__a, 0x80, 0); + if (ret < 0) + return ret; + + return 0; +} + +static int ma120x0p_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + int ret = 0; + + struct ma120x0p_priv *ma120x0p; + + ma120x0p = snd_soc_component_get_drvdata(component); + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + break; + + case SND_SOC_BIAS_STANDBY: + ret = gpiod_get_value_cansleep(priv_data->enable_gpio); + if (ret != 0) { + dev_err(component->dev, "Device ma120x0p disabled in STANDBY BIAS: %d\n", + ret); + return ret; + } + break; + + case SND_SOC_BIAS_OFF: + break; + } + + return 0; +} + +static const struct snd_soc_dapm_widget ma120x0p_dapm_widgets[] = { + SND_SOC_DAPM_OUTPUT("OUT_A"), + SND_SOC_DAPM_OUTPUT("OUT_B"), +}; + +static const struct snd_soc_dapm_route ma120x0p_dapm_routes[] = { + { "OUT_B", NULL, "Playback" }, + { "OUT_A", NULL, "Playback" }, +}; + +static const struct snd_soc_component_driver ma120x0p_component_driver = { + .probe = ma120x0p_probe, + .remove = ma120x0p_remove, + .set_bias_level = ma120x0p_set_bias_level, + .dapm_widgets = ma120x0p_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(ma120x0p_dapm_widgets), + .dapm_routes = ma120x0p_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(ma120x0p_dapm_routes), + .controls = ma120x0p_snd_controls, + .num_controls = ARRAY_SIZE(ma120x0p_snd_controls), + .use_pmdown_time = 1, + .endianness = 1, +}; + +//I2C Driver +static const struct reg_default ma120x0p_reg_defaults[] = { + { 0x01, 0x3c }, +}; + +static bool ma120x0p_reg_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ma_error__a: + return true; + default: + return false; + } +} + +static const struct of_device_id ma120x0p_of_match[] = { + { .compatible = "ma,ma120x0p", }, + { } +}; + +MODULE_DEVICE_TABLE(of, ma120x0p_of_match); + +static struct regmap_config ma120x0p_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = 255, + .volatile_reg = ma120x0p_reg_volatile, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = ma120x0p_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(ma120x0p_reg_defaults), +}; + +static int ma120x0p_i2c_probe(struct i2c_client *i2c) +{ + int ret; + + priv_data = devm_kzalloc(&i2c->dev, sizeof(*priv_data), GFP_KERNEL); + if (!priv_data) + return -ENOMEM; + i2c_set_clientdata(i2c, priv_data); + + priv_data->regmap = devm_regmap_init_i2c(i2c, &ma120x0p_regmap_config); + if (IS_ERR(priv_data->regmap)) { + ret = PTR_ERR(priv_data->regmap); + return ret; + } + + //Startup sequence + + //Make sure the device is muted + priv_data->mute_gpio = devm_gpiod_get_optional(&i2c->dev, "mute_gp", + GPIOD_OUT_LOW); + if (IS_ERR(priv_data->mute_gpio)) { + ret = PTR_ERR(priv_data->mute_gpio); + dev_err(&i2c->dev, "Failed to get mute gpio line: %d\n", ret); + return ret; + } + msleep(50); + +// MA120xx0P devices are usually powered by an integrated boost converter. +// An option GPIO control line is provided to enable the booster properly and +// in sync with the enable and mute GPIO lines. + priv_data->booster_gpio = devm_gpiod_get_optional(&i2c->dev, + "booster_gp", GPIOD_OUT_LOW); + if (IS_ERR(priv_data->booster_gpio)) { + ret = PTR_ERR(priv_data->booster_gpio); + dev_err(&i2c->dev, + "Failed to get booster enable gpio line: %d\n", ret); + return ret; + } + msleep(50); + + //Enable booster and wait 200ms until stable PVDD + gpiod_set_value_cansleep(priv_data->booster_gpio, 1); + msleep(200); + + //Enable ma120x0pp + priv_data->enable_gpio = devm_gpiod_get_optional(&i2c->dev, + "enable_gp", GPIOD_OUT_LOW); + if (IS_ERR(priv_data->enable_gpio)) { + ret = PTR_ERR(priv_data->enable_gpio); + dev_err(&i2c->dev, + "Failed to get ma120x0p enable gpio line: %d\n", ret); + return ret; + } + msleep(50); + + //Optional use of ma120x0pp error line as an interrupt trigger to + //platform GPIO. + //Get error input gpio ma120x0p + priv_data->error_gpio = devm_gpiod_get_optional(&i2c->dev, + "error_gp", GPIOD_IN); + if (IS_ERR(priv_data->error_gpio)) { + ret = PTR_ERR(priv_data->error_gpio); + dev_err(&i2c->dev, + "Failed to get ma120x0p error gpio line: %d\n", ret); + return ret; + } + + if (priv_data->error_gpio != NULL) { + irqNumber = gpiod_to_irq(priv_data->error_gpio); + + ret = devm_request_threaded_irq(&i2c->dev, + irqNumber, ma120x0p_irq_handler, + NULL, IRQF_TRIGGER_FALLING, + "ma120x0p", priv_data); + if (ret != 0) + dev_warn(&i2c->dev, "Failed to request IRQ: %d\n", + ret); + } + + ret = devm_snd_soc_register_component(&i2c->dev, + &ma120x0p_component_driver, &ma120x0p_dai, 1); + + return ret; +} + +static irqreturn_t ma120x0p_irq_handler(int irq, void *data) +{ + gpiod_set_value_cansleep(priv_data->mute_gpio, 0); + gpiod_set_value_cansleep(priv_data->enable_gpio, 1); + return IRQ_HANDLED; +} + +static void ma120x0p_i2c_remove(struct i2c_client *i2c) +{ + snd_soc_unregister_component(&i2c->dev); + i2c_set_clientdata(i2c, NULL); + + gpiod_set_value_cansleep(priv_data->mute_gpio, 0); + msleep(30); + gpiod_set_value_cansleep(priv_data->enable_gpio, 1); + msleep(200); + gpiod_set_value_cansleep(priv_data->booster_gpio, 0); + msleep(200); + + kfree(priv_data); +} + +static void ma120x0p_i2c_shutdown(struct i2c_client *i2c) +{ + snd_soc_unregister_component(&i2c->dev); + i2c_set_clientdata(i2c, NULL); + + gpiod_set_value_cansleep(priv_data->mute_gpio, 0); + msleep(30); + gpiod_set_value_cansleep(priv_data->enable_gpio, 1); + msleep(200); + gpiod_set_value_cansleep(priv_data->booster_gpio, 0); + msleep(200); + + kfree(priv_data); +} + +static const struct i2c_device_id ma120x0p_i2c_id[] = { + { "ma120x0p", 0 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, ma120x0p_i2c_id); + +static struct i2c_driver ma120x0p_i2c_driver = { + .driver = { + .name = "ma120x0p", + .owner = THIS_MODULE, + .of_match_table = ma120x0p_of_match, + }, + .probe = ma120x0p_i2c_probe, + .remove = ma120x0p_i2c_remove, + .shutdown = ma120x0p_i2c_shutdown, + .id_table = ma120x0p_i2c_id +}; + +static int __init ma120x0p_modinit(void) +{ + int ret = 0; + + ret = i2c_add_driver(&ma120x0p_i2c_driver); + if (ret != 0) { + pr_err("Failed to register MA120X0P I2C driver: %d\n", ret); + return ret; + } + return ret; +} +module_init(ma120x0p_modinit); + +static void __exit ma120x0p_exit(void) +{ + i2c_del_driver(&ma120x0p_i2c_driver); +} +module_exit(ma120x0p_exit); + +MODULE_AUTHOR("Ariel Muszkat ariel.muszkat@gmail.com>"); +MODULE_DESCRIPTION("ASoC driver for ma120x0p"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/pcm1794a.c b/sound/soc/codecs/pcm1794a.c new file mode 100644 index 000000000000..3877a138f896 --- /dev/null +++ b/sound/soc/codecs/pcm1794a.c @@ -0,0 +1,68 @@ +/* + * Driver for the PCM1794A codec + * + * Author: Florian Meier + * Copyright 2013 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + + +#include +#include +#include + +#include + +static struct snd_soc_dai_driver pcm1794a_dai = { + .name = "pcm1794a-hifi", + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE + }, +}; + +static struct snd_soc_component_driver soc_component_dev_pcm1794a; + +static int pcm1794a_probe(struct platform_device *pdev) +{ + return snd_soc_register_component(&pdev->dev, &soc_component_dev_pcm1794a, + &pcm1794a_dai, 1); +} + +static void pcm1794a_remove(struct platform_device *pdev) +{ + snd_soc_unregister_component(&pdev->dev); +} + +static const struct of_device_id pcm1794a_of_match[] = { + { .compatible = "ti,pcm1794a", }, + { } +}; +MODULE_DEVICE_TABLE(of, pcm1794a_of_match); + +static struct platform_driver pcm1794a_component_driver = { + .probe = pcm1794a_probe, + .remove = pcm1794a_remove, + .driver = { + .name = "pcm1794a-codec", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(pcm1794a_of_match), + }, +}; + +module_platform_driver(pcm1794a_component_driver); + +MODULE_DESCRIPTION("ASoC PCM1794A codec driver"); +MODULE_AUTHOR("Florian Meier "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/pcm3168a.c b/sound/soc/codecs/pcm3168a.c index c8617a488b11..0363869aed63 100644 --- a/sound/soc/codecs/pcm3168a.c +++ b/sound/soc/codecs/pcm3168a.c @@ -60,6 +60,7 @@ struct pcm3168a_priv { struct clk *scki; struct gpio_desc *gpio_rst; unsigned long sysclk; + bool adc_fc, dac_fc; // Force clock consumer mode struct pcm3168a_io_params io_params[2]; struct snd_soc_dai_driver dai_drv[2]; @@ -478,6 +479,12 @@ static int pcm3168a_hw_params(struct snd_pcm_substream *substream, ms = 0; } + // Force clock consumer mode if needed + if (pcm3168a->adc_fc && dai->id == PCM3168A_DAI_ADC) + ms = 0; + if (pcm3168a->dac_fc && dai->id == PCM3168A_DAI_DAC) + ms = 0; + format = io_params->format; if (io_params->slot_width) @@ -759,6 +766,11 @@ int pcm3168a_probe(struct device *dev, struct regmap *regmap) if (!pcm3168a->sysclk) pcm3168a->sysclk = 24576000; + pcm3168a->adc_fc = of_property_read_bool(dev->of_node, + "adc-force-cons"); + pcm3168a->dac_fc = of_property_read_bool(dev->of_node, + "dac-force-cons"); + for (i = 0; i < ARRAY_SIZE(pcm3168a->supplies); i++) pcm3168a->supplies[i].supply = pcm3168a_supply_names[i]; diff --git a/sound/soc/codecs/pcm512x.c b/sound/soc/codecs/pcm512x.c index 007dfc0fa224..34649db9c48f 100644 --- a/sound/soc/codecs/pcm512x.c +++ b/sound/soc/codecs/pcm512x.c @@ -537,7 +537,7 @@ static unsigned long pcm512x_ncp_target(struct pcm512x_priv *pcm512x, static const u32 pcm512x_dai_rates[] = { 8000, 11025, 16000, 22050, 32000, 44100, 48000, 64000, - 88200, 96000, 176400, 192000, 384000, + 88200, 96000, 176400, 192000, 352800, 384000, }; static const struct snd_pcm_hw_constraint_list constraints_slave = { @@ -630,7 +630,7 @@ static int pcm512x_dai_startup_slave(struct snd_pcm_substream *substream, struct regmap *regmap = pcm512x->regmap; if (IS_ERR(pcm512x->sclk)) { - dev_info(dev, "No SCLK, using BCLK: %ld\n", + dev_dbg(dev, "No SCLK, using BCLK: %ld\n", PTR_ERR(pcm512x->sclk)); /* Disable reporting of missing SCLK as an error */ diff --git a/sound/soc/codecs/tas5713.c b/sound/soc/codecs/tas5713.c new file mode 100644 index 000000000000..01dd21a1d7a2 --- /dev/null +++ b/sound/soc/codecs/tas5713.c @@ -0,0 +1,360 @@ +/* + * ASoC Driver for TAS5713 + * + * Author: Sebastian Eickhoff + * Copyright 2014 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "tas5713.h" + + +static struct i2c_client *i2c; + +struct tas5713_priv { + struct regmap *regmap; + int mclk_div; + struct snd_soc_component *component; +}; + +static struct tas5713_priv *priv_data; + + + + +/* + * _ _ ___ _ ___ _ _ + * /_\ | | / __| /_\ / __|___ _ _| |_ _ _ ___| |___ + * / _ \| |__\__ \/ _ \ | (__/ _ \ ' \ _| '_/ _ \ (_-< + * /_/ \_\____|___/_/ \_\ \___\___/_||_\__|_| \___/_/__/ + * + */ + +static const DECLARE_TLV_DB_SCALE(tas5713_vol_tlv, -10000, 50, 1); + + +static const struct snd_kcontrol_new tas5713_snd_controls[] = { + SOC_SINGLE_TLV ("Master" , TAS5713_VOL_MASTER, 0, 248, 1, tas5713_vol_tlv), + SOC_DOUBLE_R_TLV("Channels" , TAS5713_VOL_CH1, TAS5713_VOL_CH2, 0, 248, 1, tas5713_vol_tlv) +}; + + + + +/* + * __ __ _ _ ___ _ + * | \/ |__ _ __| |_ (_)_ _ ___ | \ _ _(_)_ _____ _ _ + * | |\/| / _` / _| ' \| | ' \/ -_) | |) | '_| \ V / -_) '_| + * |_| |_\__,_\__|_||_|_|_||_\___| |___/|_| |_|\_/\___|_| + * + */ + +static int tas5713_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + u16 blen = 0x00; + + struct snd_soc_component *component = dai->component; + priv_data->component = component; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + blen = 0x03; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + blen = 0x1; + break; + case SNDRV_PCM_FORMAT_S24_LE: + blen = 0x04; + break; + case SNDRV_PCM_FORMAT_S32_LE: + blen = 0x05; + break; + default: + dev_err(dai->dev, "Unsupported word length: %u\n", + params_format(params)); + return -EINVAL; + } + + // set word length + snd_soc_component_update_bits(component, TAS5713_SERIAL_DATA_INTERFACE, 0x7, blen); + + return 0; +} + + +static int tas5713_mute_stream(struct snd_soc_dai *dai, int mute, int stream) +{ + unsigned int val = 0; + + struct tas5713_priv *tas5713; + struct snd_soc_component *component = dai->component; + tas5713 = snd_soc_component_get_drvdata(component); + + if (mute) { + val = TAS5713_SOFT_MUTE_ALL; + } + + return regmap_write(tas5713->regmap, TAS5713_SOFT_MUTE, val); +} + + +static const struct snd_soc_dai_ops tas5713_dai_ops = { + .hw_params = tas5713_hw_params, + .mute_stream = tas5713_mute_stream, +}; + + +static struct snd_soc_dai_driver tas5713_dai = { + .name = "tas5713-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE ), + }, + .ops = &tas5713_dai_ops, +}; + + + + +/* + * ___ _ ___ _ + * / __|___ __| |___ __ | \ _ _(_)_ _____ _ _ + * | (__/ _ \/ _` / -_) _| | |) | '_| \ V / -_) '_| + * \___\___/\__,_\___\__| |___/|_| |_|\_/\___|_| + * + */ + +static void tas5713_remove(struct snd_soc_component *component) +{ + struct tas5713_priv *tas5713; + + tas5713 = snd_soc_component_get_drvdata(component); +} + + +static int tas5713_probe(struct snd_soc_component *component) +{ + struct tas5713_priv *tas5713; + int i, ret; + + i2c = container_of(component->dev, struct i2c_client, dev); + + tas5713 = snd_soc_component_get_drvdata(component); + + // Reset error + ret = snd_soc_component_write(component, TAS5713_ERROR_STATUS, 0x00); + if (ret < 0) return ret; + + // Trim oscillator + ret = snd_soc_component_write(component, TAS5713_OSC_TRIM, 0x00); + if (ret < 0) return ret; + msleep(1000); + + // Reset error + ret = snd_soc_component_write(component, TAS5713_ERROR_STATUS, 0x00); + if (ret < 0) return ret; + + // I2S 24bit + ret = snd_soc_component_write(component, TAS5713_SERIAL_DATA_INTERFACE, 0x05); + if (ret < 0) return ret; + + // Unmute + ret = snd_soc_component_write(component, TAS5713_SYSTEM_CTRL2, 0x00); + if (ret < 0) return ret; + ret = snd_soc_component_write(component, TAS5713_SOFT_MUTE, 0x00); + if (ret < 0) return ret; + + // Set volume to 0db + ret = snd_soc_component_write(component, TAS5713_VOL_MASTER, 0x00); + if (ret < 0) return ret; + + // Now start programming the default initialization sequence + for (i = 0; i < ARRAY_SIZE(tas5713_init_sequence); ++i) { + ret = i2c_master_send(i2c, + tas5713_init_sequence[i].data, + tas5713_init_sequence[i].size); + if (ret < 0) { + printk(KERN_INFO "TAS5713 CODEC PROBE: InitSeq returns: %d\n", ret); + } + } + + // Unmute + ret = snd_soc_component_write(component, TAS5713_SYSTEM_CTRL2, 0x00); + if (ret < 0) return ret; + + return 0; +} + + +static struct snd_soc_component_driver soc_codec_dev_tas5713 = { + .probe = tas5713_probe, + .remove = tas5713_remove, + .controls = tas5713_snd_controls, + .num_controls = ARRAY_SIZE(tas5713_snd_controls), +}; + + + + +/* + * ___ ___ ___ ___ _ + * |_ _|_ ) __| | \ _ _(_)_ _____ _ _ + * | | / / (__ | |) | '_| \ V / -_) '_| + * |___/___\___| |___/|_| |_|\_/\___|_| + * + */ + +static const struct reg_default tas5713_reg_defaults[] = { + { 0x07 ,0x80 }, // R7 - VOL_MASTER - -40dB + { 0x08 , 30 }, // R8 - VOL_CH1 - 0dB + { 0x09 , 30 }, // R9 - VOL_CH2 - 0dB + { 0x0A ,0x80 }, // R10 - VOL_HEADPHONE - -40dB +}; + + +static bool tas5713_reg_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TAS5713_DEVICE_ID: + case TAS5713_ERROR_STATUS: + case TAS5713_CLOCK_CTRL: + return true; + default: + return false; + } +} + + +static const struct of_device_id tas5713_of_match[] = { + { .compatible = "ti,tas5713", }, + { } +}; +MODULE_DEVICE_TABLE(of, tas5713_of_match); + + +static struct regmap_config tas5713_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = TAS5713_MAX_REGISTER, + .volatile_reg = tas5713_reg_volatile, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = tas5713_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(tas5713_reg_defaults), +}; + + +static int tas5713_i2c_probe(struct i2c_client *i2c) +{ + int ret; + + priv_data = devm_kzalloc(&i2c->dev, sizeof *priv_data, GFP_KERNEL); + if (!priv_data) + return -ENOMEM; + + priv_data->regmap = devm_regmap_init_i2c(i2c, &tas5713_regmap_config); + if (IS_ERR(priv_data->regmap)) { + ret = PTR_ERR(priv_data->regmap); + return ret; + } + + i2c_set_clientdata(i2c, priv_data); + + ret = snd_soc_register_component(&i2c->dev, + &soc_codec_dev_tas5713, &tas5713_dai, 1); + + return ret; +} + + +static void tas5713_i2c_remove(struct i2c_client *i2c) +{ + snd_soc_unregister_component(&i2c->dev); + i2c_set_clientdata(i2c, NULL); + + kfree(priv_data); +} + + +static const struct i2c_device_id tas5713_i2c_id[] = { + { "tas5713", 0 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, tas5713_i2c_id); + + +static struct i2c_driver tas5713_i2c_driver = { + .driver = { + .name = "tas5713", + .owner = THIS_MODULE, + .of_match_table = tas5713_of_match, + }, + .probe = tas5713_i2c_probe, + .remove = tas5713_i2c_remove, + .id_table = tas5713_i2c_id +}; + + +static int __init tas5713_modinit(void) +{ + int ret = 0; + + ret = i2c_add_driver(&tas5713_i2c_driver); + if (ret) { + printk(KERN_ERR "Failed to register tas5713 I2C driver: %d\n", + ret); + } + + return ret; +} +module_init(tas5713_modinit); + + +static void __exit tas5713_exit(void) +{ + i2c_del_driver(&tas5713_i2c_driver); +} +module_exit(tas5713_exit); + + +MODULE_AUTHOR("Sebastian Eickhoff "); +MODULE_DESCRIPTION("ASoC driver for TAS5713"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/tas5713.h b/sound/soc/codecs/tas5713.h new file mode 100644 index 000000000000..8f019e048987 --- /dev/null +++ b/sound/soc/codecs/tas5713.h @@ -0,0 +1,210 @@ +/* + * ASoC Driver for TAS5713 + * + * Author: Sebastian Eickhoff + * Copyright 2014 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#ifndef _TAS5713_H +#define _TAS5713_H + + +// TAS5713 I2C-bus register addresses + +#define TAS5713_CLOCK_CTRL 0x00 +#define TAS5713_DEVICE_ID 0x01 +#define TAS5713_ERROR_STATUS 0x02 +#define TAS5713_SYSTEM_CTRL1 0x03 +#define TAS5713_SERIAL_DATA_INTERFACE 0x04 +#define TAS5713_SYSTEM_CTRL2 0x05 +#define TAS5713_SOFT_MUTE 0x06 +#define TAS5713_VOL_MASTER 0x07 +#define TAS5713_VOL_CH1 0x08 +#define TAS5713_VOL_CH2 0x09 +#define TAS5713_VOL_HEADPHONE 0x0A +#define TAS5713_VOL_CONFIG 0x0E +#define TAS5713_MODULATION_LIMIT 0x10 +#define TAS5713_IC_DLY_CH1 0x11 +#define TAS5713_IC_DLY_CH2 0x12 +#define TAS5713_IC_DLY_CH3 0x13 +#define TAS5713_IC_DLY_CH4 0x14 + +#define TAS5713_START_STOP_PERIOD 0x1A +#define TAS5713_OSC_TRIM 0x1B +#define TAS5713_BKND_ERR 0x1C + +#define TAS5713_INPUT_MUX 0x20 +#define TAS5713_SRC_SELECT_CH4 0x21 +#define TAS5713_PWM_MUX 0x25 + +#define TAS5713_CH1_BQ0 0x29 +#define TAS5713_CH1_BQ1 0x2A +#define TAS5713_CH1_BQ2 0x2B +#define TAS5713_CH1_BQ3 0x2C +#define TAS5713_CH1_BQ4 0x2D +#define TAS5713_CH1_BQ5 0x2E +#define TAS5713_CH1_BQ6 0x2F +#define TAS5713_CH1_BQ7 0x58 +#define TAS5713_CH1_BQ8 0x59 + +#define TAS5713_CH2_BQ0 0x30 +#define TAS5713_CH2_BQ1 0x31 +#define TAS5713_CH2_BQ2 0x32 +#define TAS5713_CH2_BQ3 0x33 +#define TAS5713_CH2_BQ4 0x34 +#define TAS5713_CH2_BQ5 0x35 +#define TAS5713_CH2_BQ6 0x36 +#define TAS5713_CH2_BQ7 0x5C +#define TAS5713_CH2_BQ8 0x5D + +#define TAS5713_CH4_BQ0 0x5A +#define TAS5713_CH4_BQ1 0x5B +#define TAS5713_CH3_BQ0 0x5E +#define TAS5713_CH3_BQ1 0x5F + +#define TAS5713_DRC1_SOFTENING_FILTER_ALPHA_OMEGA 0x3B +#define TAS5713_DRC1_ATTACK_RELEASE_RATE 0x3C +#define TAS5713_DRC2_SOFTENING_FILTER_ALPHA_OMEGA 0x3E +#define TAS5713_DRC2_ATTACK_RELEASE_RATE 0x3F +#define TAS5713_DRC1_ATTACK_RELEASE_THRES 0x40 +#define TAS5713_DRC2_ATTACK_RELEASE_THRES 0x43 +#define TAS5713_DRC_CTRL 0x46 + +#define TAS5713_BANK_SW_CTRL 0x50 +#define TAS5713_CH1_OUTPUT_MIXER 0x51 +#define TAS5713_CH2_OUTPUT_MIXER 0x52 +#define TAS5713_CH1_INPUT_MIXER 0x53 +#define TAS5713_CH2_INPUT_MIXER 0x54 +#define TAS5713_OUTPUT_POST_SCALE 0x56 +#define TAS5713_OUTPUT_PRESCALE 0x57 + +#define TAS5713_IDF_POST_SCALE 0x62 + +#define TAS5713_CH1_INLINE_MIXER 0x70 +#define TAS5713_CH1_INLINE_DRC_EN_MIXER 0x71 +#define TAS5713_CH1_R_CHANNEL_MIXER 0x72 +#define TAS5713_CH1_L_CHANNEL_MIXER 0x73 +#define TAS5713_CH2_INLINE_MIXER 0x74 +#define TAS5713_CH2_INLINE_DRC_EN_MIXER 0x75 +#define TAS5713_CH2_L_CHANNEL_MIXER 0x76 +#define TAS5713_CH2_R_CHANNEL_MIXER 0x77 + +#define TAS5713_UPDATE_DEV_ADDR_KEY 0xF8 +#define TAS5713_UPDATE_DEV_ADDR_REG 0xF9 + +#define TAS5713_REGISTER_COUNT 0x46 +#define TAS5713_MAX_REGISTER 0xF9 + + +// Bitmasks for registers +#define TAS5713_SOFT_MUTE_ALL 0x07 + + + +struct tas5713_init_command { + const int size; + const char *const data; +}; + +static const struct tas5713_init_command tas5713_init_sequence[] = { + + // Trim oscillator + { .size = 2, .data = "\x1B\x00" }, + // System control register 1 (0x03): block DC + { .size = 2, .data = "\x03\x80" }, + // Mute everything + { .size = 2, .data = "\x05\x40" }, + // Modulation limit register (0x10): 97.7% + { .size = 2, .data = "\x10\x02" }, + // Interchannel delay registers + // (0x11, 0x12, 0x13, and 0x14): BD mode + { .size = 2, .data = "\x11\xB8" }, + { .size = 2, .data = "\x12\x60" }, + { .size = 2, .data = "\x13\xA0" }, + { .size = 2, .data = "\x14\x48" }, + // PWM shutdown group register (0x19): no shutdown + { .size = 2, .data = "\x19\x00" }, + // Input multiplexer register (0x20): BD mode + { .size = 2, .data = "\x20\x00\x89\x77\x72" }, + // PWM output mux register (0x25) + // Channel 1 --> OUTA, channel 1 neg --> OUTB + // Channel 2 --> OUTC, channel 2 neg --> OUTD + { .size = 5, .data = "\x25\x01\x02\x13\x45" }, + // DRC control (0x46): DRC off + { .size = 5, .data = "\x46\x00\x00\x00\x00" }, + // BKND_ERR register (0x1C): 299ms reset period + { .size = 2, .data = "\x1C\x07" }, + // Mute channel 3 + { .size = 2, .data = "\x0A\xFF" }, + // Volume configuration register (0x0E): volume slew 512 steps + { .size = 2, .data = "\x0E\x90" }, + // Clock control register (0x00): 44/48kHz, MCLK=64xfs + { .size = 2, .data = "\x00\x60" }, + // Bank switch and eq control (0x50): no bank switching + { .size = 5, .data = "\x50\x00\x00\x00\x00" }, + // Volume registers (0x07, 0x08, 0x09, 0x0A) + { .size = 2, .data = "\x07\x20" }, + { .size = 2, .data = "\x08\x30" }, + { .size = 2, .data = "\x09\x30" }, + { .size = 2, .data = "\x0A\xFF" }, + // 0x72, 0x73, 0x76, 0x77 input mixer: + // no intermix between channels + { .size = 5, .data = "\x72\x00\x00\x00\x00" }, + { .size = 5, .data = "\x73\x00\x80\x00\x00" }, + { .size = 5, .data = "\x76\x00\x00\x00\x00" }, + { .size = 5, .data = "\x77\x00\x80\x00\x00" }, + // 0x70, 0x71, 0x74, 0x75 inline DRC mixer: + // no inline DRC inmix + { .size = 5, .data = "\x70\x00\x80\x00\x00" }, + { .size = 5, .data = "\x71\x00\x00\x00\x00" }, + { .size = 5, .data = "\x74\x00\x80\x00\x00" }, + { .size = 5, .data = "\x75\x00\x00\x00\x00" }, + // 0x56, 0x57 Output scale + { .size = 5, .data = "\x56\x00\x80\x00\x00" }, + { .size = 5, .data = "\x57\x00\x02\x00\x00" }, + // 0x3B, 0x3c + { .size = 9, .data = "\x3B\x00\x08\x00\x00\x00\x78\x00\x00" }, + { .size = 9, .data = "\x3C\x00\x00\x01\x00\xFF\xFF\xFF\x00" }, + { .size = 9, .data = "\x3E\x00\x08\x00\x00\x00\x78\x00\x00" }, + { .size = 9, .data = "\x3F\x00\x00\x01\x00\xFF\xFF\xFF\x00" }, + { .size = 9, .data = "\x40\x00\x00\x01\x00\xFF\xFF\xFF\x00" }, + { .size = 9, .data = "\x43\x00\x00\x01\x00\xFF\xFF\xFF\x00" }, + // 0x51, 0x52: output mixer + { .size = 9, .data = "\x51\x00\x80\x00\x00\x00\x00\x00\x00" }, + { .size = 9, .data = "\x52\x00\x80\x00\x00\x00\x00\x00\x00" }, + // PEQ defaults + { .size = 21, .data = "\x29\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }, + { .size = 21, .data = "\x2A\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }, + { .size = 21, .data = "\x2B\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }, + { .size = 21, .data = "\x2C\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }, + { .size = 21, .data = "\x2D\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }, + { .size = 21, .data = "\x2E\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }, + { .size = 21, .data = "\x2F\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }, + { .size = 21, .data = "\x30\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }, + { .size = 21, .data = "\x31\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }, + { .size = 21, .data = "\x32\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }, + { .size = 21, .data = "\x33\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }, + { .size = 21, .data = "\x34\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }, + { .size = 21, .data = "\x35\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }, + { .size = 21, .data = "\x36\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }, + { .size = 21, .data = "\x58\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }, + { .size = 21, .data = "\x59\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }, + { .size = 21, .data = "\x5C\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }, + { .size = 21, .data = "\x5D\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }, + { .size = 21, .data = "\x5E\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }, + { .size = 21, .data = "\x5F\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }, + { .size = 21, .data = "\x5A\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }, + { .size = 21, .data = "\x5B\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }, +}; + + +#endif /* _TAS5713_H */ diff --git a/sound/soc/dwc/dwc-i2s.c b/sound/soc/dwc/dwc-i2s.c index 28001e9857d9..0df1bfc52f4e 100644 --- a/sound/soc/dwc/dwc-i2s.c +++ b/sound/soc/dwc/dwc-i2s.c @@ -209,12 +209,21 @@ static void i2s_start(struct dw_i2s_dev *dev, static void i2s_stop(struct dw_i2s_dev *dev, struct snd_pcm_substream *substream) { + if (dev->is_jh7110) { + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai_link *dai_link = rtd->dai_link; + dai_link->trigger_stop = SND_SOC_TRIGGER_ORDER_LDC; + } + i2s_clear_irqs(dev, substream->stream); + + i2s_disable_irqs(dev, substream->stream, 8); +} + +static void i2s_pause(struct dw_i2s_dev *dev, + struct snd_pcm_substream *substream) +{ i2s_clear_irqs(dev, substream->stream); - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - i2s_write_reg(dev->i2s_base, ITER, 0); - else - i2s_write_reg(dev->i2s_base, IRER, 0); if (!(dev->use_pio || dev->is_jh7110)) i2s_disable_dma(dev, substream->stream); @@ -224,14 +233,39 @@ static void i2s_stop(struct dw_i2s_dev *dev, if (!dev->active) { i2s_write_reg(dev->i2s_base, CER, 0); - i2s_write_reg(dev->i2s_base, IER, 0); + /* Keep the device enabled until the shutdown - do not clear IER */ } } +static void dw_i2s_config(struct dw_i2s_dev *dev, int stream); static int dw_i2s_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *cpu_dai) { struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(cpu_dai); + union dw_i2s_snd_dma_data *dma_data = NULL; + u32 dmacr; + + dev_dbg(dev->dev, "%s(%s)\n", __func__, substream->name); + if (!(dev->capability & DWC_I2S_RECORD) && + substream->stream == SNDRV_PCM_STREAM_CAPTURE) + return -EINVAL; + + if (!(dev->capability & DWC_I2S_PLAY) && + substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + return -EINVAL; + + dw_i2s_config(dev, substream->stream); + dmacr = i2s_read_reg(dev->i2s_base, I2S_DMACR); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + dma_data = &dev->play_dma_data; + dmacr |= DMACR_DMAEN_TX; + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + dma_data = &dev->capture_dma_data; + dmacr |= DMACR_DMAEN_RX; + } + + snd_soc_dai_set_dma_data(cpu_dai, substream, (void *)dma_data); + i2s_write_reg(dev->i2s_base, I2S_DMACR, dmacr); if (dev->is_jh7110) { struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); @@ -243,22 +277,52 @@ static int dw_i2s_startup(struct snd_pcm_substream *substream, return 0; } +static void dw_i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); + + dev_dbg(dev->dev, "%s(%s)\n", __func__, substream->name); + i2s_disable_channels(dev, substream->stream); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + i2s_write_reg(dev->i2s_base, ITER, 0); + else + i2s_write_reg(dev->i2s_base, IRER, 0); + + i2s_disable_irqs(dev, substream->stream, 8); + + if (!dev->active) { + i2s_write_reg(dev->i2s_base, CER, 0); + i2s_write_reg(dev->i2s_base, IER, 0); + } +} + static void dw_i2s_config(struct dw_i2s_dev *dev, int stream) { u32 ch_reg; struct i2s_clk_config_data *config = &dev->config; - + u32 dmacr; + u32 comp1 = i2s_read_reg(dev->i2s_base, dev->i2s_reg_comp1); + u32 fifo_depth = 1 << (1 + COMP1_FIFO_DEPTH_GLOBAL(comp1)); i2s_disable_channels(dev, stream); + dmacr = i2s_read_reg(dev->i2s_base, I2S_DMACR); + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + dmacr &= ~(DMACR_DMAEN_TXCH0 * 0xf); + else + dmacr &= ~(DMACR_DMAEN_RXCH0 * 0xf); + for (ch_reg = 0; ch_reg < (config->chan_nr / 2); ch_reg++) { if (stream == SNDRV_PCM_STREAM_PLAYBACK) { i2s_write_reg(dev->i2s_base, TCR(ch_reg), dev->xfer_resolution); i2s_write_reg(dev->i2s_base, TFCR(ch_reg), - dev->fifo_th - 1); + fifo_depth - dev->fifo_th - 1); i2s_write_reg(dev->i2s_base, TER(ch_reg), TER_TXCHEN | dev->tdm_mask << TER_TXSLOT_SHIFT); + dmacr |= (DMACR_DMAEN_TXCH0 << ch_reg); } else { i2s_write_reg(dev->i2s_base, RCR(ch_reg), dev->xfer_resolution); @@ -266,9 +330,11 @@ static void dw_i2s_config(struct dw_i2s_dev *dev, int stream) dev->fifo_th - 1); i2s_write_reg(dev->i2s_base, RER(ch_reg), RER_RXCHEN | dev->tdm_mask << RER_RXSLOT_SHIFT); + dmacr |= (DMACR_DMAEN_RXCH0 << ch_reg); } - } + + i2s_write_reg(dev->i2s_base, I2S_DMACR, dmacr); } static int dw_i2s_hw_params(struct snd_pcm_substream *substream, @@ -276,24 +342,32 @@ static int dw_i2s_hw_params(struct snd_pcm_substream *substream, { struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); struct i2s_clk_config_data *config = &dev->config; + union dw_i2s_snd_dma_data *dma_data = NULL; int ret; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dma_data = &dev->play_dma_data; + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + dma_data = &dev->capture_dma_data; + else + return -1; + switch (params_format(params)) { case SNDRV_PCM_FORMAT_S16_LE: config->data_width = 16; - dev->ccr = 0x00; + dma_data->dt.addr_width = 2; dev->xfer_resolution = 0x02; break; case SNDRV_PCM_FORMAT_S24_LE: config->data_width = 24; - dev->ccr = 0x08; + dma_data->dt.addr_width = 4; dev->xfer_resolution = 0x04; break; case SNDRV_PCM_FORMAT_S32_LE: config->data_width = 32; - dev->ccr = 0x10; + dma_data->dt.addr_width = 4; dev->xfer_resolution = 0x05; break; @@ -314,17 +388,37 @@ static int dw_i2s_hw_params(struct snd_pcm_substream *substream, case TWO_CHANNEL_SUPPORT: break; default: - dev_err(dev->dev, "channel not supported\n"); + dev_err(dev->dev, "channel count %d not supported\n", config->chan_nr); return -EINVAL; } dw_i2s_config(dev, substream->stream); - i2s_write_reg(dev->i2s_base, CCR, dev->ccr); - config->sample_rate = params_rate(params); if (dev->capability & DW_I2S_MASTER) { + u32 frame_length = config->data_width * 2; + + if (dev->bclk_ratio) + frame_length = dev->bclk_ratio; + + switch (frame_length) { + case 32: + dev->ccr = 0x00; + break; + + case 48: + dev->ccr = 0x08; + break; + + case 64: + dev->ccr = 0x10; + break; + + default: + return -EINVAL; + } + if (dev->i2s_clk_cfg) { ret = dev->i2s_clk_cfg(config); if (ret < 0) { @@ -332,8 +426,7 @@ static int dw_i2s_hw_params(struct snd_pcm_substream *substream, return ret; } } else { - u32 bitclk = config->sample_rate * - config->data_width * 2; + u32 bitclk = config->sample_rate * frame_length; ret = clk_set_rate(dev->clk, bitclk); if (ret) { @@ -342,6 +435,8 @@ static int dw_i2s_hw_params(struct snd_pcm_substream *substream, return ret; } } + + i2s_write_reg(dev->i2s_base, CCR, dev->ccr); } return 0; } @@ -373,9 +468,12 @@ static int dw_i2s_trigger(struct snd_pcm_substream *substream, i2s_start(dev, substream); break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + dev->active--; + i2s_pause(dev, substream); + break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: - case SNDRV_PCM_TRIGGER_PAUSE_PUSH: dev->active--; i2s_stop(dev, substream); break; @@ -459,6 +557,18 @@ static int dw_i2s_set_tdm_slot(struct snd_soc_dai *cpu_dai, unsigned int tx_mask return 0; } +static int dw_i2s_set_bclk_ratio(struct snd_soc_dai *cpu_dai, + unsigned int ratio) +{ + struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(cpu_dai); + + dev_dbg(dev->dev, "%s(%d)\n", __func__, ratio); + + dev->bclk_ratio = ratio; + + return 0; +} + static int dw_i2s_dai_probe(struct snd_soc_dai *dai) { struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); @@ -470,11 +580,13 @@ static int dw_i2s_dai_probe(struct snd_soc_dai *dai) static const struct snd_soc_dai_ops dw_i2s_dai_ops = { .probe = dw_i2s_dai_probe, .startup = dw_i2s_startup, + .shutdown = dw_i2s_shutdown, .hw_params = dw_i2s_hw_params, .prepare = dw_i2s_prepare, .trigger = dw_i2s_trigger, .set_fmt = dw_i2s_set_fmt, .set_tdm_slot = dw_i2s_set_tdm_slot, + .set_bclk_ratio = dw_i2s_set_bclk_ratio, }; static int dw_i2s_runtime_suspend(struct device *dev) @@ -605,7 +717,7 @@ static int dw_configure_dai(struct dw_i2s_dev *dev, idx = 1; dw_i2s_dai->playback.channels_min = MIN_CHANNEL_NUM; dw_i2s_dai->playback.channels_max = - 1 << (COMP1_TX_CHANNELS(comp1) + 1); + 2 * (COMP1_TX_CHANNELS(comp1) + 1); dw_i2s_dai->playback.formats = formats[idx]; dw_i2s_dai->playback.rates = rates; } @@ -619,7 +731,7 @@ static int dw_configure_dai(struct dw_i2s_dev *dev, idx = 1; dw_i2s_dai->capture.channels_min = MIN_CHANNEL_NUM; dw_i2s_dai->capture.channels_max = - 1 << (COMP1_RX_CHANNELS(comp1) + 1); + 2 * (COMP1_RX_CHANNELS(comp1) + 1); dw_i2s_dai->capture.formats = formats[idx]; dw_i2s_dai->capture.rates = rates; } @@ -680,8 +792,8 @@ static int dw_configure_dai_by_pd(struct dw_i2s_dev *dev, dev->capture_dma_data.pd.data = pdata->capture_dma_data; dev->play_dma_data.pd.addr = res->start + I2S_TXDMA; dev->capture_dma_data.pd.addr = res->start + I2S_RXDMA; - dev->play_dma_data.pd.max_burst = 16; - dev->capture_dma_data.pd.max_burst = 16; + dev->play_dma_data.pd.max_burst = dev->fifo_th; + dev->capture_dma_data.pd.max_burst = dev->fifo_th; dev->play_dma_data.pd.addr_width = bus_widths[idx]; dev->capture_dma_data.pd.addr_width = bus_widths[idx]; dev->play_dma_data.pd.filter = pdata->filter; @@ -701,7 +813,7 @@ static int dw_configure_dai_by_dt(struct dw_i2s_dev *dev, u32 idx2; int ret; - ret = dw_configure_dai(dev, dw_i2s_dai, SNDRV_PCM_RATE_8000_192000); + ret = dw_configure_dai(dev, dw_i2s_dai, SNDRV_PCM_RATE_8000_384000); if (ret < 0) return ret; @@ -712,7 +824,10 @@ static int dw_configure_dai_by_dt(struct dw_i2s_dev *dev, dev->play_dma_data.dt.addr = res->start + I2S_TXDMA; dev->play_dma_data.dt.fifo_size = fifo_depth * (fifo_width[idx2]) >> 8; - dev->play_dma_data.dt.maxburst = 16; + if (dev->max_dma_burst) + dev->play_dma_data.dt.maxburst = dev->max_dma_burst; + else + dev->play_dma_data.dt.maxburst = fifo_depth / 2; } if (COMP1_RX_ENABLED(comp1)) { idx2 = COMP2_RX_WORDSIZE_0(comp2); @@ -721,9 +836,14 @@ static int dw_configure_dai_by_dt(struct dw_i2s_dev *dev, dev->capture_dma_data.dt.addr = res->start + I2S_RXDMA; dev->capture_dma_data.dt.fifo_size = fifo_depth * (fifo_width[idx2] >> 8); - dev->capture_dma_data.dt.maxburst = 16; + if (dev->max_dma_burst) + dev->capture_dma_data.dt.maxburst = dev->max_dma_burst; + else + dev->capture_dma_data.dt.maxburst = fifo_depth / 2; } + if (dev->max_dma_burst) + dev->fifo_th = min(dev->max_dma_burst, dev->fifo_th); return 0; } @@ -967,6 +1087,8 @@ static int dw_i2s_probe(struct platform_device *pdev) } } + of_property_read_u32(pdev->dev.of_node, "dma-maxburst", &dev->max_dma_burst); + dev->bclk_ratio = 0; dev->i2s_reg_comp1 = I2S_COMP_PARAM_1; dev->i2s_reg_comp2 = I2S_COMP_PARAM_2; if (pdata) { diff --git a/sound/soc/dwc/local.h b/sound/soc/dwc/local.h index dce88c9ad5f3..afff89db5dbb 100644 --- a/sound/soc/dwc/local.h +++ b/sound/soc/dwc/local.h @@ -63,6 +63,17 @@ #define TER_TXSLOT_SHIFT 8 #define TER_TXCHEN BIT(0) +#define DMACR_DMAEN_TX BIT(17) +#define DMACR_DMAEN_RX BIT(16) +#define DMACR_DMAEN_TXCH3 BIT(11) +#define DMACR_DMAEN_TXCH2 BIT(10) +#define DMACR_DMAEN_TXCH1 BIT(9) +#define DMACR_DMAEN_TXCH0 BIT(8) +#define DMACR_DMAEN_RXCH3 BIT(3) +#define DMACR_DMAEN_RXCH2 BIT(2) +#define DMACR_DMAEN_RXCH1 BIT(1) +#define DMACR_DMAEN_RXCH0 BIT(0) + /* I2SCOMPRegisters */ #define I2S_COMP_PARAM_2 0x01F0 #define I2S_COMP_PARAM_1 0x01F4 @@ -117,10 +128,12 @@ struct dw_i2s_dev { unsigned int quirks; unsigned int i2s_reg_comp1; unsigned int i2s_reg_comp2; + unsigned int bclk_ratio; struct device *dev; u32 ccr; u32 xfer_resolution; u32 fifo_th; + u32 max_dma_burst; u32 l_reg; u32 r_reg; bool is_jh7110; /* Flag for StarFive JH7110 SoC */ diff --git a/sound/soc/raspberrypi/Kconfig b/sound/soc/raspberrypi/Kconfig new file mode 100644 index 000000000000..389bb82e3651 --- /dev/null +++ b/sound/soc/raspberrypi/Kconfig @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0-only +config SND_RP1_AUDIO_OUT + tristate "PWM Audio Out from RP1" + select SND_SOC_GENERIC_DMAENGINE_PCM + select SND_SOC_SPDIF + help + Say Y or M if you want to add support for PWM digital + audio output from a Raspberry Pi 5, 500 or CM5. + + Output is from RP1 GPIOs pins 12 and 13 only, and additional + components will be needed. It may be useful when HDMI, I2S + or USB audio devices are unavailable, or for compatibility. diff --git a/sound/soc/raspberrypi/Makefile b/sound/soc/raspberrypi/Makefile new file mode 100644 index 000000000000..80ff460c785a --- /dev/null +++ b/sound/soc/raspberrypi/Makefile @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_SND_RP1_AUDIO_OUT) += rp1_aout.o diff --git a/sound/soc/raspberrypi/rp1_aout.c b/sound/soc/raspberrypi/rp1_aout.c new file mode 100644 index 000000000000..ffcc7b815d5e --- /dev/null +++ b/sound/soc/raspberrypi/rp1_aout.c @@ -0,0 +1,372 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2025 Raspberry Pi Ltd. + * + * rp1_aout.c -- Driver for Raspberry Pi RP1 Audio Out block. + * This is a modified 2-channel PWM with hardware 40 times oversampling, + * 2nd order noise shaping and dither. Only output (playback) is supported. + * + * For ASOC, this is implemented as a "DAI" and will need to be linked to a + * dummy codec such as "linux,spdif-dit" and card such as "simple-audio-card". + * + * Driver/file structure derived in part from "soc/starfive/jh7110_pwmdac.c" + * and other example drivers. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define AUDIO_OUT_CTRL 0x0000 +#define AUDIO_OUT_CTRL_PERIPH_EN BIT(31) +#define AUDIO_OUT_CTRL_CIC_RATE GENMASK(11, 8) +#define AUDIO_OUT_CTRL_CHANNEL_SWAP BIT(2) +#define AUDIO_OUT_CTRL_RIGHT_CH_ENABLE BIT(1) +#define AUDIO_OUT_CTRL_LEFT_CH_ENABLE BIT(0) + +#define AUDIO_OUT_SDMCTL_LEFT 0x0004 +#define AUDIO_OUT_SDMCTL_RIGHT 0x0008 +#define AUDIO_OUT_SDMCTL_LR_BIAS GENMASK(31, 16) +#define AUDIO_OUT_SDMCTL_LR_BYPASS BIT(7) +#define AUDIO_OUT_SDMCTL_LR_SDM_ORDER BIT(6) +#define AUDIO_OUT_SDMCTL_LR_CLAMP_EN BIT(5) +#define AUDIO_OUT_SDMCTL_LR_DITHER_EN BIT(4) +#define AUDIO_OUT_SDMCTL_LR_OUTPUT_BITWIDTH GENMASK(3, 0) + +#define AUDIO_OUT_QCLAMP_LEFT 0x000c +#define AUDIO_OUT_QCLAMP_RIGHT 0x0010 +#define AUDIO_OUT_QCLAMP_LR_MAX GENMASK(31, 16) +#define AUDIO_OUT_QCLAMP_LR_MIN GENMASK(15, 0) + +#define AUDIO_OUT_MUTE_CTRL_LEFT 0x0014 +#define AUDIO_OUT_MUTE_CTRL_RIGHT 0x0018 +#define AUDIO_OUT_MUTE_CTRL_LR_BYPASS BIT(31) +#define AUDIO_OUT_MUTE_CTRL_LR_MUTE_PERIOD GENMASK(23, 16) +#define AUDIO_OUT_MUTE_CTRL_LR_STEP_SIZE GENMASK(15, 8) +#define AUDIO_OUT_MUTE_CTRL_LR_INIT_UNMUTE BIT(5) +#define AUDIO_OUT_MUTE_CTRL_LR_INIT_MUTE BIT(4) +#define AUDIO_OUT_MUTE_CTRL_LR_MUTE_FSM GENMASK(3, 0) + +#define AUDIO_OUT_PWMCTL_LEFT 0x001c +#define AUDIO_OUT_PWMRANGE_LEFT 0x0020 +#define AUDIO_OUT_PWMCTL_RIGHT 0x0028 +#define AUDIO_OUT_PWMRANGE_RIGHT 0x002c + +#define AUDIO_OUT_FIFO_CONTROL 0x0034 +#define AUDIO_OUT_FIFO_CONTROL_DMA_DREQ_EN BIT(31) +#define AUDIO_OUT_FIFO_CONTROL_FLUSH_DONE BIT(25) +#define AUDIO_OUT_FIFO_CONTROL_FIFO_FLUSH BIT(24) +#define AUDIO_OUT_FIFO_CONTROL_DWELL_TIME GENMASK(20, 16) +#define AUDIO_OUT_FIFO_CONTROL_THRESHOLD GENMASK(5, 0) + +#define AUDIO_OUT_SAMPLE_FIFO 0x0038 + +struct rp1_aout { + void __iomem *regs; + phys_addr_t physaddr; + struct clk *clk; + struct device *dev; + struct snd_dmaengine_dai_dma_data play_dma_data; + bool initted; + bool swap_lr; +}; + +static inline void aout_reg_wr(struct rp1_aout *ao, unsigned int offset, u32 val) +{ + void __iomem *addr = ao->regs + offset; + + writel(val, addr); +} + +static inline u32 aout_reg_rd(struct rp1_aout *ao, unsigned int offset) +{ + void __iomem *addr = ao->regs + offset; + + return readl(addr); +} + +static void audio_init(struct rp1_aout *aout) +{ + u32 val; + + /* + * The hardware was tuned to play at 48 kHz with 40 times oversampling and + * 40-level two-sided PWM, for a clock rate of 48000 * 40 * 80 = 153.6 MHz. + * Changing these settings is not recommended. At those rates, the filter + * leaves ~2.2 dB headroom, so Qclamp will clip just slightly below FSD. + */ + + /* Clamp to +/- (32767 * 40 / 64) before quantization */ + val = FIELD_PREP_CONST(AUDIO_OUT_QCLAMP_LR_MAX, 20479) | + FIELD_PREP_CONST(AUDIO_OUT_QCLAMP_LR_MIN, (u16)(-20479)); + aout_reg_wr(aout, AUDIO_OUT_QCLAMP_LEFT, val); + aout_reg_wr(aout, AUDIO_OUT_QCLAMP_RIGHT, val); + aout_reg_wr(aout, AUDIO_OUT_PWMCTL_LEFT, 0); + aout_reg_wr(aout, AUDIO_OUT_PWMCTL_RIGHT, 0); + + /* Range = 39 */ + aout_reg_wr(aout, AUDIO_OUT_PWMRANGE_LEFT, 0x27); + aout_reg_wr(aout, AUDIO_OUT_PWMRANGE_RIGHT, 0x27); + + /* bias = 20 (half FSD). Quantize to 5+1 bits */ + val = FIELD_PREP_CONST(AUDIO_OUT_SDMCTL_LR_BIAS, 0x14) | + AUDIO_OUT_SDMCTL_LR_CLAMP_EN | + AUDIO_OUT_SDMCTL_LR_DITHER_EN | + FIELD_PREP_CONST(AUDIO_OUT_SDMCTL_LR_OUTPUT_BITWIDTH, 5); + aout_reg_wr(aout, AUDIO_OUT_SDMCTL_LEFT, val); + aout_reg_wr(aout, AUDIO_OUT_SDMCTL_RIGHT, val); + + /* ~300ms ramp = 12k*40 samples to FSD/2 => step size 1, interval 13 */ + val = FIELD_PREP_CONST(AUDIO_OUT_MUTE_CTRL_LR_STEP_SIZE, 1) | + FIELD_PREP_CONST(AUDIO_OUT_MUTE_CTRL_LR_MUTE_PERIOD, 13); + aout_reg_wr(aout, AUDIO_OUT_MUTE_CTRL_LEFT, val); + aout_reg_wr(aout, AUDIO_OUT_MUTE_CTRL_RIGHT, val); + + /* Configure DMA flow control with threshold at half FIFO depth */ + val = FIELD_PREP_CONST(AUDIO_OUT_FIFO_CONTROL_DWELL_TIME, 2) | + FIELD_PREP_CONST(AUDIO_OUT_FIFO_CONTROL_THRESHOLD, 0x10) | + AUDIO_OUT_FIFO_CONTROL_DMA_DREQ_EN; + aout_reg_wr(aout, AUDIO_OUT_FIFO_CONTROL, val); +} + +static void audio_startup(struct rp1_aout *aout) +{ + u32 val; + + /* CIC rate 10, for an overall upsampling ratio of 40 */ + val = FIELD_PREP_CONST(AUDIO_OUT_CTRL_CIC_RATE, 0xa) | + AUDIO_OUT_CTRL_LEFT_CH_ENABLE | AUDIO_OUT_CTRL_RIGHT_CH_ENABLE; + if (aout->swap_lr) + val |= AUDIO_OUT_CTRL_CHANNEL_SWAP; + aout_reg_wr(aout, AUDIO_OUT_CTRL, val); + aout_reg_rd(aout, AUDIO_OUT_CTRL); /* synchronization delay */ + + /* Press the "go" button */ + val |= AUDIO_OUT_CTRL_PERIPH_EN; + aout_reg_wr(aout, AUDIO_OUT_CTRL, val); + aout_reg_rd(aout, AUDIO_OUT_CTRL); /* FIFO reset release delay */ + + /* Poke zeroes in to avoid undefined values on underrun */ + aout_reg_wr(aout, AUDIO_OUT_SAMPLE_FIFO, 0); +} + +static void audio_muting(struct rp1_aout *aout, u32 flag) +{ + u32 val = FIELD_PREP_CONST(AUDIO_OUT_MUTE_CTRL_LR_STEP_SIZE, 1) | + FIELD_PREP_CONST(AUDIO_OUT_MUTE_CTRL_LR_MUTE_PERIOD, 13); + aout_reg_wr(aout, AUDIO_OUT_MUTE_CTRL_LEFT, val); + aout_reg_wr(aout, AUDIO_OUT_MUTE_CTRL_RIGHT, val); + + val |= flag; + aout_reg_wr(aout, AUDIO_OUT_MUTE_CTRL_LEFT, val); + aout_reg_wr(aout, AUDIO_OUT_MUTE_CTRL_RIGHT, val); + aout_reg_rd(aout, AUDIO_OUT_MUTE_CTRL_RIGHT); /* synchronization delay */ +} + +static void audio_mute_sync(struct rp1_aout *aout) +{ + static const u32 mask = 0x1 | 0x4; /* transitional states */ + unsigned int count; + + for (count = 0; count < 500; count++) { + if ((aout_reg_rd(aout, AUDIO_OUT_MUTE_CTRL_LEFT) & mask) == 0) + break; + usleep_range(1000, 5000); + } + for (; count < 500; count++) { + if ((aout_reg_rd(aout, AUDIO_OUT_MUTE_CTRL_RIGHT) & mask) == 0) + break; + usleep_range(1000, 5000); + } +} + +/* Device DAI interface */ + +static int rp1_aout_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct rp1_aout *aout = dev_get_drvdata(dai->dev); + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai_link *dai_link = rtd->dai_link; + + dai_link->trigger_stop = SND_SOC_TRIGGER_ORDER_LDC; + + if (!aout->initted) { + dev_info(dai->dev, "RP1 Audio Out start\n"); + audio_init(aout); + audio_startup(aout); + audio_muting(aout, AUDIO_OUT_MUTE_CTRL_LR_INIT_UNMUTE); + aout->initted = true; + } + + return 0; +} + +static int rp1_aout_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct rp1_aout *aout = dev_get_drvdata(dai->dev); + + /* We only support this one configuration */ + if (params_rate(params) != 48000 || params_channels(params) != 2 || + params_format(params) != SNDRV_PCM_FORMAT_S16_LE) { + dev_err(dai->dev, "Invalid HW params\n"); + return -EINVAL; + } + + audio_mute_sync(aout); + + return 0; +} + +static int rp1_aout_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct rp1_aout *aout = snd_soc_dai_get_drvdata(dai); + + if (cmd == SNDRV_PCM_TRIGGER_STOP || + cmd == SNDRV_PCM_TRIGGER_SUSPEND || + cmd == SNDRV_PCM_TRIGGER_PAUSE_PUSH) { + /* Push a zero sample (assuming DMA has stopped already) */ + aout_reg_wr(aout, AUDIO_OUT_SAMPLE_FIFO, 0); + } + + return 0; +} + +static int rp1_aout_dai_probe(struct snd_soc_dai *dai) +{ + struct rp1_aout *aout = dev_get_drvdata(dai->dev); + + snd_soc_dai_init_dma_data(dai, &aout->play_dma_data, NULL); + snd_soc_dai_set_drvdata(dai, aout); + + return 0; +} + +static const struct snd_soc_dai_ops rp1_aout_dai_ops = { + .probe = rp1_aout_dai_probe, + .startup = rp1_aout_startup, + .hw_params = rp1_aout_hw_params, + .trigger = rp1_aout_trigger, +}; + +static const struct snd_soc_component_driver rp1_aout_component = { + .name = "rp1-aout", +}; + +static struct snd_soc_dai_driver rp1_aout_dai = { + .name = "rp1-aout", + .id = 0, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &rp1_aout_dai_ops, +}; + +static int rp1_aout_platform_probe(struct platform_device *pdev) +{ + int ret; + struct rp1_aout *aout; + struct resource *ioresource; + + dev_info(&pdev->dev, "rp1_aout_platform_probe"); + aout = devm_kzalloc(&pdev->dev, sizeof(*aout), GFP_KERNEL); + if (!aout) + return -ENOMEM; + + aout->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(aout->clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(aout->clk), + "could not get clk\n"); + + aout->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &ioresource); + if (IS_ERR(aout->regs)) + return dev_err_probe(&pdev->dev, PTR_ERR(aout->regs), + "could not map registers\n"); + aout->physaddr = ioresource->start; + aout->swap_lr = of_property_read_bool(pdev->dev.of_node, "swap_lr"); + + /* Initialize playback DMA parameters */ + aout->play_dma_data.addr = aout->physaddr + AUDIO_OUT_SAMPLE_FIFO; + aout->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + aout->play_dma_data.fifo_size = 16; /* actually 32 but the threshold is 16(?) */ + aout->play_dma_data.maxburst = 4; + + clk_prepare_enable(aout->clk); + aout->dev = &pdev->dev; + platform_set_drvdata(pdev, aout); + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) + return dev_err_probe(&pdev->dev, ret, "failed to register pcm\n"); + + /* Finally, register the component so simple-audio-card can detect it */ + ret = devm_snd_soc_register_component(&pdev->dev, + &rp1_aout_component, + &rp1_aout_dai, 1); + if (ret) + return dev_err_probe(&pdev->dev, ret, "failed to register dai\n"); + + return 0; +} + +static void rp1_aout_platform_remove(struct platform_device *pdev) +{ + struct rp1_aout *aout = platform_get_drvdata(pdev); + + if (aout) { + /* + * We leave the PWM carrier/bias running between playbacks, + * but mute it just before shutting down, to avoid a click + * (devm should clear up everything else). + */ + if (aout->initted) { + audio_mute_sync(aout); + audio_muting(aout, + AUDIO_OUT_MUTE_CTRL_LR_INIT_MUTE); + audio_mute_sync(aout); + aout->initted = false; + } + clk_disable_unprepare(aout->clk); + } +} + +static const struct of_device_id rp1_aout_of_match[] = { + { .compatible = "raspberrypi,rp1-audio-out", }, + { /* sentinel */ }, +}; + +MODULE_DEVICE_TABLE(of, rp1_aout_of_match); + +static struct platform_driver rp1_audio_out_driver = { + .probe = rp1_aout_platform_probe, + .remove = rp1_aout_platform_remove, + .shutdown = rp1_aout_platform_remove, + .driver = { + .name = "rp1-audio-out", + .of_match_table = rp1_aout_of_match, + }, +}; + +module_platform_driver(rp1_audio_out_driver); + +MODULE_DESCRIPTION("RP1 Audio out"); +MODULE_AUTHOR("Nick Hollinghurst "); +MODULE_AUTHOR("Jonathan Bell "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index cc9125ffe92a..9aa8238ba652 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1485,8 +1485,16 @@ int snd_soc_runtime_set_dai_fmt(struct snd_soc_pcm_runtime *rtd, * ext_fmt includes 4 */ for_each_rtd_codec_dais(rtd, i, codec_dai) { + unsigned int codec_dai_fmt = dai_fmt; + + // there can only be one master when using multiple codecs + if (i && (codec_dai_fmt & SND_SOC_DAIFMT_MASTER_MASK)) { + codec_dai_fmt &= ~SND_SOC_DAIFMT_MASTER_MASK; + codec_dai_fmt |= SND_SOC_DAIFMT_CBC_CFC; + } + ext_fmt = rtd->dai_link->codecs[i].ext_fmt; - ret = snd_soc_dai_set_fmt(codec_dai, dai_fmt | ext_fmt); + ret = snd_soc_dai_set_fmt(codec_dai, codec_dai_fmt | ext_fmt); if (ret != 0 && ret != -ENOTSUPP) return ret; } diff --git a/sound/usb/card.c b/sound/usb/card.c index 557f53d10ecf..18b913ece1f1 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -965,8 +965,14 @@ static int usb_audio_probe(struct usb_interface *intf, if (ignore_ctl_error) chip->quirk_flags |= QUIRK_FLAG_IGNORE_CTL_ERROR; - if (chip->quirk_flags & QUIRK_FLAG_DISABLE_AUTOSUSPEND) + if (chip->quirk_flags & QUIRK_FLAG_DISABLE_AUTOSUSPEND) { + /* + * Grab the interface, because on a webcam uvcvideo may race + * with snd-usb-audio during probe and re-enable autosuspend. + */ + usb_autopm_get_interface(intf); usb_disable_autosuspend(interface_to_usbdev(intf)); + } /* * For devices with more than one control interface, we assume the diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 4a35f962527e..debcb9cba14a 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2378,6 +2378,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_ALIGN_TRANSFER), DEVICE_FLG(0x534d, 0x2109, /* MacroSilicon MS2109 */ QUIRK_FLAG_ALIGN_TRANSFER), + DEVICE_FLG(0x09da, 0x2695, /* A4Tech FHD 1080p webcam */ + QUIRK_FLAG_DISABLE_AUTOSUSPEND | QUIRK_FLAG_GET_SAMPLE_RATE), /* Vendor matches */ VENDOR_FLG(0x045e, /* MS Lifecam */