aboutsummaryrefslogtreecommitdiffstats
path: root/target/linux/bcm27xx/patches-5.4/950-0071-Support-for-Blokas-Labs-pisound-board.patch
diff options
context:
space:
mode:
Diffstat (limited to 'target/linux/bcm27xx/patches-5.4/950-0071-Support-for-Blokas-Labs-pisound-board.patch')
-rw-r--r--target/linux/bcm27xx/patches-5.4/950-0071-Support-for-Blokas-Labs-pisound-board.patch1752
1 files changed, 1752 insertions, 0 deletions
diff --git a/target/linux/bcm27xx/patches-5.4/950-0071-Support-for-Blokas-Labs-pisound-board.patch b/target/linux/bcm27xx/patches-5.4/950-0071-Support-for-Blokas-Labs-pisound-board.patch
new file mode 100644
index 0000000000..88346102b7
--- /dev/null
+++ b/target/linux/bcm27xx/patches-5.4/950-0071-Support-for-Blokas-Labs-pisound-board.patch
@@ -0,0 +1,1752 @@
+From ed992c4a8392b757e54b60bf2390015b72e3e947 Mon Sep 17 00:00:00 2001
+From: gtrainavicius <gtrainavicius@users.noreply.github.com>
+Date: Sun, 23 Oct 2016 12:06:53 +0300
+Subject: [PATCH] Support for Blokas Labs pisound board
+
+Pisound dynamic overlay (#1760)
+
+Restructuring pisound-overlay.dts, so it can be loaded and unloaded dynamically using dtoverlay.
+
+Print a logline when the kernel module is removed.
+
+pisound improvements:
+
+* Added a writable sysfs object to enable scripts / user space software
+to blink MIDI activity LEDs for variable duration.
+* Improved hw_param constraints setting.
+* Added compatibility with S16_LE sample format.
+* Exposed some simple placeholder volume controls, so the card appears
+in volumealsa widget.
+
+Add missing SND_PISOUND selects dependency to SND_RAWMIDI
+
+Without it the Pisound module fails to compile.
+See https://github.com/raspberrypi/linux/issues/2366
+
+Updates for Pisound module code:
+
+ * Merged 'Fix a warning in DEBUG builds' (1c8b82b).
+ * Updating some strings and copyright information.
+ * Fix for handling high load of MIDI input and output.
+ * Use dual rate oversampling ratio for 96kHz instead of single
+ rate one.
+
+Signed-off-by: Giedrius Trainavicius <giedrius@blokas.io>
+
+Fixing memset call in pisound.c
+
+Signed-off-by: Giedrius Trainavicius <giedrius@blokas.io>
+
+Fix for Pisound's MIDI Input getting blocked for a while in rare cases.
+
+There was a possible race condition which could lead to Input's FIFO queue
+to be underflown, causing high amount of processing in the worker thread for
+some period of time.
+
+Signed-off-by: Giedrius Trainavicius <giedrius@blokas.io>
+
+Fix for Pisound kernel module in Real Time kernel configuration.
+
+When handler of data_available interrupt is fired, queue_work ends up
+getting called and it can block on a spin lock which is not allowed in
+interrupt context. The fix was to run the handler from a thread context
+instead.
+
+Pisound: Remove spinlock usage around spi_sync
+
+ASoC: pisound: use modern dai_link style
+
+Signed-off-by: Hui Wang <hui.wang@canonical.com>
+
+ASoC: pisound: fix the parameter for spi_device_match
+
+Signed-off-by: Hui Wang <hui.wang@canonical.com>
+---
+ .../devicetree/bindings/vendor-prefixes.txt | 463 +++++++
+ .../devicetree/bindings/vendor-prefixes.yaml | 2 +
+ sound/soc/bcm/pisound.c | 1201 +++++++++++++++++
+ 3 files changed, 1666 insertions(+)
+ create mode 100644 Documentation/devicetree/bindings/vendor-prefixes.txt
+ create mode 100644 sound/soc/bcm/pisound.c
+
+--- /dev/null
++++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
+@@ -0,0 +1,463 @@
++Device tree binding vendor prefix registry. Keep list in alphabetical order.
++
++This isn't an exhaustive list, but you should add new prefixes to it before
++using them to avoid name-space collisions.
++
++abilis Abilis Systems
++abracon Abracon Corporation
++actions Actions Semiconductor Co., Ltd.
++active-semi Active-Semi International Inc
++ad Avionic Design GmbH
++adafruit Adafruit Industries, LLC
++adapteva Adapteva, Inc.
++adaptrum Adaptrum, Inc.
++adh AD Holdings Plc.
++adi Analog Devices, Inc.
++advantech Advantech Corporation
++aeroflexgaisler Aeroflex Gaisler AB
++al Annapurna Labs
++allo Allo.com
++allwinner Allwinner Technology Co., Ltd.
++alphascale AlphaScale Integrated Circuits Systems, Inc.
++altr Altera Corp.
++amarula Amarula Solutions
++amazon Amazon.com, Inc.
++amcc Applied Micro Circuits Corporation (APM, formally AMCC)
++amd Advanced Micro Devices (AMD), Inc.
++amediatech Shenzhen Amediatech Technology Co., Ltd
++amlogic Amlogic, Inc.
++ampire Ampire Co., Ltd.
++ams AMS AG
++amstaos AMS-Taos Inc.
++analogix Analogix Semiconductor, Inc.
++andestech Andes Technology Corporation
++apm Applied Micro Circuits Corporation (APM)
++aptina Aptina Imaging
++arasan Arasan Chip Systems
++archermind ArcherMind Technology (Nanjing) Co., Ltd.
++arctic Arctic Sand
++aries Aries Embedded GmbH
++arm ARM Ltd.
++armadeus ARMadeus Systems SARL
++arrow Arrow Electronics
++artesyn Artesyn Embedded Technologies Inc.
++asahi-kasei Asahi Kasei Corp.
++aspeed ASPEED Technology Inc.
++asus AsusTek Computer Inc.
++atlas Atlas Scientific LLC
++atmel Atmel Corporation
++auo AU Optronics Corporation
++auvidea Auvidea GmbH
++avago Avago Technologies
++avia avia semiconductor
++avic Shanghai AVIC Optoelectronics Co., Ltd.
++avnet Avnet, Inc.
++axentia Axentia Technologies AB
++axis Axis Communications AB
++bananapi BIPAI KEJI LIMITED
++bhf Beckhoff Automation GmbH & Co. KG
++bitmain Bitmain Technologies
++blokaslabs Vilniaus Blokas UAB
++boe BOE Technology Group Co., Ltd.
++bosch Bosch Sensortec GmbH
++boundary Boundary Devices Inc.
++brcm Broadcom Corporation
++buffalo Buffalo, Inc.
++bticino Bticino International
++calxeda Calxeda
++capella Capella Microsystems, Inc
++cascoda Cascoda, Ltd.
++catalyst Catalyst Semiconductor, Inc.
++cavium Cavium, Inc.
++cdns Cadence Design Systems Inc.
++cdtech CDTech(H.K.) Electronics Limited
++ceva Ceva, Inc.
++chipidea Chipidea, Inc
++chipone ChipOne
++chipspark ChipSPARK
++chrp Common Hardware Reference Platform
++chunghwa Chunghwa Picture Tubes Ltd.
++ciaa Computadora Industrial Abierta Argentina
++cirrus Cirrus Logic, Inc.
++cloudengines Cloud Engines, Inc.
++cnm Chips&Media, Inc.
++cnxt Conexant Systems, Inc.
++compulab CompuLab Ltd.
++cortina Cortina Systems, Inc.
++cosmic Cosmic Circuits
++crane Crane Connectivity Solutions
++creative Creative Technology Ltd
++crystalfontz Crystalfontz America, Inc.
++csky Hangzhou C-SKY Microsystems Co., Ltd
++cubietech Cubietech, Ltd.
++cypress Cypress Semiconductor Corporation
++cznic CZ.NIC, z.s.p.o.
++dallas Maxim Integrated Products (formerly Dallas Semiconductor)
++dataimage DataImage, Inc.
++davicom DAVICOM Semiconductor, Inc.
++delta Delta Electronics, Inc.
++denx Denx Software Engineering
++devantech Devantech, Ltd.
++dh DH electronics GmbH
++digi Digi International Inc.
++digilent Diglent, Inc.
++dioo Dioo Microcircuit Co., Ltd
++dlc DLC Display Co., Ltd.
++dlg Dialog Semiconductor
++dlink D-Link Corporation
++dmo Data Modul AG
++domintech Domintech Co., Ltd.
++dongwoon Dongwoon Anatech
++dptechnics DPTechnics
++dragino Dragino Technology Co., Limited
++ea Embedded Artists AB
++ebs-systart EBS-SYSTART GmbH
++ebv EBV Elektronik
++eckelmann Eckelmann AG
++edt Emerging Display Technologies
++eeti eGalax_eMPIA Technology Inc
++elan Elan Microelectronic Corp.
++elgin Elgin S/A.
++embest Shenzhen Embest Technology Co., Ltd.
++emlid Emlid, Ltd.
++emmicro EM Microelectronic
++emtrion emtrion GmbH
++endless Endless Mobile, Inc.
++energymicro Silicon Laboratories (formerly Energy Micro AS)
++engicam Engicam S.r.l.
++epcos EPCOS AG
++epfl Ecole Polytechnique Fédérale de Lausanne
++epson Seiko Epson Corp.
++est ESTeem Wireless Modems
++ettus NI Ettus Research
++eukrea Eukréa Electromatique
++everest Everest Semiconductor Co. Ltd.
++everspin Everspin Technologies, Inc.
++exar Exar Corporation
++excito Excito
++ezchip EZchip Semiconductor
++facebook Facebook
++fairphone Fairphone B.V.
++faraday Faraday Technology Corporation
++fastrax Fastrax Oy
++fcs Fairchild Semiconductor
++feiyang Shenzhen Fly Young Technology Co.,LTD.
++firefly Firefly
++focaltech FocalTech Systems Co.,Ltd
++friendlyarm Guangzhou FriendlyARM Computer Tech Co., Ltd
++fsl Freescale Semiconductor
++fujitsu Fujitsu Ltd.
++gateworks Gateworks Corporation
++gcw Game Consoles Worldwide
++ge General Electric Company
++geekbuying GeekBuying
++gef GE Fanuc Intelligent Platforms Embedded Systems, Inc.
++GEFanuc GE Fanuc Intelligent Platforms Embedded Systems, Inc.
++geniatech Geniatech, Inc.
++giantec Giantec Semiconductor, Inc.
++giantplus Giantplus Technology Co., Ltd.
++globalscale Globalscale Technologies, Inc.
++globaltop GlobalTop Technology, Inc.
++gmt Global Mixed-mode Technology, Inc.
++goodix Shenzhen Huiding Technology Co., Ltd.
++google Google, Inc.
++grinn Grinn
++grmn Garmin Limited
++gumstix Gumstix, Inc.
++gw Gateworks Corporation
++hannstar HannStar Display Corporation
++haoyu Haoyu Microelectronic Co. Ltd.
++hardkernel Hardkernel Co., Ltd
++hideep HiDeep Inc.
++himax Himax Technologies, Inc.
++hisilicon Hisilicon Limited.
++hit Hitachi Ltd.
++hitex Hitex Development Tools
++holt Holt Integrated Circuits, Inc.
++honeywell Honeywell
++hp Hewlett Packard
++holtek Holtek Semiconductor, Inc.
++hwacom HwaCom Systems Inc.
++i2se I2SE GmbH
++ibm International Business Machines (IBM)
++icplus IC Plus Corp.
++idt Integrated Device Technologies, Inc.
++ifi Ingenieurburo Fur Ic-Technologie (I/F/I)
++ilitek ILI Technology Corporation (ILITEK)
++img Imagination Technologies Ltd.
++infineon Infineon Technologies
++inforce Inforce Computing
++ingenic Ingenic Semiconductor
++innolux Innolux Corporation
++inside-secure INSIDE Secure
++intel Intel Corporation
++intercontrol Inter Control Group
++invensense InvenSense Inc.
++inversepath Inverse Path
++iom Iomega Corporation
++isee ISEE 2007 S.L.
++isil Intersil
++issi Integrated Silicon Solutions Inc.
++itead ITEAD Intelligent Systems Co.Ltd
++iwave iWave Systems Technologies Pvt. Ltd.
++jdi Japan Display Inc.
++jedec JEDEC Solid State Technology Association
++jianda Jiandangjing Technology Co., Ltd.
++karo Ka-Ro electronics GmbH
++keithkoep Keith & Koep GmbH
++keymile Keymile GmbH
++khadas Khadas
++kiebackpeter Kieback & Peter GmbH
++kinetic Kinetic Technologies
++kingdisplay King & Display Technology Co., Ltd.
++kingnovel Kingnovel Technology Co., Ltd.
++koe Kaohsiung Opto-Electronics Inc.
++kosagi Sutajio Ko-Usagi PTE Ltd.
++kyo Kyocera Corporation
++lacie LaCie
++laird Laird PLC
++lantiq Lantiq Semiconductor
++lattice Lattice Semiconductor
++lego LEGO Systems A/S
++lemaker Shenzhen LeMaker Technology Co., Ltd.
++lenovo Lenovo Group Ltd.
++lg LG Corporation
++libretech Shenzhen Libre Technology Co., Ltd
++licheepi Lichee Pi
++linaro Linaro Limited
++linksys Belkin International, Inc. (Linksys)
++linux Linux-specific binding
++linx Linx Technologies
++lltc Linear Technology Corporation
++logicpd Logic PD, Inc.
++lsi LSI Corp. (LSI Logic)
++lwn Liebherr-Werk Nenzing GmbH
++macnica Macnica Americas
++marvell Marvell Technology Group Ltd.
++maxim Maxim Integrated Products
++mbvl Mobiveil Inc.
++mcube mCube
++meas Measurement Specialties
++mediatek MediaTek Inc.
++megachips MegaChips
++mele Shenzhen MeLE Digital Technology Ltd.
++melexis Melexis N.V.
++melfas MELFAS Inc.
++mellanox Mellanox Technologies
++memsic MEMSIC Inc.
++merrii Merrii Technology Co., Ltd.
++micrel Micrel Inc.
++microchip Microchip Technology Inc.
++microcrystal Micro Crystal AG
++micron Micron Technology Inc.
++mikroe MikroElektronika d.o.o.
++minix MINIX Technology Ltd.
++miramems MiraMEMS Sensing Technology Co., Ltd.
++mitsubishi Mitsubishi Electric Corporation
++mosaixtech Mosaix Technologies, Inc.
++motorola Motorola, Inc.
++moxa Moxa Inc.
++mpl MPL AG
++mqmaker mqmaker Inc.
++mscc Microsemi Corporation
++msi Micro-Star International Co. Ltd.
++mti Imagination Technologies Ltd. (formerly MIPS Technologies Inc.)
++multi-inno Multi-Inno Technology Co.,Ltd
++mundoreader Mundo Reader S.L.
++murata Murata Manufacturing Co., Ltd.
++mxicy Macronix International Co., Ltd.
++myir MYIR Tech Limited
++national National Semiconductor
++nec NEC LCD Technologies, Ltd.
++neonode Neonode Inc.
++netgear NETGEAR
++netlogic Broadcom Corporation (formerly NetLogic Microsystems)
++netron-dy Netron DY
++netxeon Shenzhen Netxeon Technology CO., LTD
++nexbox Nexbox
++nextthing Next Thing Co.
++newhaven Newhaven Display International
++ni National Instruments
++nintendo Nintendo
++nlt NLT Technologies, Ltd.
++nokia Nokia
++nordic Nordic Semiconductor
++novtech NovTech, Inc.
++nutsboard NutsBoard
++nuvoton Nuvoton Technology Corporation
++nvd New Vision Display
++nvidia NVIDIA
++nxp NXP Semiconductors
++okaya Okaya Electric America, Inc.
++oki Oki Electric Industry Co., Ltd.
++olimex OLIMEX Ltd.
++olpc One Laptop Per Child
++onion Onion Corporation
++onnn ON Semiconductor Corp.
++ontat On Tat Industrial Company
++opalkelly Opal Kelly Incorporated
++opencores OpenCores.org
++openrisc OpenRISC.io
++option Option NV
++oranth Shenzhen Oranth Technology Co., Ltd.
++ORCL Oracle Corporation
++orisetech Orise Technology
++ortustech Ortus Technology Co., Ltd.
++ovti OmniVision Technologies
++oxsemi Oxford Semiconductor, Ltd.
++panasonic Panasonic Corporation
++parade Parade Technologies Inc.
++pda Precision Design Associates, Inc.
++pericom Pericom Technology Inc.
++pervasive Pervasive Displays, Inc.
++phicomm PHICOMM Co., Ltd.
++phytec PHYTEC Messtechnik GmbH
++picochip Picochip Ltd
++pine64 Pine64
++pixcir PIXCIR MICROELECTRONICS Co., Ltd
++plantower Plantower Co., Ltd
++plathome Plat'Home Co., Ltd.
++plda PLDA
++plx Broadcom Corporation (formerly PLX Technology)
++pni PNI Sensor Corporation
++portwell Portwell Inc.
++poslab Poslab Technology Co., Ltd.
++powervr PowerVR (deprecated, use img)
++probox2 PROBOX2 (by W2COMP Co., Ltd.)
++pulsedlight PulsedLight, Inc
++qca Qualcomm Atheros, Inc.
++qcom Qualcomm Technologies, Inc
++qemu QEMU, a generic and open source machine emulator and virtualizer
++qi Qi Hardware
++qiaodian QiaoDian XianShi Corporation
++qnap QNAP Systems, Inc.
++radxa Radxa
++raidsonic RaidSonic Technology GmbH
++ralink Mediatek/Ralink Technology Corp.
++ramtron Ramtron International
++raspberrypi Raspberry Pi Foundation
++raydium Raydium Semiconductor Corp.
++rda Unisoc Communications, Inc.
++realtek Realtek Semiconductor Corp.
++renesas Renesas Electronics Corporation
++richtek Richtek Technology Corporation
++ricoh Ricoh Co. Ltd.
++rikomagic Rikomagic Tech Corp. Ltd
++riscv RISC-V Foundation
++rockchip Fuzhou Rockchip Electronics Co., Ltd
++rohm ROHM Semiconductor Co., Ltd
++roofull Shenzhen Roofull Technology Co, Ltd
++samsung Samsung Semiconductor
++samtec Samtec/Softing company
++sancloud Sancloud Ltd
++sandisk Sandisk Corporation
++sbs Smart Battery System
++schindler Schindler
++seagate Seagate Technology PLC
++semtech Semtech Corporation
++sensirion Sensirion AG
++sff Small Form Factor Committee
++sgd Solomon Goldentek Display Corporation
++sgx SGX Sensortech
++sharp Sharp Corporation
++shimafuji Shimafuji Electric, Inc.
++si-en Si-En Technology Ltd.
++sifive SiFive, Inc.
++sigma Sigma Designs, Inc.
++sii Seiko Instruments, Inc.
++sil Silicon Image
++silabs Silicon Laboratories
++silead Silead Inc.
++silergy Silergy Corp.
++siliconmitus Silicon Mitus, Inc.
++simtek
++sirf SiRF Technology, Inc.
++sis Silicon Integrated Systems Corp.
++sitronix Sitronix Technology Corporation
++skyworks Skyworks Solutions, Inc.
++smsc Standard Microsystems Corporation
++snps Synopsys, Inc.
++socionext Socionext Inc.
++solidrun SolidRun
++solomon Solomon Systech Limited
++sony Sony Corporation
++spansion Spansion Inc.
++sprd Spreadtrum Communications Inc.
++sst Silicon Storage Technology, Inc.
++st STMicroelectronics
++starry Starry Electronic Technology (ShenZhen) Co., LTD
++startek Startek
++ste ST-Ericsson
++stericsson ST-Ericsson
++summit Summit microelectronics
++sunchip Shenzhen Sunchip Technology Co., Ltd
++SUNW Sun Microsystems, Inc
++swir Sierra Wireless
++syna Synaptics Inc.
++synology Synology, Inc.
++tbs TBS Technologies
++tbs-biometrics Touchless Biometric Systems AG
++tcg Trusted Computing Group
++tcl Toby Churchill Ltd.
++technexion TechNexion
++technologic Technologic Systems
++tempo Tempo Semiconductor
++techstar Shenzhen Techstar Electronics Co., Ltd.
++terasic Terasic Inc.
++thine THine Electronics, Inc.
++ti Texas Instruments
++tianma Tianma Micro-electronics Co., Ltd.
++tlm Trusted Logic Mobility
++tmt Tecon Microprocessor Technologies, LLC.
++topeet Topeet
++toradex Toradex AG
++toshiba Toshiba Corporation
++toumaz Toumaz
++tpk TPK U.S.A. LLC
++tplink TP-LINK Technologies Co., Ltd.
++tpo TPO
++tronfy Tronfy
++tronsmart Tronsmart
++truly Truly Semiconductors Limited
++tsd Theobroma Systems Design und Consulting GmbH
++tyan Tyan Computer Corporation
++u-blox u-blox
++ucrobotics uCRobotics
++ubnt Ubiquiti Networks
++udoo Udoo
++uniwest United Western Technologies Corp (UniWest)
++upisemi uPI Semiconductor Corp.
++urt United Radiant Technology Corporation
++usi Universal Scientific Industrial Co., Ltd.
++v3 V3 Semiconductor
++vamrs Vamrs Ltd.
++variscite Variscite Ltd.
++via VIA Technologies, Inc.
++virtio Virtual I/O Device Specification, developed by the OASIS consortium
++vishay Vishay Intertechnology, Inc
++vitesse Vitesse Semiconductor Corporation
++vivante Vivante Corporation
++vocore VoCore Studio
++voipac Voipac Technologies s.r.o.
++vot Vision Optical Technology Co., Ltd.
++wd Western Digital Corp.
++wetek WeTek Electronics, limited.
++wexler Wexler
++whwave Shenzhen whwave Electronics, Inc.
++wi2wi Wi2Wi, Inc.
++winbond Winbond Electronics corp.
++winstar Winstar Display Corp.
++wlf Wolfson Microelectronics
++wm Wondermedia Technologies, Inc.
++x-powers X-Powers
++xes Extreme Engineering Solutions (X-ES)
++xillybus Xillybus Ltd.
++xlnx Xilinx
++xunlong Shenzhen Xunlong Software CO.,Limited
++ysoft Y Soft Corporation a.s.
++zarlink Zarlink Semiconductor
++zeitec ZEITEC Semiconductor Co., LTD.
++zidoo Shenzhen Zidoo Technology Co., Ltd.
++zii Zodiac Inflight Innovations
++zte ZTE Corp.
++zyxel ZyXEL Communications Corp.
+--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
++++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
+@@ -143,6 +143,8 @@ patternProperties:
+ description: Beckhoff Automation GmbH & Co. KG
+ "^bitmain,.*":
+ description: Bitmain Technologies
++ "^blokaslabs,.*":
++ description: Vilniaus Blokas UAB
+ "^boe,.*":
+ description: BOE Technology Group Co., Ltd.
+ "^bosch,.*":
+--- /dev/null
++++ b/sound/soc/bcm/pisound.c
+@@ -0,0 +1,1201 @@
++/*
++ * Pisound Linux kernel module.
++ * Copyright (C) 2016-2019 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_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];
++static char g_version[5];
++
++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 = 100000;
++ transfer.delay_usecs = 10;
++ 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 = 128 };
++ enum { MIDI_BYTES_PER_SECOND = 3125 };
++ int out_buffer_used = 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 < PISOUND_OUTPUT_BUFFER_SIZE;
++ 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;
++ }
++ }
++
++ 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;
++ out_buffer_used -=
++ (MIDI_BYTES_PER_SECOND / HZ) /
++ (now - last_transfer_at);
++ if (out_buffer_used < 0)
++ out_buffer_used = 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
++ );
++
++ 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_version, 0, sizeof(g_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_version,
++ sizeof(g_version),
++ "%x.%02x",
++ buffer[0],
++ buffer[1]
++ );
++ 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 >= sizeof(g_id))
++ return -EINVAL;
++
++ p = g_id;
++ for (j = 0; j < n; ++j)
++ p += sprintf(p, "%02x", buffer[j]);
++ }
++ 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_version, 0, sizeof(g_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)
++{
++ if (strlen(g_serial_num))
++ return g_serial_num;
++
++ return "";
++}
++
++static const char *pisnd_spi_get_id(void)
++{
++ if (strlen(g_id))
++ return g_id;
++
++ return "";
++}
++
++static const char *pisnd_spi_get_version(void)
++{
++ if (strlen(g_version))
++ return g_version;
++
++ return "";
++}
++
++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 = rtd->cpu_dai;
++
++ /* 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_version_show(
++ struct kobject *kobj,
++ struct kobj_attribute *attr,
++ char *buf
++ )
++{
++ return sprintf(buf, "%s\n", pisnd_spi_get_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_version_attribute =
++ __ATTR(version, 0444, pisnd_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_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("\tVersion: %s\n", pisnd_spi_get_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();
++
++ return snd_soc_unregister_card(&pisnd_card);
++}
++
++MODULE_DEVICE_TABLE(of, pisound_of_match);
++
++static struct platform_driver pisnd_driver = {
++ .driver = {
++ .name = "snd-rpi-pisound",
++ .owner = THIS_MODULE,
++ .of_match_table = pisound_of_match,
++ },
++ .probe = pisnd_probe,
++ .remove = pisnd_remove,
++};
++
++module_platform_driver(pisnd_driver);
++
++MODULE_AUTHOR("Giedrius Trainavicius <giedrius@blokas.io>");
++MODULE_DESCRIPTION("ASoC Driver for Pisound, https://blokas.io/pisound");
++MODULE_LICENSE("GPL v2");