mirror of
https://github.com/openwrt/openwrt.git
synced 2024-12-20 22:23:27 +00:00
a1383655cf
Tested on bcm2710 (Raspberry Pi 3B). Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
1753 lines
43 KiB
Diff
1753 lines
43 KiB
Diff
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");
|