diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt new file mode 100644 index 00000000000000..f8d32547195b3a --- /dev/null +++ b/Documentation/devicetree/bindings/vendor-prefixes.txt @@ -0,0 +1,463 @@ +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 +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 a70ce43b3dc032..7a9595fab33732 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -216,6 +216,8 @@ patternProperties: description: Shenzhen BigTree Tech Co., LTD "^bitmain,.*": description: Bitmain Technologies + "^blokaslabs,.*": + description: Vilniaus Blokas UAB "^blutek,.*": description: BluTek Power "^boe,.*": diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index 983ef4f36d8c43..16c889def20d24 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -98,6 +98,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 f793a16cad40bf..3e3f31c18e5f00 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -46,6 +46,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 00000000000000..5280b5100559d2 --- /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 00000000000000..0cbc0349f9f6e8 --- /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/soc/bcm/Kconfig b/sound/soc/bcm/Kconfig index 4218057b087426..d27ba79e1208f7 100644 --- a/sound/soc/bcm/Kconfig +++ b/sound/soc/bcm/Kconfig @@ -26,3 +26,272 @@ config SND_BCM63XX_I2S_WHISTLER DSL/PON chips (bcm63158, bcm63178) 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_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 diff --git a/sound/soc/bcm/Makefile b/sound/soc/bcm/Makefile index 0c1325a97b709f..7a969681aa7510 100644 --- a/sound/soc/bcm/Makefile +++ b/sound/soc/bcm/Makefile @@ -12,4 +12,73 @@ 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-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_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 00000000000000..aa0723b98fabb9 --- /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_CBM_CFM; + + 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_CBS_CFS, + .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 00000000000000..20dededd919642 --- /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_CBS_CFS: + dai_mode_val = 0; + break; + case SND_SOC_DAIFMT_CBM_CFM: + 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_CBM_CFM) { + /* 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_CBS_CFS: + cs43130->dais[codec_dai->id].dai_mode = SND_SOC_DAIFMT_CBS_CFS; + break; + case SND_SOC_DAIFMT_CBM_CFM: + cs43130->dais[codec_dai->id].dai_mode = SND_SOC_DAIFMT_CBM_CFM; + 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 00000000000000..266a89e4dc94b0 --- /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_CBM_CFM: // 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_CBS_CFS: + 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 00000000000000..4e12399ec04f55 --- /dev/null +++ b/sound/soc/bcm/allo-piano-dac-plus.c @@ -0,0 +1,1063 @@ +/* + * 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]); + + if (digital_gain_0db_limit) { + ret = snd_soc_limit_volume(card, "Subwoofer Playback Volume", + 207); + if (ret < 0) + dev_warn(card->dev, "Failed to set volume limit: %d\n", + ret); + } + + // 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 (digital_gain_0db_limit) { + ret = snd_soc_limit_volume(card, "Master Playback Volume", + 207); + if (ret < 0) + dev_warn(card->dev, "Failed to set volume limit: %d\n", + ret); + } + + 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); + + if (digital_gain_0db_limit) { + int ret; + + //Set volume limit on both dacs + for (i = 0; i < ARRAY_SIZE(codec_ctl_pfx); i++) { + char cname[256]; + + sprintf(cname, "%s %s", codec_ctl_pfx[i], codec_ctl_name[0]); + ret = snd_soc_limit_volume(card, cname, 207); + if (ret < 0) + dev_warn(card->dev, "Failed to set volume limit: %d\n", + ret); + } + } + + // 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) { + dev_err(rtd->card->dev, "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 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_CBS_CFS, + .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); + if (!allo_piano_2_1_codecs[0].of_node) { + dev_err(&pdev->dev, + "Property 'audio-codec' missing or invalid\n"); + return -EINVAL; + } + + allo_piano_2_1_codecs[1].of_node = + of_parse_phandle(pdev->dev.of_node, "audio-codec", 1); + if (!allo_piano_2_1_codecs[1].of_node) { + dev_err(&pdev->dev, + "Property 'audio-codec' missing or invalid\n"); + return -EINVAL; + } + + mute_gpio[0] = devm_gpiod_get_optional(&pdev->dev, "mute1", + GPIOD_OUT_LOW); + if (IS_ERR(mute_gpio[0])) { + ret = PTR_ERR(mute_gpio[0]); + dev_err(&pdev->dev, + "failed to get mute1 gpio6: %d\n", ret); + return ret; + } + + mute_gpio[1] = devm_gpiod_get_optional(&pdev->dev, "mute2", + GPIOD_OUT_LOW); + if (IS_ERR(mute_gpio[1])) { + ret = PTR_ERR(mute_gpio[1]); + dev_err(&pdev->dev, + "failed to get mute2 gpio25: %d\n", ret); + return ret; + } + + 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) { + dev_err(&pdev->dev, + "snd_soc_register_card() failed: %d\n", ret); + return 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 00000000000000..61640fb9543146 --- /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_CBS_CFS, + .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 00000000000000..6abe08223f3406 --- /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_CBM_CFM|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 00000000000000..e9521d0c60a224 --- /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_CBS_CFS|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_CBM_CFM|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 00000000000000..23958e5bf28f64 --- /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_CBM_CFM|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 00000000000000..200bb76df2ccf0 --- /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_CBM_CFM, + .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 9bda6499e66e1c..2d0fe53245f0bb 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 @@ -830,8 +829,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 +844,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 +853,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 00000000000000..ddf4f5ed90f6f2 --- /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_CBM_CFM, + .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 00000000000000..4a2520d077d2b7 --- /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_CBS_CFS, + .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 00000000000000..e1e7054534c8a4 --- /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_CBM_CFM, + .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_CBS_CFS, + 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 00000000000000..49aeaf121a3fe2 --- /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_CBS_CFS, + .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 00000000000000..5d5b677bf11cba --- /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_CBS_CFS, + .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 00000000000000..3263e2a9c30daa --- /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_CBM_CFM, + .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 00000000000000..79da8247d177b3 --- /dev/null +++ b/sound/soc/bcm/googlevoicehat-codec.c @@ -0,0 +1,213 @@ +/* + * 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 (dai->stream[SNDRV_PCM_STREAM_PLAYBACK].active) { + 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 (dai->stream[SNDRV_PCM_STREAM_PLAYBACK].active) { + 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_dacplus.c b/sound/soc/bcm/hifiberry_dacplus.c new file mode 100644 index 00000000000000..a6dacdced79a74 --- /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_CBM_CFM; + + 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_CBS_CFS, + .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 00000000000000..d21b006aad7d05 --- /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_CBM_CFM; + + 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_CBS_CFS, + .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 00000000000000..9cb8692caf1a35 --- /dev/null +++ b/sound/soc/bcm/hifiberry_dacplusadcpro.c @@ -0,0 +1,606 @@ +/* + * 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" + +#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 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]), +}; + +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_CBM_CFM; + + // 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_CBM_CFM); + 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_CBS_CFS); + 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_CBS_CFS); + 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_CBS_CFS, + .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 00000000000000..5a6ea965042d2f --- /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 00000000000000..4a5f7e4f95d982 --- /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_CBM_CFM; + + /* 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_CBS_CFS, + .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 00000000000000..6099651f53ee4c --- /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_CBS_CFS, + .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_CBS_CFS; + } + + /* 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 00000000000000..8eafd795d454d9 --- /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_CBM_CFM, + .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 00000000000000..f0680af30b741b --- /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_CBS_CFS, + .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 00000000000000..835217ffb0ad0a --- /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_CBM_CFM, + .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 00000000000000..e3b258a3518dd0 --- /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_CBS_CFS, + .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 00000000000000..cfdc7c67358336 --- /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_CBS_CFS, + .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 00000000000000..84396880e50273 --- /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_CBM_CFM, + .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 00000000000000..b04c1bf38f6bbf --- /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_CBM_CFM, + .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_CBM_CFM, + .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 00000000000000..d3cdadfbeec857 --- /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_CBM_CFM, + .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 00000000000000..3bf8653913f614 --- /dev/null +++ b/sound/soc/bcm/rpi-simple-soundcard.c @@ -0,0 +1,523 @@ +// 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_CBM_CFM, + 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_CBS_CFS, + 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_CBS_CFS, + 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_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_CBS_CFS, + 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_CBS_CFS, + 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_CBS_CFS, + 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, +}; + +static int hifiberry_dac8x_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + + /* override the defaults to reflect 4 x PCM5102A on the card + * and limit the sample rate to 192ksps + */ + codec_dai->driver->playback.channels_max = 8; + codec_dai->driver->playback.rates = SNDRV_PCM_RATE_8000_192000; + + 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_CBS_CFS, + .init = hifiberry_dac8x_init, + SND_SOC_DAILINK_REG(hifiberry_dac), + }, +}; + +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_CBS_CFS, + 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_CBS_CFS, + 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_CBS_CFS, + 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_CBS_CFS, + 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-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 00000000000000..3ecbf7adf7af88 --- /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_CBM_CFM; + + 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 b5e6d0a986c8e9..a2faf1b31a7a72 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -125,6 +125,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 @@ -132,6 +133,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 @@ -174,6 +176,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 @@ -266,6 +269,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 @@ -423,12 +427,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 @@ -1229,6 +1233,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 @@ -1638,6 +1649,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 @@ -1995,6 +2010,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 @@ -2589,4 +2607,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 622e360f00866b..b40a8eb3e552ed 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -818,3 +818,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 24c7b9c84c1981..328dfdc3f4751a 100644 --- a/sound/soc/codecs/adau1977-i2c.c +++ b/sound/soc/codecs/adau1977-i2c.c @@ -38,9 +38,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 ecaebf8e1c8fc7..ecf7af852ec8ef 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 = &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 9c44b6283b8f96..8e3b88d7d3af08 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 f3ef6fb5530471..2b93bb1da75650 100644 --- a/sound/soc/codecs/da7213.c +++ b/sound/soc/codecs/da7213.c @@ -1346,6 +1346,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: @@ -1361,6 +1363,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: @@ -1523,6 +1528,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; @@ -1735,6 +1755,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 505b731c0adb93..5b6d8835689120 100644 --- a/sound/soc/codecs/da7213.h +++ b/sound/soc/codecs/da7213.h @@ -600,6 +600,7 @@ struct da7213_priv { struct clk *mclk; unsigned int mclk_rate; unsigned int out_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 00000000000000..518c57f4558e10 --- /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_CBS_CFS: + 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_CBS_CFS) { + 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 00000000000000..9cac5a2446b9e4 --- /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 00000000000000..3df7e759ace1bb --- /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 00000000000000..3877a138f896bf --- /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/pcm512x.c b/sound/soc/codecs/pcm512x.c index aa8edf87b74339..6fb35e65645597 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 = { diff --git a/sound/soc/codecs/tas5713.c b/sound/soc/codecs/tas5713.c new file mode 100644 index 00000000000000..01dd21a1d7a256 --- /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 00000000000000..8f019e04898754 --- /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 c04466f5492e9a..868021493e8a60 100644 --- a/sound/soc/dwc/dwc-i2s.c +++ b/sound/soc/dwc/dwc-i2s.c @@ -211,12 +211,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_irqs(dev, substream->stream, 8); @@ -225,14 +234,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); @@ -244,14 +278,41 @@ 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; 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), @@ -260,6 +321,7 @@ static void dw_i2s_config(struct dw_i2s_dev *dev, int stream) 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); @@ -267,9 +329,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, @@ -277,24 +341,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; @@ -315,17 +387,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) { @@ -333,8 +425,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) { @@ -343,6 +434,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; } @@ -374,9 +467,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; @@ -460,6 +556,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); @@ -471,11 +579,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, }; #ifdef CONFIG_PM @@ -606,7 +716,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; } @@ -620,7 +730,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; } @@ -702,7 +812,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; @@ -968,6 +1078,7 @@ static int dw_i2s_probe(struct platform_device *pdev) } } + 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 dce88c9ad5f333..9f21eb68bd6cf3 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,6 +128,7 @@ 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; diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 724fe1f033b550..780fb2cf834443 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1435,7 +1435,15 @@ int snd_soc_runtime_set_dai_fmt(struct snd_soc_pcm_runtime *rtd, return 0; for_each_rtd_codec_dais(rtd, i, codec_dai) { - ret = snd_soc_dai_set_fmt(codec_dai, dai_fmt); + 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_CBS_CFS; + } + + ret = snd_soc_dai_set_fmt(codec_dai, codec_dai_fmt); if (ret != 0 && ret != -ENOTSUPP) return ret; } diff --git a/sound/usb/card.c b/sound/usb/card.c index 8f01a4b1fa0fa6..a956b79f2a0c57 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -867,8 +867,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 f4c68eb7e07a12..02b3f352422b07 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2293,6 +2293,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 */