diff options
author | Álvaro Fernández Rojas <noltari@gmail.com> | 2020-02-29 09:25:20 +0100 |
---|---|---|
committer | Álvaro Fernández Rojas <noltari@gmail.com> | 2020-02-29 12:50:51 +0100 |
commit | a1383655cfaa71609d6236ae0fcf3b6047462b98 (patch) | |
tree | c6f123859e0dfcfaa6c1063eda26f27e208d3cac /target/linux/bcm27xx/patches-5.4/950-0071-Support-for-Blokas-Labs-pisound-board.patch | |
parent | a8aa974a9dfb4cba484dc4c1e4207fd9ec803410 (diff) | |
download | upstream-a1383655cfaa71609d6236ae0fcf3b6047462b98.tar.gz upstream-a1383655cfaa71609d6236ae0fcf3b6047462b98.tar.bz2 upstream-a1383655cfaa71609d6236ae0fcf3b6047462b98.zip |
bcm27xx: add linux 5.4 support
Tested on bcm2710 (Raspberry Pi 3B).
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
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.patch | 1752 |
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"); |