--- /dev/null
+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.
description: Beckhoff Automation GmbH & Co. KG
"^bitmain,.*":
description: Bitmain Technologies
+ "^blokaslabs,.*":
+ description: Vilniaus Blokas UAB
"^blutek,.*":
description: BluTek Power
"^boe,.*":
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
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
--- /dev/null
+// 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 <linux/clk-provider.h>
+#include <linux/clk.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <linux/regmap.h>
+
+#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 <joerg@i2audio.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:clk-hifiberry-dachd");
--- /dev/null
+/*
+ * 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 <linux/clk-provider.h>
+#include <linux/clkdev.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+
+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 int clk_hifiberry_dacpro_remove(struct platform_device *pdev)
+{
+ of_clk_del_provider(pdev->dev.of_node);
+ return 0;
+}
+
+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");
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"
+ depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+ 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"
+ depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+ 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"
+ depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+ select SND_SOC_PCM5102A
+ select SND_RPI_SIMPLE_SOUNDCARD
+ help
+ Say Y or M if you want to add support for HifiBerry DAC.
+
+config SND_BCM2708_SOC_HIFIBERRY_DACPLUS
+ tristate "Support for HifiBerry DAC+"
+ depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+ 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"
+ depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+ 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"
+ depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+ 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"
+ depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+ 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"
+ depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+ 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"
+ depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+ 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"
+ depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+ 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"
+ depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+ 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"
+ depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+ 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"
+ depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+ 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"
+ depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+ 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"
+ depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+ 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"
+ depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+ 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"
+ depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+ 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"
+ depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+ 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"
+ depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+ 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"
+ depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+ 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"
+ depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+ 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"
+ depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+ 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"
+ depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+ 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"
+ depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+ 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"
+ depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+ 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"
+ depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+ 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"
+ depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+ 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"
+ depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+ 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"
+ depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+ 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"
+ depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+ 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"
+ depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+ 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"
+ depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+ 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 SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+ 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"
+ depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+ 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 SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+ 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"
+ depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+ 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"
+ depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+ 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"
+ depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+ select SND_SOC_TLV320AIC3X_I2C
+ help
+ Say Y or M if you want to add support for tlv320aic3x add-on
# BCM63XX Platform Support
snd-soc-63xx-objs := 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
--- /dev/null
+/*
+ * ALSA ASoC Machine Driver for Allo Boss DAC
+ *
+ * Author: Baswaraj K <jaikumar@cem-solutions.net>
+ * Copyright 2017
+ * based on code by Daniel Matuschek,
+ * Stuart MacLean <stuart@hifiberry.com>
+ * based on code by Florian Meier <florian.meier@koalo.de>
+ *
+ * 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 <linux/module.h>
+#include <linux/gpio/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#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 = asoc_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 = asoc_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 = asoc_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_physical_width(params_format(params));
+
+ if (snd_soc_allo_boss_master) {
+ struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
+
+ snd_allo_boss_set_sclk(component,
+ params_rate(params));
+
+ ret = snd_allo_boss_update_rate_den(
+ substream, params);
+ if (ret)
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_bclk_ratio(asoc_rtd_to_cpu(rtd, 0), channels * width);
+ if (ret)
+ return ret;
+ ret = snd_soc_dai_set_bclk_ratio(asoc_rtd_to_codec(rtd, 0), channels * width);
+ 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 = asoc_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 = asoc_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 int snd_allo_boss_remove(struct platform_device *pdev)
+{
+ snd_allo_boss_gpio_mute(&snd_allo_boss);
+ snd_soc_unregister_card(&snd_allo_boss);
+ return 0;
+}
+
+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 <jaikumar@cem-solutions.net>");
+MODULE_DESCRIPTION("ALSA ASoC Machine Driver for Allo Boss DAC");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+/*
+ * Driver for the ALLO KATANA CODEC
+ *
+ * Author: Jaikumar <sudeepkumar@cem-solutions.net>
+ * 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 <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/of_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <linux/of_gpio.h>
+#include <linux/regulator/consumer.h>
+#include <linux/pm_runtime.h>
+#include <linux/of_irq.h>
+#include <linux/completion.h>
+#include <linux/mutex.h>
+#include <linux/workqueue.h>
+#include <sound/jack.h>
+
+#include "../codecs/cs43130.h"
+
+#include <linux/clk.h>
+#include <linux/gcd.h>
+#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 <sudeepkumar@cem-solutions.net>");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+/*
+ * Driver for the ALLO KATANA CODEC
+ *
+ * Author: Jaikumar <jaikumar@cem-solutions.net>
+ * 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 <linux/init.h>
+#include <linux/module.h>
+#include <linux/clk.h>
+#include <linux/kernel.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/gcd.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+#include <linux/i2c.h>
+
+
+#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;
+}
+
+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 <jaikumar@cem-solutions.net>");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+/*
+ * ALSA ASoC Machine Driver for Allo Piano DAC Plus Subwoofer
+ *
+ * Author: Baswaraj K <jaikumar@cem-solutions.net>
+ * Copyright 2020
+ * based on code by David Knell <david.knell@gmail.com)
+ * based on code by Daniel Matuschek <info@crazy-audio.com>
+ * based on code by Florian Meier <florian.meier@koalo.de>
+ *
+ * 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 <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/gpio/consumer.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <linux/firmware.h>
+#include <linux/delay.h>
+#include <sound/tlv.h>
+#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(asoc_rtd_to_codec(rtd, 0)->component,
+ PCM512x_MUTE, P_DAC_UNMUTE);
+ snd_soc_component_write(asoc_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(asoc_rtd_to_codec(rtd, 0)->component,
+ PCM512x_MUTE, P_DAC_UNMUTE);
+ snd_soc_component_write(asoc_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(asoc_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(asoc_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(asoc_rtd_to_codec(rtd, 0)->component,
+ PCM512x_MUTE, P_DAC_RIGHT_MUTE);
+ snd_soc_component_write(asoc_rtd_to_codec(rtd, 1)->component,
+ PCM512x_MUTE, P_DAC_LEFT_MUTE);
+ snd_soc_component_write(asoc_rtd_to_codec(rtd, 0)->component,
+ PCM512x_DIGITAL_VOLUME_3, 0xff);
+ snd_soc_component_write(asoc_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(asoc_rtd_to_codec(rtd, 0)->component,
+ PCM512x_DIGITAL_VOLUME_2);
+ right_val = snd_soc_component_read(asoc_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(asoc_rtd_to_codec(rtd, 0)->component,
+ PCM512x_DIGITAL_VOLUME_3, left_val);
+ snd_soc_component_write(asoc_rtd_to_codec(rtd, 1)->component,
+ PCM512x_DIGITAL_VOLUME_2, right_val);
+ snd_soc_component_write(asoc_rtd_to_codec(rtd, 0)->component,
+ PCM512x_MUTE, P_DAC_UNMUTE);
+ snd_soc_component_write(asoc_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(asoc_rtd_to_codec(rtd, 0)->component,
+ PCM512x_DIGITAL_VOLUME_2);
+ right_val = snd_soc_component_read(asoc_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(asoc_rtd_to_codec(rtd, 0)->component,
+ PCM512x_DIGITAL_VOLUME_3, left_val);
+ snd_soc_component_write(asoc_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(asoc_rtd_to_codec(rtd, 1)->component,
+ PCM512x_DIGITAL_VOLUME_3);
+ if (glb_ptr->dual_mode != 1) {
+ left_val = snd_soc_component_read(asoc_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(asoc_rtd_to_codec(rtd, 1)->component,
+ PCM512x_DIGITAL_VOLUME_2, (~left_val));
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_component_write(asoc_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(asoc_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(asoc_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(asoc_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(asoc_rtd_to_codec(rtd, 1)->component,
+ PCM512x_DIGITAL_VOLUME_3);
+ } else {
+ right_val = snd_soc_component_read(asoc_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(asoc_rtd_to_codec(rtd, 0)->component,
+ PCM512x_DIGITAL_VOLUME_2, (~left_val));
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_component_write(asoc_rtd_to_codec(rtd, 1)->component,
+ PCM512x_DIGITAL_VOLUME_3, (~right_val));
+ if (ret < 0)
+ return ret;
+
+ } else {
+
+ ret = snd_soc_component_write(asoc_rtd_to_codec(rtd, 0)->component,
+ PCM512x_DIGITAL_VOLUME_2, (~left_val));
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_component_write(asoc_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(asoc_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(asoc_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(asoc_rtd_to_codec(rtd, 0)->component, PCM512x_MUTE,
+ ~((left_val & 0x01)<<4));
+ if (ret < 0)
+ return ret;
+ ret = snd_soc_component_write(asoc_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(asoc_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(asoc_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(asoc_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 = asoc_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(asoc_rtd_to_codec(rtd, dac)->component,
+ PCM512x_RATE_DET_4);
+
+ if (val & 0x40) {
+ snd_soc_component_write(asoc_rtd_to_codec(rtd, dac)->component,
+ PCM512x_PLL_REF,
+ PCM512x_SREF_BCK);
+
+ dev_info(asoc_rtd_to_codec(rtd, dac)->component->dev,
+ "Setting BCLK as input clock & Enable PLL\n");
+ } else {
+ snd_soc_component_write(asoc_rtd_to_codec(rtd, dac)->component,
+ PCM512x_PLL_EN,
+ 0x00);
+
+ snd_soc_component_write(asoc_rtd_to_codec(rtd, dac)->component,
+ PCM512x_PLL_REF,
+ PCM512x_SREF_SCK);
+
+ dev_info(asoc_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 int 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);
+ return 0;
+}
+
+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 <jaikumar@cem-solutions.net>");
+MODULE_DESCRIPTION("ALSA ASoC Machine Driver for Allo Piano DAC Plus");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+/*
+ * ALSA ASoC Machine Driver for Allo Piano DAC
+ *
+ * Author: Baswaraj K <jaikumar@cem-solutions.net>
+ * Copyright 2016
+ * based on code by Daniel Matuschek <info@crazy-audio.com>
+ * based on code by Florian Meier <florian.meier@koalo.de>
+ *
+ * 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 <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+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 <jaikumar@cem-solutions.net>");
+MODULE_DESCRIPTION("ALSA ASoC Machine Driver for Allo Piano DAC");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+/*
+ * 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 <linux/module.h>
+#include <linux/types.h>
+#include <linux/gpio/consumer.h>
+
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+#include <sound/control.h>
+
+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(asoc_rtd_to_codec(rtd, 0), 0, 24576000, 0);
+ if (ret)
+ return ret;
+
+ return snd_soc_dai_set_bclk_ratio(asoc_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",
+ .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 <flatmax@flatmax.org>");
+MODULE_DESCRIPTION("AudioInjector.net isolated Soundcard");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:audioinjector-isolated-soundcard");
--- /dev/null
+/*
+ * 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 <linux/module.h>
+#include <linux/types.h>
+#include <linux/gpio/consumer.h>
+
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+#include <sound/control.h>
+
+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(asoc_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;
+ asoc_rtd_to_cpu(rtd, 0)->driver->playback.channels_min = 8;
+ asoc_rtd_to_cpu(rtd, 0)->driver->playback.channels_max = 8;
+ asoc_rtd_to_cpu(rtd, 0)->driver->capture.channels_min = 8;
+ asoc_rtd_to_cpu(rtd, 0)->driver->capture.channels_max = 8;
+ asoc_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;
+ asoc_rtd_to_cpu(rtd, 0)->driver->playback.channels_min = 2;
+ asoc_rtd_to_cpu(rtd, 0)->driver->playback.channels_max = 2;
+ asoc_rtd_to_cpu(rtd, 0)->driver->capture.channels_min = 2;
+ asoc_rtd_to_cpu(rtd, 0)->driver->capture.channels_max = 2;
+ asoc_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(asoc_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(asoc_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(asoc_rtd_to_codec(rtd, 0), 0, 49152000,
+ 0);
+ break;
+ case 24000:
+ return snd_soc_dai_set_sysclk(asoc_rtd_to_codec(rtd, 0), 0, 49152000/2,
+ 0);
+ break;
+ case 32000:
+ case 16000:
+ return snd_soc_dai_set_sysclk(asoc_rtd_to_codec(rtd, 0), 0, 49152000/3,
+ 0);
+ break;
+ case 8000:
+ return snd_soc_dai_set_sysclk(asoc_rtd_to_codec(rtd, 0), 0, 49152000/6,
+ 0);
+ break;
+ case 88200:
+ case 44100:
+ return snd_soc_dai_set_sysclk(asoc_rtd_to_codec(rtd, 0), 0, 45185400,
+ 0);
+ break;
+ case 22050:
+ return snd_soc_dai_set_sysclk(asoc_rtd_to_codec(rtd, 0), 0, 45185400/2,
+ 0);
+ break;
+ case 29400:
+ case 14700:
+ return snd_soc_dai_set_sysclk(asoc_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",
+ .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 <flatmax@flatmax.org>");
+MODULE_DESCRIPTION("AudioInjector.net octo Soundcard");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:audioinjector-octo-soundcard");
--- /dev/null
+/*
+ * ASoC Driver for AudioInjector Pi add on soundcard
+ *
+ * Created on: 13-May-2016
+ * Author: flatmax@flatmax.org
+ * based on code by Cliff Cai <Cliff.Cai@analog.com> 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 <linux/module.h>
+#include <linux/types.h>
+
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+#include <sound/control.h>
+
+#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 = asoc_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(asoc_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",
+ .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 <flatmax@flatmax.org>");
+MODULE_DESCRIPTION("AudioInjector.net Pi Soundcard");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:audioinjector-pi-soundcard");
+
--- /dev/null
+/*
+ * ASoC Driver for AudioSense add on soundcard
+ * Author:
+ * Bhargav A K <anur.bhargav@gmail.com>
+ * 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 <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/i2c.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include <sound/control.h>
+
+#include <sound/tlv320aic32x4.h>
+#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 = asoc_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 = asoc_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 int audiosense_pi_card_remove(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = platform_get_drvdata(pdev);
+
+ snd_soc_unregister_card(card);
+ return 0;
+}
+
+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 <anur.bhargav@gmail.com>");
+MODULE_DESCRIPTION("ASoC Driver for TLV320AIC3204 Audio");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:audiosense-pi");
+
#include <linux/init.h>
#include <linux/io.h>
#include <linux/module.h>
-#include <linux/of_address.h>
#include <linux/slab.h>
#include <sound/core.h>
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);
"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);
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 =
--- /dev/null
+/*
+ * ASoC Driver for ChipDip DAC
+ *
+ * Author: Evgenij Sapunov
+ * Copyright 2021
+ * based on code by Milan Neskovic <info@justboom.co>
+ * based on code by Jaikumar <jaikumar@cem-solutions.net>
+ *
+ * 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 <linux/module.h>
+#include <linux/gpio/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#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(asoc_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 <evgenij.sapunov@chipdip.ru>");
+MODULE_DESCRIPTION("ASoC Driver for ChipDip DAC");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+/*
+ * ASoC Driver for Dacberry400 soundcard
+ * Author:
+ * Ashish Vara<ashishhvara@gmail.com>
+ * 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 <linux/module.h>
+#include <linux/gpio/consumer.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include <linux/i2c.h>
+#include <linux/acpi.h>
+#include <linux/slab.h>
+#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 = asoc_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 = asoc_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 = asoc_rtd_to_codec(rtd, 0);
+ struct snd_soc_dai *cpu_dai = asoc_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 int snd_rpi_dacberry400_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_card(&snd_rpi_dacberry400);
+ return 0;
+}
+
+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");
--- /dev/null
+/*
+ * ASoC Driver for RRA DigiDAC1
+ * Copyright 2016
+ * Author: José M. Tasende <vintage@redrocksaudio.es>
+ * based on the HifiBerry DAC driver by Florian Meier <florian.meier@koalo.de>
+ * and the Wolfson card driver by Nikesh Oswal, <Nikesh.Oswal@wolfsonmicro.com>
+ * 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 <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include <sound/soc-dapm.h>
+#include <sound/tlv.h>
+#include <linux/regulator/consumer.h>
+
+#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 = asoc_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 = asoc_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 = asoc_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 = asoc_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 = asoc_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 = asoc_rtd_to_codec(rtd, 0);
+ struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
+ struct snd_soc_dai *cpu_dai = asoc_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 = asoc_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 <vintage@redrocksaudio.es>");
+MODULE_DESCRIPTION("ASoC Driver for RRA DigiDAC1");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+/*
+ * ASoC Driver for Dion Audio LOCO-V2 DAC-AMP
+ *
+ * Author: Miquel Blauw <info@dionaudio.nl>
+ * 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 <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+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",
+ .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 <info@dionaudio.nl>");
+MODULE_DESCRIPTION("ASoC Driver for DionAudio LOCO-V2");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+/*
+ * ASoC Driver for Dion Audio LOCO DAC-AMP
+ *
+ * Author: Miquel Blauw <info@dionaudio.nl>
+ * 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 <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+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 = asoc_rtd_to_cpu(rtd, 0);
+
+ unsigned int sample_bits =
+ snd_pcm_format_physical_width(params_format(params));
+
+ 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",
+ .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 <info@dionaudio.nl>");
+MODULE_DESCRIPTION("ASoC Driver for DionAudio LOCO");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+/*
+ * ASoC Driver for Fe-Pi Audio Sound Card
+ *
+ * Author: Henry Kupis <kuupaz@gmail.com>
+ * Copyright 2016
+ * based on code by Florian Meier <florian.meier@koalo.de>
+ * based on code by Shawn Guo <shawn.guo@linaro.org>
+ *
+ * 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 <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#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 = asoc_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 = asoc_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 <fe-pi@cox.net>");
+MODULE_DESCRIPTION("ASoC Driver for Fe-Pi Audio");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+/*
+ * Driver for the Google voiceHAT audio codec for Raspberry Pi.
+ *
+ * Author: Peter Malkin <petermalkin@google.com>
+ * 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 <linux/device.h>
+#include <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/version.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+#include <sound/soc-dapm.h>
+
+#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 int voicehat_platform_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_component(&pdev->dev);
+ return 0;
+}
+
+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 <petermalkin@google.com>");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+/*
+ * ASoC Driver for HiFiBerry DAC+ / DAC Pro / AMP100
+ *
+ * Author: Daniel Matuschek, Stuart MacLean <stuart@hifiberry.com>
+ * Copyright 2014-2015
+ * based on code by Florian Meier <florian.meier@koalo.de>
+ * Headphone/AMP100 Joerg Schambacher <joerg@hifiberry.com>
+ *
+ * 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 <linux/module.h>
+#include <linux/gpio/consumer.h>
+#include <../drivers/gpio/gpiolib.h>
+#include <linux/platform_device.h>
+#include <linux/kernel.h>
+#include <linux/clk.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#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 struct gpio_desc *snd_mute_gpio;
+static struct gpio_desc *snd_reset_gpio;
+static struct snd_soc_card snd_rpi_hifiberry_dacplus;
+
+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 = asoc_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;
+
+ 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 = asoc_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 = 32;
+
+ if (snd_rpi_hifiberry_is_dacpro) {
+ struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
+
+ width = snd_pcm_format_physical_width(params_format(params));
+
+ 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(asoc_rtd_to_cpu(rtd, 0), channels * width);
+ if (ret)
+ return ret;
+ ret = snd_soc_dai_set_bclk_ratio(asoc_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 = asoc_rtd_to_codec(rtd, 0)->component;
+
+ 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 = asoc_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 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 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;
+ }
+ }
+ }
+
+ 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 <daniel@hifiberry.com>");
+MODULE_DESCRIPTION("ASoC Driver for HiFiBerry DAC+");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+/*
+ * ASoC Driver for HiFiBerry DAC+ / DAC Pro with ADC
+ *
+ * Author: Daniel Matuschek, Stuart MacLean <stuart@hifiberry.com>
+ * Copyright 2014-2015
+ * based on code by Florian Meier <florian.meier@koalo.de>
+ * ADC added by Joerg Schambacher <joscha@schambacher.com>
+ * 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 <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/kernel.h>
+#include <linux/clk.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#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 = asoc_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 = asoc_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 = 32;
+
+ if (snd_rpi_hifiberry_is_dacpro) {
+ struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
+
+ width = snd_pcm_format_physical_width(params_format(params));
+
+ 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(asoc_rtd_to_cpu(rtd, 0), channels * width);
+ if (ret)
+ return ret;
+ ret = snd_soc_dai_set_bclk_ratio(asoc_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 = asoc_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 = asoc_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 <joscha@schambacher.com>");
+MODULE_AUTHOR("Daniel Matuschek <daniel@hifiberry.com>");
+MODULE_DESCRIPTION("ASoC Driver for HiFiBerry DAC+ADC");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+/*
+ * ASoC Driver for HiFiBerry DAC+ / DAC Pro with ADC PRO Version (SW control)
+ *
+ * Author: Daniel Matuschek, Stuart MacLean <stuart@hifiberry.com>
+ * Copyright 2014-2015
+ * based on code by Florian Meier <florian.meier@koalo.de>
+ * ADC, HP added by Joerg Schambacher <joerg@hifiberry.com>
+ * 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 <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/kernel.h>
+#include <linux/clk.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include <sound/tlv.h>
+
+#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 = asoc_rtd_to_codec(rtd, 0)->component;
+ struct snd_soc_component *adc = asoc_rtd_to_codec(rtd, 1)->component;
+ struct snd_soc_dai_driver *adc_driver = asoc_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(asoc_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(asoc_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(asoc_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 = asoc_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 = 32;
+ struct snd_soc_component *dac = asoc_rtd_to_codec(rtd, 0)->component;
+ struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0);
+ struct snd_soc_dai_driver *drv = dai->driver;
+ const struct snd_soc_dai_ops *ops = drv->ops;
+
+ if (snd_rpi_hifiberry_is_dacpro) {
+ width = snd_pcm_format_physical_width(params_format(params));
+
+ 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(asoc_rtd_to_cpu(rtd, 0), channels * width);
+ if (ret)
+ return ret;
+ ret = snd_soc_dai_set_bclk_ratio(asoc_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 = asoc_rtd_to_codec(rtd, 0)->component;
+ struct snd_soc_component *adc = asoc_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 = asoc_rtd_to_codec(rtd, 0)->component;
+ struct snd_soc_component *adc = asoc_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 <joerg@hifiberry.com>");
+MODULE_AUTHOR("Daniel Matuschek <daniel@hifiberry.com>");
+MODULE_DESCRIPTION("ASoC Driver for HiFiBerry DAC+ADC");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ASoC Driver for HiFiBerry DAC + DSP
+ *
+ * Author: Joerg Schambacher <joscha@schambacher.com>
+ * 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 <linux/init.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <sound/soc.h>
+
+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 int dacplusdsp_platform_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_component(&pdev->dev);
+ return 0;
+}
+
+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 <joerg@i2audio.com>");
+MODULE_DESCRIPTION("ASoC Driver for HiFiBerry DAC+DSP");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+// 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 <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <linux/i2c.h>
+#include <linux/clk.h>
+
+#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 = asoc_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 = asoc_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 int snd_rpi_hifiberry_dacplushd_remove(struct platform_device *pdev)
+{
+ if (IS_ERR(reset_gpio))
+ return -EINVAL;
+
+ /* put DAC into RESET and release GPIO */
+ gpiod_set_value(reset_gpio, 0);
+ gpiod_put(reset_gpio);
+
+ return 0;
+}
+
+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 <joerg@i2audio.com>");
+MODULE_DESCRIPTION("ASoC Driver for HiFiBerry DAC+ HD");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+/*
+ * 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 <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/fs.h>
+#include <asm/uaccess.h>
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#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 = asoc_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 = asoc_rtd_to_cpu(rtd, 0);
+ int bclk_ratio;
+
+ bclk_ratio = snd_pcm_format_physical_width(
+ params_format(params)) * 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 int snd_rpi_i_sabre_q2m_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_card(&snd_rpi_i_sabre_q2m);
+ return 0;
+}
+
+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 <http://www.audiophonics.fr>");
+MODULE_LICENSE("GPL");
--- /dev/null
+/*
+ * ASoC Driver for IQaudIO Raspberry Pi Codec board
+ *
+ * Author: Gordon Garrity <gordon@iqaudio.com>
+ * (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 <linux/module.h>
+#include <linux/gpio/consumer.h>
+#include <linux/platform_device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#include <linux/acpi.h>
+#include <linux/slab.h>
+#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 = asoc_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 = asoc_rtd_to_codec(rtd, 0);
+ struct snd_soc_dai *cpu_dai = asoc_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);
+
+ /* Set bclk ratio to align with codec's BCLK rate */
+ 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;
+ }
+
+ /* 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 = asoc_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 int snd_rpi_iqaudio_codec_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_card(&snd_rpi_iqaudio_codec);
+ return 0;
+}
+
+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 <gordon@iqaudio.com>");
+MODULE_DESCRIPTION("ASoC Driver for IQaudIO CODEC");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+/*
+ * ASoC Driver for IQaudIO DAC
+ *
+ * Author: Florian Meier <florian.meier@koalo.de>
+ * 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 <linux/module.h>
+#include <linux/gpio/consumer.h>
+#include <linux/platform_device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+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 = asoc_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 int 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);
+ return 0;
+}
+
+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 <florian.meier@koalo.de>");
+MODULE_DESCRIPTION("ASoC Driver for IQAudio DAC");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * rpi--wm8804.c -- ALSA SoC Raspberry Pi soundcard.
+ *
+ * Authors: Johannes Krude <johannes@krude.de
+ *
+ * Driver for when connecting simultaneously justboom-digi and justboom-dac
+ *
+ * Based upon code from:
+ * justboom-digi.c
+ * by Milan Neskovic <info@justboom.co>
+ * justboom-dac.c
+ * by Milan Neskovic <info@justboom.co>
+ *
+ * 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 <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#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 = asoc_rtd_to_codec(rtd, 0)->component;
+ struct snd_soc_component *dac = asoc_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 = asoc_rtd_to_codec(rtd, 0);
+ struct snd_soc_component *digi = asoc_rtd_to_codec(rtd, 0)->component;
+ struct snd_soc_dai *cpu_dai = asoc_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 = asoc_rtd_to_codec(rtd, 0)->component;
+ struct snd_soc_component *dac = asoc_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 = asoc_rtd_to_codec(rtd, 0)->component;
+ struct snd_soc_component *dac = asoc_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 int snd_rpi_justboom_both_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_card(&snd_rpi_justboom_both);
+ return 0;
+}
+
+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 <johannes@krude.de>");
+MODULE_DESCRIPTION("ASoC Driver for simultaneous use of JustBoom PI Digi & DAC HAT Sound Cards");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+/*
+ * ASoC Driver for JustBoom DAC Raspberry Pi HAT Sound Card
+ *
+ * Author: Milan Neskovic
+ * Copyright 2016
+ * based on code by Daniel Matuschek <info@crazy-audio.com>
+ * based on code by Florian Meier <florian.meier@koalo.de>
+ *
+ * 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 <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#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 = asoc_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 = asoc_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 = asoc_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 <info@justboom.co>");
+MODULE_DESCRIPTION("ASoC Driver for JustBoom PI DAC HAT Sound Card");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ALSA ASoC Machine Driver for PiFi-40
+ *
+ * Author: David Knell <david.knell@gmail.com)
+ * based on code by Daniel Matuschek <info@crazy-audio.com>
+ * based on code by Florian Meier <florian.meier@koalo.de>
+ * 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 <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/gpio/consumer.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <linux/firmware.h>
+#include <linux/delay.h>
+#include <sound/tlv.h>
+
+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] = asoc_rtd_to_codec(rtd, 0)->component;
+ dac[1] = asoc_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] = asoc_rtd_to_codec(rtd, 0)->component;
+ dac[1] = asoc_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 = asoc_rtd_to_cpu(rtd, 0);
+ unsigned int sample_bits;
+
+ sample_bits = snd_pcm_format_physical_width(params_format(params));
+ 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 int 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);
+ return 0;
+}
+
+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 <david.knell@gmail.com>");
+MODULE_DESCRIPTION("ALSA ASoC Machine Driver for PiFi-40");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+/*
+ * Pisound Linux kernel module.
+ * Copyright (C) 2016-2020 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 <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+#include <linux/kobject.h>
+#include <linux/sysfs.h>
+#include <linux/delay.h>
+#include <linux/spi/spi.h>
+#include <linux/interrupt.h>
+#include <linux/kfifo.h>
+#include <linux/jiffies.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include <sound/rawmidi.h>
+#include <sound/asequencer.h>
+#include <sound/control.h>
+
+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 struct snd_rawmidi_ops pisnd_output_ops = {
+ .open = pisnd_output_open,
+ .close = pisnd_output_close,
+ .trigger = pisnd_output_trigger,
+ .drain = pisnd_output_drain,
+};
+
+static 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 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 = 150000;
+ 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));
+
+ 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 struct gpio_desc *button;
+
+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 = asoc_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_physical_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 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);
+
+ button = gpiod_get_index(dev, "button", 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);
+
+ gpiod_export(button, false);
+
+ return 0;
+}
+
+static int pisnd_uninit_gpio(void)
+{
+ int i;
+
+ struct gpio_desc **gpios[] = {
+ &osr0, &osr1, &osr2, &reset, &button,
+ };
+
+ gpiod_unexport(button);
+
+ 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 int 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);
+ return 0;
+}
+
+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 <giedrius@blokas.io>");
+MODULE_DESCRIPTION("ASoC Driver for Pisound, https://blokas.io/pisound");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+/*
+ * ASoC machine driver for Cirrus Logic Audio Card
+ * (with WM5102 and WM8804 codecs)
+ *
+ * Copyright 2015-2017 Matthias Reichl <hias@horus.com>
+ *
+ * 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 <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/list.h>
+#include <linux/delay.h>
+#include <sound/pcm_params.h>
+
+#include <linux/mfd/arizona/registers.h>
+
+#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 =
+ asoc_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 =
+ asoc_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 =
+ asoc_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 =
+ asoc_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 =
+ asoc_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 =
+ asoc_rtd_to_codec(wm5102_runtime, 0)->component;
+
+ int ret = 0;
+ unsigned int clk_freq;
+
+ if (dapm->dev != asoc_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 =
+ asoc_rtd_to_codec(wm5102_runtime, 0)->component;
+
+ if (dapm->dev != asoc_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 = asoc_rtd_to_cpu(rtd, 0);
+ struct snd_soc_component *wm5102_component = asoc_rtd_to_codec(rtd, 0)->component;
+ struct snd_soc_dai *wm8804_dai = asoc_rtd_to_codec(get_wm8804_runtime(card), 0);
+
+ int ret;
+
+ unsigned int width = snd_pcm_format_physical_width(
+ params_format(params));
+ unsigned int rate = params_rate(params);
+ unsigned int clk_freq = calc_sysclk(rate);
+
+ 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(asoc_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 = asoc_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 = asoc_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 = asoc_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(
+ asoc_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(
+ asoc_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 <hias@horus.com>");
+MODULE_DESCRIPTION("ASoC driver for Cirrus Logic Audio Card");
+MODULE_LICENSE("GPL");
--- /dev/null
+/*
+ * ASoC driver for PROTO AudioCODEC (with a WM8731)
+ * connected to a Raspberry Pi
+ *
+ * Author: Florian Meier, <koalo@koalo.de>
+ * 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 <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#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 = asoc_rtd_to_codec(rtd, 0);
+ struct snd_soc_dai *cpu_dai = asoc_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");
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * rpi-simple-soundcard.c -- ALSA SoC Raspberry Pi soundcard.
+ *
+ * Copyright (C) 2018 Raspberry Pi.
+ *
+ * Authors: Tim Gover <tim.gover@raspberrypi.org>
+ *
+ * Based on code:
+ * hifiberry_amp.c, hifiberry_dac.c, rpi-dac.c
+ * by Florian Meier <florian.meier@koalo.de>
+ *
+ * googlevoicehat-soundcard.c
+ * by Peter Malkin <petermalkin@google.com>
+ *
+ * adau1977-adc.c
+ * by Andrey Grodzovsky <andrey2805@gmail.com>
+ *
+ * merus-amp.c
+ * by Ariel Muszkat <ariel.muszkat@gmail.com>
+ * Jorgen Kragh Jakobsen <jorgen.kraghjakobsen@infineon.com>
+ *
+ * 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 <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/gpio/consumer.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+/* 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 = asoc_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 = asoc_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 = asoc_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. More complex drivers could just replace
+ * the hw_params routine.
+ */
+ sample_bits = snd_pcm_format_physical_width(params_format(params));
+ 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 = asoc_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 = asoc_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,
+};
+
+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 = "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 <tim.gover@raspberrypi.org>");
+MODULE_DESCRIPTION("ASoC Raspberry Pi simple soundcard driver ");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * rpi--wm8804.c -- ALSA SoC Raspberry Pi soundcard.
+ *
+ * Copyright (C) 2018 Raspberry Pi.
+ *
+ * Authors: Tim Gover <tim.gover@raspberrypi.org>
+ *
+ * Generic driver for Pi Hat WM8804 digi sounds cards
+ *
+ * Based upon code from:
+ * justboom-digi.c
+ * by Milan Neskovic <info@justboom.co>
+ *
+ * iqaudio_digi.c
+ * by Daniel Matuschek <info@crazy-audio.com>
+ *
+ * allo-digione.c
+ * by Baswaraj <jaikumar@cem-solutions.net>
+ *
+ * hifiberry-digi.c
+ * Daniel Matuschek <info@crazy-audio.com>
+ *
+ * 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 <linux/gpio/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/module.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#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;
+
+/* 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 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 = asoc_rtd_to_codec(rtd, 0);
+ struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
+ struct snd_soc_dai *cpu_dai = asoc_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,
+};
+
+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,
+};
+
+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 },
+ {},
+};
+
+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 <tim.gover@raspberrypi.org>");
+MODULE_DESCRIPTION("ASoC Raspberry Pi Hat generic digi driver for WM8804 based cards");
+MODULE_LICENSE("GPL v2");
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
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
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
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
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
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
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
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
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
snd-soc-idt821034-objs := idt821034.o
snd-soc-inno-rk3036-objs := inno_rk3036.o
snd-soc-isabelle-objs := isabelle.o
+snd-soc-i-sabre-codec-objs := i-sabre-codec.o
snd-soc-jz4740-codec-objs := jz4740.o
snd-soc-jz4725b-codec-objs := jz4725b.o
snd-soc-jz4760-codec-objs := jz4760.o
snd-soc-lpass-tx-macro-objs := lpass-tx-macro.o
snd-soc-lpass-wsa-macro-objs := lpass-wsa-macro.o
snd-soc-lpass-va-macro-objs := lpass-va-macro.o
+snd-soc-ma120x0p-objs := ma120x0p.o
snd-soc-madera-objs := madera.o
snd-soc-max9759-objs := max9759.o
snd-soc-max9768-objs := max9768.o
snd-soc-pcm186x-objs := pcm186x.o
snd-soc-pcm186x-i2c-objs := pcm186x-i2c.o
snd-soc-pcm186x-spi-objs := pcm186x-spi.o
+snd-soc-pcm1794a-objs := pcm1794a.o
snd-soc-pcm3008-objs := pcm3008.o
snd-soc-pcm3060-objs := pcm3060.o
snd-soc-pcm3060-i2c-objs := pcm3060-i2c.o
snd-soc-stac9766-objs := stac9766.o
snd-soc-sti-sas-objs := sti-sas.o
snd-soc-tas5086-objs := tas5086.o
+snd-soc-tas5713-objs := tas5713.o
snd-soc-tas571x-objs := tas571x.o
snd-soc-tas5720-objs := tas5720.o
snd-soc-tas5805m-objs := tas5805m.o
obj-$(CONFIG_SND_SOC_IDT821034) += snd-soc-idt821034.o
obj-$(CONFIG_SND_SOC_INNO_RK3036) += snd-soc-inno-rk3036.o
obj-$(CONFIG_SND_SOC_ISABELLE) += snd-soc-isabelle.o
+obj-$(CONFIG_SND_SOC_I_SABRE_CODEC) += snd-soc-i-sabre-codec.o
obj-$(CONFIG_SND_SOC_JZ4740_CODEC) += snd-soc-jz4740-codec.o
obj-$(CONFIG_SND_SOC_JZ4725B_CODEC) += snd-soc-jz4725b-codec.o
obj-$(CONFIG_SND_SOC_JZ4760_CODEC) += snd-soc-jz4760-codec.o
obj-$(CONFIG_SND_SOC_LM4857) += snd-soc-lm4857.o
obj-$(CONFIG_SND_SOC_LM49453) += snd-soc-lm49453.o
obj-$(CONFIG_SND_SOC_LOCHNAGAR_SC) += snd-soc-lochnagar-sc.o
+obj-$(CONFIG_SND_SOC_MA120X0P) += snd-soc-ma120x0p.o
obj-$(CONFIG_SND_SOC_MADERA) += snd-soc-madera.o
obj-$(CONFIG_SND_SOC_MAX9759) += snd-soc-max9759.o
obj-$(CONFIG_SND_SOC_MAX9768) += snd-soc-max9768.o
obj-$(CONFIG_SND_SOC_PCM179X) += snd-soc-pcm179x-codec.o
obj-$(CONFIG_SND_SOC_PCM1789_I2C) += snd-soc-pcm1789-i2c.o
obj-$(CONFIG_SND_SOC_PCM1789) += snd-soc-pcm1789-codec.o
+obj-$(CONFIG_SND_SOC_PCM1794A) += snd-soc-pcm1794a.o
obj-$(CONFIG_SND_SOC_PCM179X_I2C) += snd-soc-pcm179x-i2c.o
obj-$(CONFIG_SND_SOC_PCM179X_SPI) += snd-soc-pcm179x-spi.o
obj-$(CONFIG_SND_SOC_PCM186X) += snd-soc-pcm186x.o
obj-$(CONFIG_SND_SOC_TAS6424) += snd-soc-tas6424.o
obj-$(CONFIG_SND_SOC_TDA7419) += snd-soc-tda7419.o
obj-$(CONFIG_SND_SOC_TAS2770) += snd-soc-tas2770.o
+obj-$(CONFIG_SND_SOC_TAS5713) += snd-soc-tas5713.o
obj-$(CONFIG_SND_SOC_TFA9879) += snd-soc-tfa9879.o
obj-$(CONFIG_SND_SOC_TFA989X) += snd-soc-tfa989x.o
obj-$(CONFIG_SND_SOC_TLV320ADC3XXX) += snd-soc-tlv320adc3xxx.o
};
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,
};
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,
};
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;
--- /dev/null
+/*
+ * 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 <linux/init.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/i2c.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+
+#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 <http://www.audiophonics.fr>");
+MODULE_LICENSE("GPL");
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ASoC Driver for Infineon Merus(TM) ma120x0p multi-level class-D amplifier
+ *
+ * Authors: Ariel Muszkat <ariel.muszkat@gmail.com>
+ * Jorgen Kragh Jakobsen <jorgen.kraghjakobsen@infineon.com>
+ *
+ * Copyright (C) 2019 Infineon Technologies AG
+ *
+ */
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm_runtime.h>
+#include <linux/i2c.h>
+#include <linux/of_device.h>
+#include <linux/spi/spi.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <linux/gpio/consumer.h>
+#include <linux/gpio.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <linux/interrupt.h>
+
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/fs.h>
+#include <linux/uaccess.h>
+
+#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");
--- /dev/null
+/*
+ * Driver for the PCM1794A codec
+ *
+ * Author: Florian Meier <florian.meier@koalo.de>
+ * 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 <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <sound/soc.h>
+
+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 int pcm1794a_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_component(&pdev->dev);
+ return 0;
+}
+
+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 <florian.meier@koalo.de>");
+MODULE_LICENSE("GPL v2");
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 = {
--- /dev/null
+/*
+ * ASoC Driver for TAS5713
+ *
+ * Author: Sebastian Eickhoff <basti.eickhoff@googlemail.com>
+ * 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 <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/of_device.h>
+#include <linux/spi/spi.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/fs.h>
+#include <asm/uaccess.h>
+
+#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 <basti.eickhoff@googlemail.com>");
+MODULE_DESCRIPTION("ASoC driver for TAS5713");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+/*
+ * ASoC Driver for TAS5713
+ *
+ * Author: Sebastian Eickhoff <basti.eickhoff@googlemail.com>
+ * 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 */
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;
}
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
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 */