These patches were generated from: https://github.com/raspberrypi/linux/commits/rpi-6.12.y With the following command: git format-patch -N v6.12.27..HEAD (HEAD -> 8d3206ee456a5ecdf9ddbfd8e5e231e4f0cd716e) Exceptions: - (def)configs patches - github workflows patches - applied & reverted patches - readme patches - wireless patches Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
7329 lines
217 KiB
Diff
7329 lines
217 KiB
Diff
From 8ab68f8c2cf6828cac9674eec4150eff777bf39d Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?Giedrius=20Trainavi=C4=8Dius?= <giedrius@blokas.io>
|
|
Date: Wed, 19 Mar 2025 19:55:46 +0200
|
|
Subject: [PATCH] Adding Pisound Micro kernel module
|
|
MIME-Version: 1.0
|
|
Content-Type: text/plain; charset=UTF-8
|
|
Content-Transfer-Encoding: 8bit
|
|
|
|
Signed-off-by: Giedrius Trainavičius <giedrius@blokas.io>
|
|
---
|
|
.../devicetree/bindings/vendor-prefixes.txt | 1 +
|
|
.../devicetree/bindings/vendor-prefixes.yaml | 2 +-
|
|
sound/drivers/Kconfig | 9 +
|
|
sound/drivers/Makefile | 2 +-
|
|
sound/drivers/upisnd/Makefile | 23 +
|
|
sound/drivers/upisnd/upisnd_codec.c | 1739 +++++++++++++++++
|
|
sound/drivers/upisnd/upisnd_codec.h | 23 +
|
|
sound/drivers/upisnd/upisnd_comm.c | 638 ++++++
|
|
sound/drivers/upisnd/upisnd_comm.h | 104 +
|
|
sound/drivers/upisnd/upisnd_common.h | 97 +
|
|
sound/drivers/upisnd/upisnd_ctrl.c | 246 +++
|
|
sound/drivers/upisnd/upisnd_ctrl.h | 36 +
|
|
sound/drivers/upisnd/upisnd_debug.h | 34 +
|
|
sound/drivers/upisnd/upisnd_gpio.c | 867 ++++++++
|
|
sound/drivers/upisnd/upisnd_gpio.h | 75 +
|
|
sound/drivers/upisnd/upisnd_midi.c | 326 +++
|
|
sound/drivers/upisnd/upisnd_midi.h | 47 +
|
|
sound/drivers/upisnd/upisnd_module.c | 273 +++
|
|
sound/drivers/upisnd/upisnd_pins.c | 113 ++
|
|
sound/drivers/upisnd/upisnd_pins.h | 67 +
|
|
sound/drivers/upisnd/upisnd_protocol.h | 322 +++
|
|
sound/drivers/upisnd/upisnd_sound.c | 277 +++
|
|
sound/drivers/upisnd/upisnd_sound.h | 28 +
|
|
sound/drivers/upisnd/upisnd_sysfs.c | 1728 ++++++++++++++++
|
|
sound/drivers/upisnd/upisnd_sysfs.h | 32 +
|
|
sound/drivers/upisnd/upisnd_utils.c | 33 +
|
|
sound/drivers/upisnd/upisnd_utils.h | 25 +
|
|
27 files changed, 7165 insertions(+), 2 deletions(-)
|
|
create mode 100644 sound/drivers/upisnd/Makefile
|
|
create mode 100644 sound/drivers/upisnd/upisnd_codec.c
|
|
create mode 100644 sound/drivers/upisnd/upisnd_codec.h
|
|
create mode 100644 sound/drivers/upisnd/upisnd_comm.c
|
|
create mode 100644 sound/drivers/upisnd/upisnd_comm.h
|
|
create mode 100644 sound/drivers/upisnd/upisnd_common.h
|
|
create mode 100644 sound/drivers/upisnd/upisnd_ctrl.c
|
|
create mode 100644 sound/drivers/upisnd/upisnd_ctrl.h
|
|
create mode 100644 sound/drivers/upisnd/upisnd_debug.h
|
|
create mode 100644 sound/drivers/upisnd/upisnd_gpio.c
|
|
create mode 100644 sound/drivers/upisnd/upisnd_gpio.h
|
|
create mode 100644 sound/drivers/upisnd/upisnd_midi.c
|
|
create mode 100644 sound/drivers/upisnd/upisnd_midi.h
|
|
create mode 100644 sound/drivers/upisnd/upisnd_module.c
|
|
create mode 100644 sound/drivers/upisnd/upisnd_pins.c
|
|
create mode 100644 sound/drivers/upisnd/upisnd_pins.h
|
|
create mode 100644 sound/drivers/upisnd/upisnd_protocol.h
|
|
create mode 100644 sound/drivers/upisnd/upisnd_sound.c
|
|
create mode 100644 sound/drivers/upisnd/upisnd_sound.h
|
|
create mode 100644 sound/drivers/upisnd/upisnd_sysfs.c
|
|
create mode 100644 sound/drivers/upisnd/upisnd_sysfs.h
|
|
create mode 100644 sound/drivers/upisnd/upisnd_utils.c
|
|
create mode 100644 sound/drivers/upisnd/upisnd_utils.h
|
|
|
|
--- a/Documentation/devicetree/bindings/vendor-prefixes.txt
|
|
+++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
|
|
@@ -57,6 +57,7 @@ axis Axis Communications AB
|
|
bananapi BIPAI KEJI LIMITED
|
|
bhf Beckhoff Automation GmbH & Co. KG
|
|
bitmain Bitmain Technologies
|
|
+blokas Vilniaus Blokas UAB
|
|
blokaslabs Vilniaus Blokas UAB
|
|
boe BOE Technology Group Co., Ltd.
|
|
bosch Bosch Sensortec GmbH
|
|
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
|
|
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
|
|
@@ -218,7 +218,7 @@ patternProperties:
|
|
description: Shenzhen BigTree Tech Co., LTD
|
|
"^bitmain,.*":
|
|
description: Bitmain Technologies
|
|
- "^blokaslabs,.*":
|
|
+ "^blokas(labs)?,.*":
|
|
description: Vilniaus Blokas UAB
|
|
"^blutek,.*":
|
|
description: BluTek Power
|
|
--- a/sound/drivers/Kconfig
|
|
+++ b/sound/drivers/Kconfig
|
|
@@ -273,4 +273,13 @@ config SND_PIMIDI
|
|
To compile this driver as a module, choose M here: the module
|
|
will be called snd-pimidi.
|
|
|
|
+config SND_PISOUND_MICRO
|
|
+ tristate "Pisound Micro driver"
|
|
+ depends on SND_SEQUENCER && CRC8
|
|
+ help
|
|
+ Say Y here to include support for Blokas Pisound Micro.
|
|
+
|
|
+ To compile this driver as modules, choose M here: the modules
|
|
+ will be called snd-soc-upisnd-ctrl and snd-soc-upisnd-codec.
|
|
+
|
|
endif # SND_DRIVERS
|
|
--- a/sound/drivers/Makefile
|
|
+++ b/sound/drivers/Makefile
|
|
@@ -27,4 +27,4 @@ obj-$(CONFIG_SND_MTS64) += snd-mts64.o
|
|
obj-$(CONFIG_SND_PIMIDI) += snd-pimidi.o
|
|
obj-$(CONFIG_SND_PORTMAN2X4) += snd-portman2x4.o
|
|
|
|
-obj-$(CONFIG_SND) += opl3/ opl4/ mpu401/ vx/ pcsp/
|
|
+obj-$(CONFIG_SND) += opl3/ opl4/ mpu401/ vx/ pcsp/ upisnd/
|
|
--- /dev/null
|
|
+++ b/sound/drivers/upisnd/Makefile
|
|
@@ -0,0 +1,23 @@
|
|
+# Pisound Micro Linux kernel module.
|
|
+# Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/
|
|
+#
|
|
+# 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.
|
|
+#
|
|
+
|
|
+-include $(shell pwd)/Makefile.dev
|
|
+
|
|
+ifdef M
|
|
+-include $(M)/Makefile.dev
|
|
+endif
|
|
+
|
|
+obj-$(CONFIG_SND_PISOUND_MICRO) := snd-soc-upisnd-ctrl.o snd-soc-upisnd-codec.o
|
|
+snd-soc-upisnd-ctrl-objs := upisnd_module.o upisnd_ctrl.o upisnd_comm.o upisnd_sound.o upisnd_midi.o upisnd_pins.o upisnd_gpio.o upisnd_sysfs.o upisnd_utils.o
|
|
+snd-soc-upisnd-codec-objs := upisnd_codec.o
|
|
--- /dev/null
|
|
+++ b/sound/drivers/upisnd/upisnd_codec.c
|
|
@@ -0,0 +1,1739 @@
|
|
+// SPDX-License-Identifier: GPL-2.0-only
|
|
+/*
|
|
+ * Pisound Micro Linux kernel module.
|
|
+ * Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/
|
|
+ *
|
|
+ * 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.
|
|
+ */
|
|
+
|
|
+#include <linux/i2c.h>
|
|
+#include <linux/mod_devicetable.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/regmap.h>
|
|
+#include <linux/clk.h>
|
|
+#include <linux/device.h>
|
|
+#include <linux/regmap.h>
|
|
+#include <linux/list.h>
|
|
+#include <linux/gcd.h>
|
|
+
|
|
+#include <sound/core.h>
|
|
+#include <sound/pcm.h>
|
|
+#include <sound/pcm_params.h>
|
|
+#include <sound/soc.h>
|
|
+#include <sound/tlv.h>
|
|
+
|
|
+#include "upisnd_codec.h"
|
|
+#include "upisnd_debug.h"
|
|
+
|
|
+// Based on ADAU1361/ADAU1461/ADAU1761/ADAU1961 driver by Lars-Peter Clausen.
|
|
+// Customized specifically for ADAU1961 on Pisound Micro.
|
|
+
|
|
+static int adau_calc_pll_cfg(unsigned int freq_in, unsigned int freq_out,
|
|
+ u8 regs[5])
|
|
+{
|
|
+ unsigned int r, n, m, i, j;
|
|
+ unsigned int div;
|
|
+
|
|
+ if (!freq_out) {
|
|
+ r = 0;
|
|
+ n = 0;
|
|
+ m = 0;
|
|
+ div = 0;
|
|
+ } else {
|
|
+ if (freq_out % freq_in != 0) {
|
|
+ div = DIV_ROUND_UP(freq_in, 13500000);
|
|
+ freq_in /= div;
|
|
+ r = freq_out / freq_in;
|
|
+ i = freq_out % freq_in;
|
|
+ j = gcd(i, freq_in);
|
|
+ n = i / j;
|
|
+ m = freq_in / j;
|
|
+ div--;
|
|
+ } else {
|
|
+ r = freq_out / freq_in;
|
|
+ n = 0;
|
|
+ m = 0;
|
|
+ div = 0;
|
|
+ }
|
|
+ if (n > 0xffff || m > 0xffff || div > 3 || r > 8 || r < 2)
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ regs[0] = m >> 8;
|
|
+ regs[1] = m & 0xff;
|
|
+ regs[2] = n >> 8;
|
|
+ regs[3] = n & 0xff;
|
|
+ regs[4] = (r << 3) | (div << 1);
|
|
+ if (m != 0)
|
|
+ regs[4] |= 1; /* Fractional mode */
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * enum adau1961_micbias_voltage - Microphone bias voltage
|
|
+ * @ADAU1961_MICBIAS_0_90_AVDD: 0.9 * AVDD
|
|
+ * @ADAU1961_MICBIAS_0_65_AVDD: 0.65 * AVDD
|
|
+ */
|
|
+enum adau1961_micbias_voltage {
|
|
+ ADAU1961_MICBIAS_0_90_AVDD = 0,
|
|
+ ADAU1961_MICBIAS_0_65_AVDD = 1,
|
|
+};
|
|
+
|
|
+/**
|
|
+ * enum adau1961_output_mode - Output mode configuration
|
|
+ * @ADAU1961_OUTPUT_MODE_HEADPHONE: Headphone output
|
|
+ * @ADAU1961_OUTPUT_MODE_HEADPHONE_CAPLESS: Capless headphone output
|
|
+ * @ADAU1961_OUTPUT_MODE_LINE: Line output
|
|
+ */
|
|
+enum adau1961_output_mode {
|
|
+ ADAU1961_OUTPUT_MODE_HEADPHONE,
|
|
+ ADAU1961_OUTPUT_MODE_HEADPHONE_CAPLESS,
|
|
+ ADAU1961_OUTPUT_MODE_LINE,
|
|
+};
|
|
+
|
|
+/**
|
|
+ * struct adau1961_platform_data - ADAU1961 Codec driver platform data
|
|
+ * @input_differential: If true the input pins will be configured in
|
|
+ * differential mode.
|
|
+ * @lineout_mode: Output mode for the LOUT/ROUT pins
|
|
+ * @headphone_mode: Output mode for the LHP/RHP pins
|
|
+ * @monoout_mode: Output mode for the MONOOUT pin. Ignored if headphone_mode
|
|
+ * is set to ADAU1961_OUTPUT_MODE_HEADPHONE_CAPLESS.
|
|
+ */
|
|
+struct adau1961_platform_data {
|
|
+ bool input_differential;
|
|
+ enum adau1961_output_mode lineout_mode;
|
|
+ enum adau1961_output_mode headphone_mode;
|
|
+ enum adau1961_output_mode monoout_mode;
|
|
+};
|
|
+
|
|
+struct snd_soc_component;
|
|
+struct snd_pcm_substream;
|
|
+
|
|
+struct i2c_client;
|
|
+
|
|
+enum adau1961_pll {
|
|
+ ADAU1961_PLL,
|
|
+};
|
|
+
|
|
+enum adau1961_pll_src {
|
|
+ ADAU1961_PLL_SRC_MCLK,
|
|
+};
|
|
+
|
|
+enum adau1961_clk_src {
|
|
+ /* Automatically configure PLL based on the sample rate */
|
|
+ ADAU1961_CLK_SRC_PLL_AUTO,
|
|
+ ADAU1961_CLK_SRC_MCLK,
|
|
+ ADAU1961_CLK_SRC_PLL,
|
|
+};
|
|
+
|
|
+struct clk;
|
|
+
|
|
+struct adau {
|
|
+ unsigned int sysclk;
|
|
+ unsigned int pll_freq;
|
|
+ struct clk *mclk;
|
|
+
|
|
+ enum adau1961_clk_src clk_src;
|
|
+ void (*switch_mode)(struct device *dev);
|
|
+
|
|
+ unsigned int dai_fmt;
|
|
+
|
|
+ u8 pll_regs[6];
|
|
+
|
|
+ bool master;
|
|
+ bool vgnd_shorted;
|
|
+ bool hpl_unmuted;
|
|
+ bool hpr_unmuted;
|
|
+
|
|
+ unsigned int tdm_slot[2];
|
|
+
|
|
+ struct regmap *regmap;
|
|
+};
|
|
+
|
|
+#define ADAU1961_CLOCK_CONTROL 0x4000
|
|
+#define ADAU1961_PLL_CONTROL 0x4002
|
|
+#define ADAU1961_DIGMIC_JACKDETECT 0x4008
|
|
+#define ADAU1961_REC_MIXER_LEFT0 0x400a
|
|
+#define ADAU1961_REC_MIXER_LEFT1 0x400b
|
|
+#define ADAU1961_REC_MIXER_RIGHT0 0x400c
|
|
+#define ADAU1961_REC_MIXER_RIGHT1 0x400d
|
|
+#define ADAU1961_LEFT_DIFF_INPUT_VOL 0x400e
|
|
+#define ADAU1961_RIGHT_DIFF_INPUT_VOL 0x400f
|
|
+#define ADAU1961_MICBIAS 0x4010
|
|
+#define ADAU1961_ALC_CTRL0 0x4011
|
|
+#define ADAU1961_ALC_CTRL1 0x4012
|
|
+#define ADAU1961_ALC_CTRL2 0x4013
|
|
+#define ADAU1961_ALC_CTRL3 0x4014
|
|
+#define ADAU1961_SERIAL_PORT0 0x4015
|
|
+#define ADAU1961_SERIAL_PORT1 0x4016
|
|
+#define ADAU1961_CONVERTER0 0x4017
|
|
+#define ADAU1961_CONVERTER1 0x4018
|
|
+#define ADAU1961_ADC_CONTROL 0x4019
|
|
+#define ADAU1961_LEFT_INPUT_DIGITAL_VOL 0x401a
|
|
+#define ADAU1961_RIGHT_INPUT_DIGITAL_VOL 0x401b
|
|
+#define ADAU1961_PLAY_MIXER_LEFT0 0x401c
|
|
+#define ADAU1961_PLAY_MIXER_LEFT1 0x401d
|
|
+#define ADAU1961_PLAY_MIXER_RIGHT0 0x401e
|
|
+#define ADAU1961_PLAY_MIXER_RIGHT1 0x401f
|
|
+#define ADAU1961_PLAY_LR_MIXER_LEFT 0x4020
|
|
+#define ADAU1961_PLAY_LR_MIXER_RIGHT 0x4021
|
|
+#define ADAU1961_PLAY_MIXER_MONO 0x4022
|
|
+#define ADAU1961_PLAY_HP_LEFT_VOL 0x4023
|
|
+#define ADAU1961_PLAY_HP_RIGHT_VOL 0x4024
|
|
+#define ADAU1961_PLAY_LINE_LEFT_VOL 0x4025
|
|
+#define ADAU1961_PLAY_LINE_RIGHT_VOL 0x4026
|
|
+#define ADAU1961_PLAY_MONO_OUTPUT_VOL 0x4027
|
|
+#define ADAU1961_POP_CLICK_SUPPRESS 0x4028
|
|
+#define ADAU1961_PLAY_POWER_MGMT 0x4029
|
|
+#define ADAU1961_DAC_CONTROL0 0x402a
|
|
+#define ADAU1961_DAC_CONTROL1 0x402b
|
|
+#define ADAU1961_DAC_CONTROL2 0x402c
|
|
+#define ADAU1961_SERIAL_PORT_PAD 0x402d
|
|
+#define ADAU1961_CONTROL_PORT_PAD0 0x402f
|
|
+#define ADAU1961_CONTROL_PORT_PAD1 0x4030
|
|
+#define ADAU1961_JACK_DETECT_PIN 0x4031
|
|
+#define ADAU1961_DEJITTER 0x4036
|
|
+
|
|
+#define ADAU1961_CLOCK_CONTROL_INFREQ_MASK 0x6
|
|
+#define ADAU1961_CLOCK_CONTROL_CORECLK_SRC_PLL BIT(3)
|
|
+#define ADAU1961_CLOCK_CONTROL_SYSCLK_EN BIT(0)
|
|
+
|
|
+#define ADAU1961_SERIAL_PORT0_BCLK_POL BIT(4)
|
|
+#define ADAU1961_SERIAL_PORT0_LRCLK_POL BIT(3)
|
|
+#define ADAU1961_SERIAL_PORT0_MASTER BIT(0)
|
|
+#define ADAU1961_SERIAL_PORT0_STEREO (0x0 << 1)
|
|
+#define ADAU1961_SERIAL_PORT0_TDM4 (0x1 << 1)
|
|
+#define ADAU1961_SERIAL_PORT0_TDM_MASK (0x3 << 1)
|
|
+#define ADAU1961_SERIAL_PORT0_PULSE_MODE BIT(5)
|
|
+
|
|
+#define ADAU1961_SERIAL_PORT1_DELAY1 0x00
|
|
+#define ADAU1961_SERIAL_PORT1_DELAY0 0x01
|
|
+#define ADAU1961_SERIAL_PORT1_DELAY8 0x02
|
|
+#define ADAU1961_SERIAL_PORT1_DELAY16 0x03
|
|
+#define ADAU1961_SERIAL_PORT1_DELAY_MASK 0x03
|
|
+#define ADAU1961_SERIAL_PORT1_BCLK64 (0x0 << 5)
|
|
+#define ADAU1961_SERIAL_PORT1_BCLK32 (0x1 << 5)
|
|
+#define ADAU1961_SERIAL_PORT1_BCLK48 (0x2 << 5)
|
|
+#define ADAU1961_SERIAL_PORT1_BCLK128 (0x3 << 5)
|
|
+#define ADAU1961_SERIAL_PORT1_BCLK256 (0x4 << 5)
|
|
+#define ADAU1961_SERIAL_PORT1_BCLK_MASK (0x7 << 5)
|
|
+
|
|
+#define ADAU1961_CONVERTER0_DAC_PAIR(x) (((x) - 1) << 5)
|
|
+#define ADAU1961_CONVERTER0_DAC_PAIR_MASK (0x3 << 5)
|
|
+#define ADAU1961_CONVERTER0_CONVSR_MASK 0x7
|
|
+#define ADAU1961_CONVERTER0_ADOSR BIT(3)
|
|
+
|
|
+#define ADAU1961_CONVERTER1_ADC_PAIR(x) ((x) - 1)
|
|
+#define ADAU1961_CONVERTER1_ADC_PAIR_MASK 0x3
|
|
+
|
|
+#define ADAU1961_DIFF_INPUT_VOL_LDEN BIT(0)
|
|
+
|
|
+#define ADAU1961_PLAY_MIXER_MONO_EN BIT(0)
|
|
+
|
|
+#define ADAU1961_PLAY_MONO_OUTPUT_VOL_MODE_HP BIT(0)
|
|
+#define ADAU1961_PLAY_MONO_OUTPUT_VOL_UNMUTE BIT(1)
|
|
+
|
|
+#define ADAU1961_PLAY_HP_RIGHT_VOL_MODE_HP BIT(0)
|
|
+
|
|
+#define ADAU1961_PLAY_LINE_LEFT_VOL_MODE_HP BIT(0)
|
|
+
|
|
+#define ADAU1961_PLAY_LINE_RIGHT_VOL_MODE_HP BIT(0)
|
|
+
|
|
+static const DECLARE_TLV_DB_MINMAX(adau1961_digital_tlv, -9563, 0);
|
|
+
|
|
+static int adau1961_pll_event(struct snd_soc_dapm_widget *w,
|
|
+ struct snd_kcontrol *kcontrol, int event)
|
|
+{
|
|
+ struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
|
|
+ struct adau *adau = snd_soc_component_get_drvdata(component);
|
|
+
|
|
+ if (SND_SOC_DAPM_EVENT_ON(event)) {
|
|
+ adau->pll_regs[5] = 1;
|
|
+ } else {
|
|
+ adau->pll_regs[5] = 0;
|
|
+ // Bypass the PLL when disabled, otherwise registers will become
|
|
+ // inaccessible.
|
|
+ regmap_update_bits(adau->regmap, ADAU1961_CLOCK_CONTROL,
|
|
+ ADAU1961_CLOCK_CONTROL_CORECLK_SRC_PLL, 0);
|
|
+ }
|
|
+
|
|
+ // The PLL register is 6 bytes long and can only be written at once.
|
|
+ regmap_raw_write(adau->regmap, ADAU1961_PLL_CONTROL,
|
|
+ adau->pll_regs, ARRAY_SIZE(adau->pll_regs));
|
|
+
|
|
+ if (SND_SOC_DAPM_EVENT_ON(event)) {
|
|
+ mdelay(5);
|
|
+ regmap_update_bits(adau->regmap, ADAU1961_CLOCK_CONTROL,
|
|
+ ADAU1961_CLOCK_CONTROL_CORECLK_SRC_PLL,
|
|
+ ADAU1961_CLOCK_CONTROL_CORECLK_SRC_PLL);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int adau1961_adc_fixup(struct snd_soc_dapm_widget *w,
|
|
+ struct snd_kcontrol *kcontrol, int event)
|
|
+{
|
|
+ struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
|
|
+ struct adau *adau = snd_soc_component_get_drvdata(component);
|
|
+
|
|
+ /*
|
|
+ * If we are capturing, toggle the ADOSR bit in Converter Control 0 to
|
|
+ * avoid losing SNR (workaround from ADI). This must be done after
|
|
+ * the ADC(s) have been enabled. According to the data sheet, it is
|
|
+ * normally illegal to set this bit when the sampling rate is 96 kHz,
|
|
+ * but according to ADI it is acceptable for this workaround.
|
|
+ */
|
|
+ regmap_update_bits(adau->regmap, ADAU1961_CONVERTER0,
|
|
+ ADAU1961_CONVERTER0_ADOSR, ADAU1961_CONVERTER0_ADOSR);
|
|
+ regmap_update_bits(adau->regmap, ADAU1961_CONVERTER0,
|
|
+ ADAU1961_CONVERTER0_ADOSR, 0);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const char * const adau1961_mono_stereo_text[] = {
|
|
+ "Stereo",
|
|
+ "Mono Left Channel (L+R)",
|
|
+ "Mono Right Channel (L+R)",
|
|
+ "Mono (L+R)",
|
|
+};
|
|
+
|
|
+static SOC_ENUM_SINGLE_DECL(adau1961_dac_mode_enum,
|
|
+ ADAU1961_DAC_CONTROL0, 6, adau1961_mono_stereo_text);
|
|
+
|
|
+static const struct snd_kcontrol_new adau1961_dac_mode_mux =
|
|
+ SOC_DAPM_ENUM("DAC Mono-Stereo-Mode", adau1961_dac_mode_enum);
|
|
+
|
|
+static const struct snd_soc_dapm_route adau1961_dapm_pll_route = {
|
|
+ "SYSCLK", NULL, "PLL",
|
|
+};
|
|
+
|
|
+static int adau1961_set_dai_pll(struct snd_soc_dai *dai, int pll_id,
|
|
+ int source, unsigned int freq_in, unsigned int freq_out)
|
|
+{
|
|
+ struct snd_soc_component *component = dai->component;
|
|
+ struct adau *adau = snd_soc_component_get_drvdata(component);
|
|
+ int ret;
|
|
+
|
|
+ if (freq_in < 8000000 || freq_in > 27000000)
|
|
+ return -EINVAL;
|
|
+
|
|
+ ret = adau_calc_pll_cfg(freq_in, freq_out, adau->pll_regs);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ /* The PLL register is 6 bytes long and can only be written at once. */
|
|
+ ret = regmap_raw_write(adau->regmap, ADAU1961_PLL_CONTROL,
|
|
+ adau->pll_regs, ARRAY_SIZE(adau->pll_regs));
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ adau->pll_freq = freq_out;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int adau1961_set_dai_sysclk(struct snd_soc_dai *dai,
|
|
+ int clk_id, unsigned int freq, int dir)
|
|
+{
|
|
+ struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(dai->component);
|
|
+ struct adau *adau = snd_soc_component_get_drvdata(dai->component);
|
|
+ bool is_pll;
|
|
+ bool was_pll;
|
|
+
|
|
+ switch (clk_id) {
|
|
+ case ADAU1961_CLK_SRC_MCLK:
|
|
+ is_pll = false;
|
|
+ break;
|
|
+ case ADAU1961_CLK_SRC_PLL_AUTO:
|
|
+ if (!adau->mclk)
|
|
+ return -EINVAL;
|
|
+ fallthrough;
|
|
+ case ADAU1961_CLK_SRC_PLL:
|
|
+ is_pll = true;
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ switch (adau->clk_src) {
|
|
+ case ADAU1961_CLK_SRC_MCLK:
|
|
+ was_pll = false;
|
|
+ break;
|
|
+ case ADAU1961_CLK_SRC_PLL:
|
|
+ case ADAU1961_CLK_SRC_PLL_AUTO:
|
|
+ was_pll = true;
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ adau->sysclk = freq;
|
|
+
|
|
+ if (is_pll != was_pll) {
|
|
+ if (is_pll) {
|
|
+ snd_soc_dapm_add_routes(dapm,
|
|
+ &adau1961_dapm_pll_route, 1);
|
|
+ } else {
|
|
+ snd_soc_dapm_del_routes(dapm,
|
|
+ &adau1961_dapm_pll_route, 1);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ adau->clk_src = clk_id;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int adau1961_auto_pll(struct snd_soc_dai *dai,
|
|
+ struct snd_pcm_hw_params *params)
|
|
+{
|
|
+ struct adau *adau = snd_soc_dai_get_drvdata(dai);
|
|
+ unsigned int pll_rate;
|
|
+
|
|
+ switch (params_rate(params)) {
|
|
+ case 48000:
|
|
+ case 8000:
|
|
+ case 12000:
|
|
+ case 16000:
|
|
+ case 24000:
|
|
+ case 32000:
|
|
+ case 96000:
|
|
+ pll_rate = 48000 * 1024;
|
|
+ break;
|
|
+ case 44100:
|
|
+ case 7350:
|
|
+ case 11025:
|
|
+ case 14700:
|
|
+ case 22050:
|
|
+ case 29400:
|
|
+ case 88200:
|
|
+ pll_rate = 44100 * 1024;
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ return adau1961_set_dai_pll(dai, ADAU1961_PLL, ADAU1961_PLL_SRC_MCLK,
|
|
+ clk_get_rate(adau->mclk), pll_rate);
|
|
+}
|
|
+
|
|
+static int adau1961_hw_params(struct snd_pcm_substream *substream,
|
|
+ struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
|
|
+{
|
|
+ struct snd_soc_component *component = dai->component;
|
|
+ struct adau *adau = snd_soc_component_get_drvdata(component);
|
|
+ unsigned int val, div, dsp_div;
|
|
+ unsigned int freq;
|
|
+ int ret;
|
|
+
|
|
+ switch (adau->clk_src) {
|
|
+ case ADAU1961_CLK_SRC_PLL_AUTO:
|
|
+ ret = adau1961_auto_pll(dai, params);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ fallthrough;
|
|
+ case ADAU1961_CLK_SRC_PLL:
|
|
+ freq = adau->pll_freq;
|
|
+ break;
|
|
+ default:
|
|
+ freq = adau->sysclk;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if (freq % params_rate(params) != 0)
|
|
+ return -EINVAL;
|
|
+
|
|
+ switch (freq / params_rate(params)) {
|
|
+ case 1024: /* fs */
|
|
+ div = 0;
|
|
+ dsp_div = 1;
|
|
+ break;
|
|
+ case 6144: /* fs / 6 */
|
|
+ div = 1;
|
|
+ dsp_div = 6;
|
|
+ break;
|
|
+ case 4096: /* fs / 4 */
|
|
+ div = 2;
|
|
+ dsp_div = 5;
|
|
+ break;
|
|
+ case 3072: /* fs / 3 */
|
|
+ div = 3;
|
|
+ dsp_div = 4;
|
|
+ break;
|
|
+ case 2048: /* fs / 2 */
|
|
+ div = 4;
|
|
+ dsp_div = 3;
|
|
+ break;
|
|
+ case 1536: /* fs / 1.5 */
|
|
+ div = 5;
|
|
+ dsp_div = 2;
|
|
+ break;
|
|
+ case 512: /* fs / 0.5 */
|
|
+ div = 6;
|
|
+ dsp_div = 0;
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ regmap_update_bits(adau->regmap, ADAU1961_CONVERTER0,
|
|
+ ADAU1961_CONVERTER0_CONVSR_MASK, div);
|
|
+
|
|
+ if (adau->dai_fmt != SND_SOC_DAIFMT_RIGHT_J)
|
|
+ return 0;
|
|
+
|
|
+ switch (params_width(params)) {
|
|
+ case 16:
|
|
+ val = ADAU1961_SERIAL_PORT1_DELAY16;
|
|
+ break;
|
|
+ case 24:
|
|
+ val = ADAU1961_SERIAL_PORT1_DELAY8;
|
|
+ break;
|
|
+ case 32:
|
|
+ val = ADAU1961_SERIAL_PORT1_DELAY0;
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ return regmap_update_bits(adau->regmap, ADAU1961_SERIAL_PORT1,
|
|
+ ADAU1961_SERIAL_PORT1_DELAY_MASK, val);
|
|
+}
|
|
+
|
|
+static int adau1961_set_dai_fmt(struct snd_soc_dai *dai,
|
|
+ unsigned int fmt)
|
|
+{
|
|
+ struct adau *adau = snd_soc_component_get_drvdata(dai->component);
|
|
+ unsigned int ctrl0, ctrl1;
|
|
+ unsigned int ctrl0_mask;
|
|
+ int lrclk_pol;
|
|
+
|
|
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
|
|
+ case SND_SOC_DAIFMT_CBM_CFM:
|
|
+ ctrl0 = ADAU1961_SERIAL_PORT0_MASTER;
|
|
+ adau->master = true;
|
|
+ break;
|
|
+ case SND_SOC_DAIFMT_CBS_CFS:
|
|
+ ctrl0 = 0;
|
|
+ adau->master = false;
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
|
|
+ case SND_SOC_DAIFMT_I2S:
|
|
+ lrclk_pol = 0;
|
|
+ ctrl1 = ADAU1961_SERIAL_PORT1_DELAY1;
|
|
+ break;
|
|
+ case SND_SOC_DAIFMT_LEFT_J:
|
|
+ case SND_SOC_DAIFMT_RIGHT_J:
|
|
+ lrclk_pol = 1;
|
|
+ ctrl1 = ADAU1961_SERIAL_PORT1_DELAY0;
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
|
|
+ case SND_SOC_DAIFMT_NB_NF:
|
|
+ break;
|
|
+ case SND_SOC_DAIFMT_IB_NF:
|
|
+ ctrl0 |= ADAU1961_SERIAL_PORT0_BCLK_POL;
|
|
+ break;
|
|
+ case SND_SOC_DAIFMT_NB_IF:
|
|
+ lrclk_pol = !lrclk_pol;
|
|
+ break;
|
|
+ case SND_SOC_DAIFMT_IB_IF:
|
|
+ ctrl0 |= ADAU1961_SERIAL_PORT0_BCLK_POL;
|
|
+ lrclk_pol = !lrclk_pol;
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ if (lrclk_pol)
|
|
+ ctrl0 |= ADAU1961_SERIAL_PORT0_LRCLK_POL;
|
|
+
|
|
+ /* Set the mask to update all relevant bits in ADAU1961_SERIAL_PORT0 */
|
|
+ ctrl0_mask = ADAU1961_SERIAL_PORT0_MASTER |
|
|
+ ADAU1961_SERIAL_PORT0_LRCLK_POL |
|
|
+ ADAU1961_SERIAL_PORT0_BCLK_POL |
|
|
+ ADAU1961_SERIAL_PORT0_PULSE_MODE;
|
|
+
|
|
+ regmap_update_bits(adau->regmap, ADAU1961_SERIAL_PORT0, ctrl0_mask,
|
|
+ ctrl0);
|
|
+ regmap_update_bits(adau->regmap, ADAU1961_SERIAL_PORT1,
|
|
+ ADAU1961_SERIAL_PORT1_DELAY_MASK, ctrl1);
|
|
+
|
|
+ adau->dai_fmt = fmt & SND_SOC_DAIFMT_FORMAT_MASK;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int adau1961_set_dai_tdm_slot(struct snd_soc_dai *dai,
|
|
+ unsigned int tx_mask,
|
|
+ unsigned int rx_mask,
|
|
+ int slots,
|
|
+ int slot_width)
|
|
+{
|
|
+ struct adau *adau = snd_soc_component_get_drvdata(dai->component);
|
|
+ unsigned int ser_ctrl0, ser_ctrl1;
|
|
+ unsigned int conv_ctrl0, conv_ctrl1;
|
|
+
|
|
+ /* I2S mode */
|
|
+ if (slots == 0) {
|
|
+ slots = 2;
|
|
+ rx_mask = 3;
|
|
+ tx_mask = 3;
|
|
+ slot_width = 32;
|
|
+ }
|
|
+
|
|
+ switch (slots) {
|
|
+ case 2:
|
|
+ ser_ctrl0 = ADAU1961_SERIAL_PORT0_STEREO;
|
|
+ break;
|
|
+ case 4:
|
|
+ ser_ctrl0 = ADAU1961_SERIAL_PORT0_TDM4;
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ switch (slot_width * slots) {
|
|
+ case 32:
|
|
+ ser_ctrl1 = ADAU1961_SERIAL_PORT1_BCLK32;
|
|
+ break;
|
|
+ case 64:
|
|
+ ser_ctrl1 = ADAU1961_SERIAL_PORT1_BCLK64;
|
|
+ break;
|
|
+ case 48:
|
|
+ ser_ctrl1 = ADAU1961_SERIAL_PORT1_BCLK48;
|
|
+ break;
|
|
+ case 128:
|
|
+ ser_ctrl1 = ADAU1961_SERIAL_PORT1_BCLK128;
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ switch (rx_mask) {
|
|
+ case 0x03:
|
|
+ conv_ctrl1 = ADAU1961_CONVERTER1_ADC_PAIR(1);
|
|
+ adau->tdm_slot[SNDRV_PCM_STREAM_CAPTURE] = 0;
|
|
+ break;
|
|
+ case 0x0c:
|
|
+ conv_ctrl1 = ADAU1961_CONVERTER1_ADC_PAIR(2);
|
|
+ adau->tdm_slot[SNDRV_PCM_STREAM_CAPTURE] = 1;
|
|
+ break;
|
|
+ case 0x30:
|
|
+ conv_ctrl1 = ADAU1961_CONVERTER1_ADC_PAIR(3);
|
|
+ adau->tdm_slot[SNDRV_PCM_STREAM_CAPTURE] = 2;
|
|
+ break;
|
|
+ case 0xc0:
|
|
+ conv_ctrl1 = ADAU1961_CONVERTER1_ADC_PAIR(4);
|
|
+ adau->tdm_slot[SNDRV_PCM_STREAM_CAPTURE] = 3;
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ switch (tx_mask) {
|
|
+ case 0x03:
|
|
+ conv_ctrl0 = ADAU1961_CONVERTER0_DAC_PAIR(1);
|
|
+ adau->tdm_slot[SNDRV_PCM_STREAM_PLAYBACK] = 0;
|
|
+ break;
|
|
+ case 0x0c:
|
|
+ conv_ctrl0 = ADAU1961_CONVERTER0_DAC_PAIR(2);
|
|
+ adau->tdm_slot[SNDRV_PCM_STREAM_PLAYBACK] = 1;
|
|
+ break;
|
|
+ case 0x30:
|
|
+ conv_ctrl0 = ADAU1961_CONVERTER0_DAC_PAIR(3);
|
|
+ adau->tdm_slot[SNDRV_PCM_STREAM_PLAYBACK] = 2;
|
|
+ break;
|
|
+ case 0xc0:
|
|
+ conv_ctrl0 = ADAU1961_CONVERTER0_DAC_PAIR(4);
|
|
+ adau->tdm_slot[SNDRV_PCM_STREAM_PLAYBACK] = 3;
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ regmap_update_bits(adau->regmap, ADAU1961_CONVERTER0,
|
|
+ ADAU1961_CONVERTER0_DAC_PAIR_MASK, conv_ctrl0);
|
|
+ regmap_update_bits(adau->regmap, ADAU1961_CONVERTER1,
|
|
+ ADAU1961_CONVERTER1_ADC_PAIR_MASK, conv_ctrl1);
|
|
+ regmap_update_bits(adau->regmap, ADAU1961_SERIAL_PORT0,
|
|
+ ADAU1961_SERIAL_PORT0_TDM_MASK, ser_ctrl0);
|
|
+ regmap_update_bits(adau->regmap, ADAU1961_SERIAL_PORT1,
|
|
+ ADAU1961_SERIAL_PORT1_BCLK_MASK, ser_ctrl1);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int adau1961_startup(struct snd_pcm_substream *substream,
|
|
+ struct snd_soc_dai *dai)
|
|
+{
|
|
+ struct adau *adau = snd_soc_component_get_drvdata(dai->component);
|
|
+ (void)adau;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct snd_soc_dai_ops adau1961_dai_ops = {
|
|
+ .hw_params = adau1961_hw_params,
|
|
+ .set_sysclk = adau1961_set_dai_sysclk,
|
|
+ .set_fmt = adau1961_set_dai_fmt,
|
|
+ .set_pll = adau1961_set_dai_pll,
|
|
+ .set_tdm_slot = adau1961_set_dai_tdm_slot,
|
|
+ .startup = adau1961_startup,
|
|
+};
|
|
+
|
|
+static bool adau1961_precious_register(struct device *dev, unsigned int reg)
|
|
+{
|
|
+ return false;
|
|
+}
|
|
+
|
|
+static bool adau1961_volatile_register(struct device *dev, unsigned int reg)
|
|
+{
|
|
+ switch (reg) {
|
|
+ /* The PLL register is 6 bytes long */
|
|
+ case ADAU1961_PLL_CONTROL:
|
|
+ case ADAU1961_PLL_CONTROL + 1:
|
|
+ case ADAU1961_PLL_CONTROL + 2:
|
|
+ case ADAU1961_PLL_CONTROL + 3:
|
|
+ case ADAU1961_PLL_CONTROL + 4:
|
|
+ case ADAU1961_PLL_CONTROL + 5:
|
|
+ return true;
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ return false;
|
|
+}
|
|
+
|
|
+static int adau1961_add_routes(struct snd_soc_component *component)
|
|
+{
|
|
+ struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component);
|
|
+ struct adau *adau = snd_soc_component_get_drvdata(component);
|
|
+ int ret;
|
|
+
|
|
+ if (adau->clk_src != ADAU1961_CLK_SRC_MCLK)
|
|
+ ret = snd_soc_dapm_add_routes(dapm, &adau1961_dapm_pll_route, 1);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int adau1961_resume(struct snd_soc_component *component)
|
|
+{
|
|
+ struct adau *adau = snd_soc_component_get_drvdata(component);
|
|
+
|
|
+ if (adau->switch_mode)
|
|
+ adau->switch_mode(component->dev);
|
|
+
|
|
+ regcache_sync(adau->regmap);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void adau1961_remove(struct device *dev)
|
|
+{
|
|
+ struct adau *adau = dev_get_drvdata(dev);
|
|
+
|
|
+ clk_disable_unprepare(adau->mclk);
|
|
+}
|
|
+
|
|
+void adau1961_set_vgnd_shorted(struct snd_soc_component *component, bool shorted)
|
|
+{
|
|
+ struct adau *p = snd_soc_component_get_drvdata(component);
|
|
+
|
|
+ struct snd_ctl_elem_value value;
|
|
+ struct snd_kcontrol *k;
|
|
+
|
|
+ long prev_left, prev_right;
|
|
+
|
|
+ if (!p) {
|
|
+ printe("Failed to get adau driver data!");
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ p->vgnd_shorted = shorted;
|
|
+
|
|
+ k = snd_soc_card_get_kcontrol(component->card, "Headphone Playback Switch");
|
|
+ if (!k) {
|
|
+ printe("Failed to get Headphone Playback Switch control!");
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ snd_soc_get_volsw(k, &value);
|
|
+
|
|
+ prev_left = value.value.integer.value[0];
|
|
+ prev_right = value.value.integer.value[1];
|
|
+
|
|
+ if (shorted) {
|
|
+ p->hpl_unmuted = value.value.integer.value[0];
|
|
+ p->hpr_unmuted = value.value.integer.value[1];
|
|
+
|
|
+ value.value.integer.value[0] = 0;
|
|
+ value.value.integer.value[1] = 0;
|
|
+ } else {
|
|
+ value.value.integer.value[0] = p->hpl_unmuted ? 1 : 0;
|
|
+ value.value.integer.value[1] = p->hpr_unmuted ? 1 : 0;
|
|
+ }
|
|
+
|
|
+ if (value.value.integer.value[0] != prev_left ||
|
|
+ value.value.integer.value[1] != prev_right) {
|
|
+ snd_soc_put_volsw(k, &value);
|
|
+ snd_ctl_notify(component->card->snd_card, SNDRV_CTL_EVENT_MASK_VALUE, &k->id);
|
|
+ }
|
|
+
|
|
+ regmap_update_bits(p->regmap, ADAU1961_PLAY_MIXER_MONO,
|
|
+ ADAU1961_PLAY_MIXER_MONO_EN, shorted ? 0 : ADAU1961_PLAY_MIXER_MONO_EN);
|
|
+}
|
|
+EXPORT_SYMBOL(adau1961_set_vgnd_shorted);
|
|
+
|
|
+bool adau1961_is_hp_capless(struct snd_soc_component *component)
|
|
+{
|
|
+ struct adau1961_platform_data *pdata = component->dev->platform_data;
|
|
+
|
|
+ return pdata->headphone_mode == ADAU1961_OUTPUT_MODE_HEADPHONE_CAPLESS;
|
|
+}
|
|
+EXPORT_SYMBOL(adau1961_is_hp_capless);
|
|
+
|
|
+static int upisnd_put_hp_mute_volsw(struct snd_kcontrol *kcontrol,
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
|
|
+ struct adau *adau = snd_soc_component_get_drvdata(component);
|
|
+
|
|
+ if (adau->vgnd_shorted) {
|
|
+ adau->hpl_unmuted = ucontrol->value.integer.value[0];
|
|
+ adau->hpr_unmuted = ucontrol->value.integer.value[1];
|
|
+
|
|
+ if (ucontrol->value.integer.value[0] != 0 ||
|
|
+ ucontrol->value.integer.value[1] != 0) {
|
|
+ printe("Ignoring unmute request while VGND is shorted!");
|
|
+
|
|
+ // Ensure alsamixer shows the correct state.
|
|
+ snd_ctl_notify(component->card->snd_card,
|
|
+ SNDRV_CTL_EVENT_MASK_VALUE,
|
|
+ &kcontrol->id
|
|
+ );
|
|
+ return 0;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return snd_soc_put_volsw(kcontrol, ucontrol);
|
|
+}
|
|
+
|
|
+static const struct reg_default adau1961_reg_defaults[] = {
|
|
+ { ADAU1961_DEJITTER, 0x03 },
|
|
+ { ADAU1961_REC_MIXER_LEFT0, 0x01 },
|
|
+ { ADAU1961_REC_MIXER_LEFT1, 0x00 },
|
|
+ { ADAU1961_REC_MIXER_RIGHT0, 0x01 },
|
|
+ { ADAU1961_REC_MIXER_RIGHT1, 0x00 },
|
|
+ { ADAU1961_LEFT_DIFF_INPUT_VOL, 0x00 },
|
|
+ { ADAU1961_ALC_CTRL0, 0x00 },
|
|
+ { ADAU1961_ALC_CTRL1, 0x00 },
|
|
+ { ADAU1961_ALC_CTRL2, 0x00 },
|
|
+ { ADAU1961_ALC_CTRL3, 0x00 },
|
|
+ { ADAU1961_RIGHT_DIFF_INPUT_VOL, 0x00 },
|
|
+ { ADAU1961_PLAY_LR_MIXER_LEFT, 0x01 },
|
|
+ { ADAU1961_PLAY_MIXER_LEFT0, 0x00 },
|
|
+ { ADAU1961_PLAY_MIXER_LEFT1, 0x00 },
|
|
+ { ADAU1961_PLAY_MIXER_RIGHT0, 0x00 },
|
|
+ { ADAU1961_PLAY_MIXER_RIGHT1, 0x00 },
|
|
+ { ADAU1961_PLAY_LR_MIXER_RIGHT, 0x01 },
|
|
+ { ADAU1961_PLAY_MIXER_MONO, 0x00 },
|
|
+ { ADAU1961_PLAY_HP_LEFT_VOL, 0x00 },
|
|
+ { ADAU1961_PLAY_HP_RIGHT_VOL, 0x00 },
|
|
+ { ADAU1961_PLAY_LINE_LEFT_VOL, 0x00 },
|
|
+ { ADAU1961_PLAY_LINE_RIGHT_VOL, 0x00 },
|
|
+ { ADAU1961_PLAY_MONO_OUTPUT_VOL, 0x02 },
|
|
+ { ADAU1961_POP_CLICK_SUPPRESS, 0x00 },
|
|
+ { ADAU1961_JACK_DETECT_PIN, 0x00 },
|
|
+ { ADAU1961_CLOCK_CONTROL, 0x00 },
|
|
+ { ADAU1961_PLL_CONTROL, 0x00 },
|
|
+ { ADAU1961_MICBIAS, 0x00 },
|
|
+ { ADAU1961_SERIAL_PORT0, 0x00 },
|
|
+ { ADAU1961_SERIAL_PORT1, 0x00 },
|
|
+ { ADAU1961_CONVERTER0, 0x00 },
|
|
+ { ADAU1961_CONVERTER1, 0x00 },
|
|
+ { ADAU1961_LEFT_INPUT_DIGITAL_VOL, 0x00 },
|
|
+ { ADAU1961_RIGHT_INPUT_DIGITAL_VOL, 0x00 },
|
|
+ { ADAU1961_ADC_CONTROL, 0x00 },
|
|
+ { ADAU1961_PLAY_POWER_MGMT, 0x03 },
|
|
+ { ADAU1961_DAC_CONTROL0, 0x00 },
|
|
+ { ADAU1961_DAC_CONTROL1, 0x00 },
|
|
+ { ADAU1961_DAC_CONTROL2, 0x00 },
|
|
+ { ADAU1961_SERIAL_PORT_PAD, 0xaa },
|
|
+ { ADAU1961_CONTROL_PORT_PAD0, 0xaa },
|
|
+ { ADAU1961_CONTROL_PORT_PAD1, 0x00 },
|
|
+};
|
|
+
|
|
+static const DECLARE_TLV_DB_RANGE(adau1961_mono_output_tlv,
|
|
+ 1, 1, TLV_DB_SCALE_ITEM(0, 0, 0), // 0dB MX7[2:1]=01
|
|
+ 2, 2, TLV_DB_SCALE_ITEM(600, 0, 0) // +6dB MX7[2:1]=10
|
|
+);
|
|
+
|
|
+static const DECLARE_TLV_DB_SCALE(adau1961_sing_in_tlv, -1500, 300, 1);
|
|
+static const DECLARE_TLV_DB_SCALE(adau1961_diff_in_tlv, -1200, 75, 0);
|
|
+static const DECLARE_TLV_DB_SCALE(adau1961_out_tlv, -5700, 100, 0);
|
|
+static const DECLARE_TLV_DB_SCALE(adau1961_sidetone_tlv, -1800, 300, 1);
|
|
+static const DECLARE_TLV_DB_SCALE(adau1961_boost_tlv, -600, 600, 1);
|
|
+static const DECLARE_TLV_DB_SCALE(adau1961_pga_boost_tlv, -2000, 2000, 1);
|
|
+
|
|
+static const DECLARE_TLV_DB_SCALE(adau1961_alc_max_gain_tlv, -1200, 600, 0);
|
|
+static const DECLARE_TLV_DB_SCALE(adau1961_alc_target_tlv, -2850, 150, 0);
|
|
+static const DECLARE_TLV_DB_SCALE(adau1961_alc_ng_threshold_tlv, -7650, 150, 0);
|
|
+
|
|
+static const unsigned int adau1961_pga_slew_time_values[] = {
|
|
+ 3, 0, 1, 2,
|
|
+};
|
|
+
|
|
+static const char * const adau1961_pga_slew_time_text[] = {
|
|
+ "Off",
|
|
+ "24 ms",
|
|
+ "48 ms",
|
|
+ "96 ms",
|
|
+};
|
|
+
|
|
+static const char * const adau1961_alc_function_text[] = {
|
|
+ "Off",
|
|
+ "Right",
|
|
+ "Left",
|
|
+ "Stereo",
|
|
+};
|
|
+
|
|
+static const char * const adau1961_alc_hold_time_text[] = {
|
|
+ "2.67 ms",
|
|
+ "5.34 ms",
|
|
+ "10.68 ms",
|
|
+ "21.36 ms",
|
|
+ "42.72 ms",
|
|
+ "85.44 ms",
|
|
+ "170.88 ms",
|
|
+ "341.76 ms",
|
|
+ "683.52 ms",
|
|
+ "1367 ms",
|
|
+ "2734.1 ms",
|
|
+ "5468.2 ms",
|
|
+ "10936 ms",
|
|
+ "21873 ms",
|
|
+ "43745 ms",
|
|
+ "87491 ms",
|
|
+};
|
|
+
|
|
+static const char * const adau1961_alc_attack_time_text[] = {
|
|
+ "6 ms",
|
|
+ "12 ms",
|
|
+ "24 ms",
|
|
+ "48 ms",
|
|
+ "96 ms",
|
|
+ "192 ms",
|
|
+ "384 ms",
|
|
+ "768 ms",
|
|
+ "1540 ms",
|
|
+ "3070 ms",
|
|
+ "6140 ms",
|
|
+ "12290 ms",
|
|
+ "24580 ms",
|
|
+ "49150 ms",
|
|
+ "98300 ms",
|
|
+ "196610 ms",
|
|
+};
|
|
+
|
|
+static const char * const adau1961_alc_decay_time_text[] = {
|
|
+ "24 ms",
|
|
+ "48 ms",
|
|
+ "96 ms",
|
|
+ "192 ms",
|
|
+ "384 ms",
|
|
+ "768 ms",
|
|
+ "15400 ms",
|
|
+ "30700 ms",
|
|
+ "61400 ms",
|
|
+ "12290 ms",
|
|
+ "24580 ms",
|
|
+ "49150 ms",
|
|
+ "98300 ms",
|
|
+ "196610 ms",
|
|
+ "393220 ms",
|
|
+ "786430 ms",
|
|
+};
|
|
+
|
|
+static const char * const adau1961_alc_ng_type_text[] = {
|
|
+ "Hold",
|
|
+ "Mute",
|
|
+ "Fade",
|
|
+ "Fade + Mute",
|
|
+};
|
|
+
|
|
+static SOC_VALUE_ENUM_SINGLE_DECL(adau1961_pga_slew_time_enum,
|
|
+ ADAU1961_ALC_CTRL0, 6, 0x3, adau1961_pga_slew_time_text,
|
|
+ adau1961_pga_slew_time_values);
|
|
+static SOC_ENUM_SINGLE_DECL(adau1961_alc_function_enum,
|
|
+ ADAU1961_ALC_CTRL0, 0, adau1961_alc_function_text);
|
|
+static SOC_ENUM_SINGLE_DECL(adau1961_alc_hold_time_enum,
|
|
+ ADAU1961_ALC_CTRL1, 4, adau1961_alc_hold_time_text);
|
|
+static SOC_ENUM_SINGLE_DECL(adau1961_alc_attack_time_enum,
|
|
+ ADAU1961_ALC_CTRL2, 4, adau1961_alc_attack_time_text);
|
|
+static SOC_ENUM_SINGLE_DECL(adau1961_alc_decay_time_enum,
|
|
+ ADAU1961_ALC_CTRL2, 0, adau1961_alc_decay_time_text);
|
|
+static SOC_ENUM_SINGLE_DECL(adau1961_alc_ng_type_enum,
|
|
+ ADAU1961_ALC_CTRL3, 6, adau1961_alc_ng_type_text);
|
|
+
|
|
+static const struct snd_kcontrol_new adau1961_differential_mode_controls[] = {
|
|
+ SOC_DOUBLE_R_TLV("Capture Volume", ADAU1961_LEFT_DIFF_INPUT_VOL,
|
|
+ ADAU1961_RIGHT_DIFF_INPUT_VOL, 2, 0x3f, 0,
|
|
+ adau1961_diff_in_tlv),
|
|
+ SOC_DOUBLE_R("Capture Switch", ADAU1961_LEFT_DIFF_INPUT_VOL,
|
|
+ ADAU1961_RIGHT_DIFF_INPUT_VOL, 1, 1, 0),
|
|
+
|
|
+ SOC_DOUBLE_R_TLV("PGA Boost Capture Volume", ADAU1961_REC_MIXER_LEFT1,
|
|
+ ADAU1961_REC_MIXER_RIGHT1, 3, 2, 0, adau1961_pga_boost_tlv),
|
|
+
|
|
+ SOC_ENUM("PGA Capture Slew Time", adau1961_pga_slew_time_enum),
|
|
+
|
|
+ SOC_SINGLE_TLV("ALC Capture Max Gain Volume", ADAU1961_ALC_CTRL0,
|
|
+ 3, 7, 0, adau1961_alc_max_gain_tlv),
|
|
+ SOC_ENUM("ALC Capture Function", adau1961_alc_function_enum),
|
|
+ SOC_ENUM("ALC Capture Hold Time", adau1961_alc_hold_time_enum),
|
|
+ SOC_SINGLE_TLV("ALC Capture Target Volume", ADAU1961_ALC_CTRL1,
|
|
+ 0, 15, 0, adau1961_alc_target_tlv),
|
|
+ SOC_ENUM("ALC Capture Attack Time", adau1961_alc_decay_time_enum),
|
|
+ SOC_ENUM("ALC Capture Decay Time", adau1961_alc_attack_time_enum),
|
|
+ SOC_ENUM("ALC Capture Noise Gate Type", adau1961_alc_ng_type_enum),
|
|
+ SOC_SINGLE("ALC Capture Noise Gate Switch",
|
|
+ ADAU1961_ALC_CTRL3, 5, 1, 0),
|
|
+ SOC_SINGLE_TLV("ALC Capture Noise Gate Threshold Volume",
|
|
+ ADAU1961_ALC_CTRL3, 0, 31, 0, adau1961_alc_ng_threshold_tlv),
|
|
+};
|
|
+
|
|
+static const struct snd_kcontrol_new adau1961_single_mode_controls[] = {
|
|
+ SOC_SINGLE_TLV("Input 1 Capture Volume", ADAU1961_REC_MIXER_LEFT0,
|
|
+ 4, 7, 0, adau1961_sing_in_tlv),
|
|
+ SOC_SINGLE_TLV("Input 2 Capture Volume", ADAU1961_REC_MIXER_LEFT0,
|
|
+ 1, 7, 0, adau1961_sing_in_tlv),
|
|
+ SOC_SINGLE_TLV("Input 3 Capture Volume", ADAU1961_REC_MIXER_RIGHT0,
|
|
+ 4, 7, 0, adau1961_sing_in_tlv),
|
|
+ SOC_SINGLE_TLV("Input 4 Capture Volume", ADAU1961_REC_MIXER_RIGHT0,
|
|
+ 1, 7, 0, adau1961_sing_in_tlv),
|
|
+};
|
|
+
|
|
+static const struct snd_kcontrol_new adau1961_mono_controls[] = {
|
|
+ SOC_SINGLE_TLV("Mono Playback Volume", ADAU1961_PLAY_MONO_OUTPUT_VOL,
|
|
+ 2, 0x3f, 0, adau1961_out_tlv),
|
|
+ SOC_SINGLE("Mono Playback Switch", ADAU1961_PLAY_MONO_OUTPUT_VOL,
|
|
+ 1, 1, 0),
|
|
+ SOC_SINGLE_TLV("Mono Playback Boost Volume", ADAU1961_PLAY_MIXER_MONO,
|
|
+ 1, 2, 0, adau1961_mono_output_tlv),
|
|
+};
|
|
+
|
|
+static const struct snd_kcontrol_new adau1961_left_mixer_controls[] = {
|
|
+ SOC_DAPM_SINGLE_AUTODISABLE("Left DAC Switch",
|
|
+ ADAU1961_PLAY_MIXER_LEFT0, 5, 1, 0),
|
|
+ SOC_DAPM_SINGLE_AUTODISABLE("Right DAC Switch",
|
|
+ ADAU1961_PLAY_MIXER_LEFT0, 6, 1, 0),
|
|
+ SOC_DAPM_SINGLE_TLV("Aux Bypass Volume",
|
|
+ ADAU1961_PLAY_MIXER_LEFT0, 1, 8, 0, adau1961_sidetone_tlv),
|
|
+ SOC_DAPM_SINGLE_TLV("Right Bypass Volume",
|
|
+ ADAU1961_PLAY_MIXER_LEFT1, 4, 8, 0, adau1961_sidetone_tlv),
|
|
+ SOC_DAPM_SINGLE_TLV("Left Bypass Volume",
|
|
+ ADAU1961_PLAY_MIXER_LEFT1, 0, 8, 0, adau1961_sidetone_tlv),
|
|
+};
|
|
+
|
|
+static const struct snd_kcontrol_new adau1961_right_mixer_controls[] = {
|
|
+ SOC_DAPM_SINGLE_AUTODISABLE("Left DAC Switch",
|
|
+ ADAU1961_PLAY_MIXER_RIGHT0, 5, 1, 0),
|
|
+ SOC_DAPM_SINGLE_AUTODISABLE("Right DAC Switch",
|
|
+ ADAU1961_PLAY_MIXER_RIGHT0, 6, 1, 0),
|
|
+ SOC_DAPM_SINGLE_TLV("Aux Bypass Volume",
|
|
+ ADAU1961_PLAY_MIXER_RIGHT0, 1, 8, 0, adau1961_sidetone_tlv),
|
|
+ SOC_DAPM_SINGLE_TLV("Right Bypass Volume",
|
|
+ ADAU1961_PLAY_MIXER_RIGHT1, 4, 8, 0, adau1961_sidetone_tlv),
|
|
+ SOC_DAPM_SINGLE_TLV("Left Bypass Volume",
|
|
+ ADAU1961_PLAY_MIXER_RIGHT1, 0, 8, 0, adau1961_sidetone_tlv),
|
|
+};
|
|
+
|
|
+static const struct snd_kcontrol_new adau1961_left_lr_mixer_controls[] = {
|
|
+ SOC_DAPM_SINGLE_TLV("Left Volume",
|
|
+ ADAU1961_PLAY_LR_MIXER_LEFT, 1, 2, 0, adau1961_boost_tlv),
|
|
+ SOC_DAPM_SINGLE_TLV("Right Volume",
|
|
+ ADAU1961_PLAY_LR_MIXER_LEFT, 3, 2, 0, adau1961_boost_tlv),
|
|
+};
|
|
+
|
|
+static const struct snd_kcontrol_new adau1961_right_lr_mixer_controls[] = {
|
|
+ SOC_DAPM_SINGLE_TLV("Left Volume",
|
|
+ ADAU1961_PLAY_LR_MIXER_RIGHT, 1, 2, 0, adau1961_boost_tlv),
|
|
+ SOC_DAPM_SINGLE_TLV("Right Volume",
|
|
+ ADAU1961_PLAY_LR_MIXER_RIGHT, 3, 2, 0, adau1961_boost_tlv),
|
|
+};
|
|
+
|
|
+static const struct snd_kcontrol_new adau1961_controls[] = {
|
|
+ SOC_DOUBLE_R_TLV("Digital Capture Volume",
|
|
+ ADAU1961_LEFT_INPUT_DIGITAL_VOL,
|
|
+ ADAU1961_RIGHT_INPUT_DIGITAL_VOL,
|
|
+ 0, 0xff, 1, adau1961_digital_tlv),
|
|
+ SOC_DOUBLE_R_TLV("Digital Playback Volume", ADAU1961_DAC_CONTROL1,
|
|
+ ADAU1961_DAC_CONTROL2, 0, 0xff, 1, adau1961_digital_tlv),
|
|
+
|
|
+ SOC_SINGLE("ADC High Pass Filter Switch", ADAU1961_ADC_CONTROL,
|
|
+ 5, 1, 0),
|
|
+ SOC_SINGLE("Playback De-emphasis Switch", ADAU1961_DAC_CONTROL0,
|
|
+ 2, 1, 0),
|
|
+
|
|
+ SOC_DOUBLE_R_TLV("Aux Capture Volume", ADAU1961_REC_MIXER_LEFT1,
|
|
+ ADAU1961_REC_MIXER_RIGHT1, 0, 7, 0, adau1961_sing_in_tlv),
|
|
+
|
|
+ SOC_DOUBLE_R_TLV("Headphone Playback Volume", ADAU1961_PLAY_HP_LEFT_VOL,
|
|
+ ADAU1961_PLAY_HP_RIGHT_VOL, 2, 0x3f, 0, adau1961_out_tlv),
|
|
+ {
|
|
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
+ .name = "Headphone Playback Switch",
|
|
+ .info = snd_soc_info_volsw,
|
|
+ .get = snd_soc_get_volsw,
|
|
+ .put = upisnd_put_hp_mute_volsw,
|
|
+ .private_value = SOC_DOUBLE_R_VALUE(ADAU1961_PLAY_HP_LEFT_VOL,
|
|
+ ADAU1961_PLAY_HP_RIGHT_VOL, 1, 1, 0)
|
|
+ },
|
|
+ SOC_DOUBLE_R_TLV("Lineout Playback Volume", ADAU1961_PLAY_LINE_LEFT_VOL,
|
|
+ ADAU1961_PLAY_LINE_RIGHT_VOL, 2, 0x3f, 0, adau1961_out_tlv),
|
|
+ SOC_DOUBLE_R("Lineout Playback Switch", ADAU1961_PLAY_LINE_LEFT_VOL,
|
|
+ ADAU1961_PLAY_LINE_RIGHT_VOL, 1, 1, 0),
|
|
+ SOC_SINGLE("MicBias Switch", ADAU1961_MICBIAS, 0, 1, 0),
|
|
+};
|
|
+
|
|
+static int adau1961_dejitter_fixup(struct snd_soc_dapm_widget *w,
|
|
+ struct snd_kcontrol *kcontrol, int event)
|
|
+{
|
|
+ struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
|
|
+ struct adau *adau = snd_soc_component_get_drvdata(component);
|
|
+
|
|
+ // After any power changes have been made the dejitter circuit
|
|
+ // has to be reinitialized.
|
|
+ regmap_write(adau->regmap, ADAU1961_DEJITTER, 0);
|
|
+ if (!adau->master)
|
|
+ regmap_write(adau->regmap, ADAU1961_DEJITTER, 3);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct snd_soc_dapm_widget adau1961_dapm_widgets[] = {
|
|
+ SND_SOC_DAPM_MIXER("Left Input Mixer", SND_SOC_NOPM, 0, 0,
|
|
+ NULL, 0),
|
|
+ SND_SOC_DAPM_MIXER("Right Input Mixer", SND_SOC_NOPM, 0, 0,
|
|
+ NULL, 0),
|
|
+
|
|
+ SND_SOC_DAPM_SUPPLY("MICBIAS", ADAU1961_MICBIAS, 0, 0, NULL, 0),
|
|
+
|
|
+ SOC_MIXER_ARRAY("Left Playback Mixer", ADAU1961_PLAY_MIXER_LEFT0,
|
|
+ 0, 0, adau1961_left_mixer_controls),
|
|
+ SOC_MIXER_ARRAY("Right Playback Mixer", ADAU1961_PLAY_MIXER_RIGHT0,
|
|
+ 0, 0, adau1961_right_mixer_controls),
|
|
+
|
|
+ SOC_MIXER_ARRAY("Left LR Playback Mixer", SND_SOC_NOPM,
|
|
+ 0, 0, adau1961_left_lr_mixer_controls),
|
|
+ SOC_MIXER_ARRAY("Right LR Playback Mixer", SND_SOC_NOPM,
|
|
+ 0, 0, adau1961_right_lr_mixer_controls),
|
|
+
|
|
+ SND_SOC_DAPM_SUPPLY("Headphone", ADAU1961_PLAY_HP_LEFT_VOL,
|
|
+ 0, 0, NULL, 0),
|
|
+
|
|
+ SND_SOC_DAPM_SUPPLY_S("SYSCLK", 2, SND_SOC_NOPM, 0, 0, NULL, 0),
|
|
+
|
|
+ SND_SOC_DAPM_POST("Dejitter fixup", adau1961_dejitter_fixup),
|
|
+
|
|
+ SND_SOC_DAPM_INPUT("LAUX"),
|
|
+ SND_SOC_DAPM_INPUT("RAUX"),
|
|
+ SND_SOC_DAPM_INPUT("LINP"),
|
|
+ SND_SOC_DAPM_INPUT("LINN"),
|
|
+ SND_SOC_DAPM_INPUT("RINP"),
|
|
+ SND_SOC_DAPM_INPUT("RINN"),
|
|
+
|
|
+ SND_SOC_DAPM_OUTPUT("LOUT"),
|
|
+ SND_SOC_DAPM_OUTPUT("ROUT"),
|
|
+ SND_SOC_DAPM_OUTPUT("LHP"),
|
|
+ SND_SOC_DAPM_OUTPUT("RHP"),
|
|
+
|
|
+ SND_SOC_DAPM_SUPPLY_S("PLL", 3, SND_SOC_NOPM, 0, 0, adau1961_pll_event,
|
|
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
|
|
+
|
|
+ SND_SOC_DAPM_SUPPLY("AIFCLK", SND_SOC_NOPM, 0, 0, NULL, 0),
|
|
+
|
|
+ SND_SOC_DAPM_SUPPLY("Left Playback Enable", SND_SOC_NOPM,
|
|
+ 0, 0, NULL, 0),
|
|
+ SND_SOC_DAPM_SUPPLY("Right Playback Enable", SND_SOC_NOPM,
|
|
+ 1, 0, NULL, 0),
|
|
+
|
|
+ SND_SOC_DAPM_MUX("Left DAC Mode Mux", SND_SOC_NOPM, 0, 0,
|
|
+ &adau1961_dac_mode_mux),
|
|
+ SND_SOC_DAPM_MUX("Right DAC Mode Mux", SND_SOC_NOPM, 0, 0,
|
|
+ &adau1961_dac_mode_mux),
|
|
+
|
|
+ SND_SOC_DAPM_ADC_E("Left Decimator", NULL, ADAU1961_ADC_CONTROL, 0, 0,
|
|
+ adau1961_adc_fixup, SND_SOC_DAPM_POST_PMU),
|
|
+ SND_SOC_DAPM_ADC("Right Decimator", NULL, ADAU1961_ADC_CONTROL, 1, 0),
|
|
+ SND_SOC_DAPM_DAC("Left DAC", NULL, ADAU1961_DAC_CONTROL0, 0, 0),
|
|
+ SND_SOC_DAPM_DAC("Right DAC", NULL, ADAU1961_DAC_CONTROL0, 1, 0),
|
|
+};
|
|
+
|
|
+static const struct snd_kcontrol_new adau1961_mono_mixer_controls[] = {
|
|
+ SOC_DAPM_SINGLE_TLV("Mono Mixer Gain", ADAU1961_PLAY_MIXER_MONO,
|
|
+ 1, 2, 0, adau1961_mono_output_tlv),
|
|
+};
|
|
+
|
|
+static const struct snd_soc_dapm_widget adau1961_mono_dapm_widgets[] = {
|
|
+ SND_SOC_DAPM_MIXER("Mono Playback Mixer", ADAU1961_PLAY_MIXER_MONO,
|
|
+ 0, 0, adau1961_mono_mixer_controls,
|
|
+ ARRAY_SIZE(adau1961_mono_mixer_controls)),
|
|
+
|
|
+ SND_SOC_DAPM_OUTPUT("MONOOUT"),
|
|
+};
|
|
+
|
|
+static const struct snd_soc_dapm_widget adau1961_capless_dapm_widgets[] = {
|
|
+ SND_SOC_DAPM_SUPPLY_S("Headphone VGND", 1, ADAU1961_PLAY_MIXER_MONO,
|
|
+ 0, 0, NULL, 0),
|
|
+};
|
|
+
|
|
+static const struct snd_soc_dapm_route adau1961_dapm_routes[] = {
|
|
+ { "Left Input Mixer", NULL, "LINP" },
|
|
+ { "Left Input Mixer", NULL, "LINN" },
|
|
+ { "Left Input Mixer", NULL, "LAUX" },
|
|
+
|
|
+ { "Right Input Mixer", NULL, "RINP" },
|
|
+ { "Right Input Mixer", NULL, "RINN" },
|
|
+ { "Right Input Mixer", NULL, "RAUX" },
|
|
+
|
|
+ { "Left Input Mixer", NULL, "MICBIAS" },
|
|
+ { "Right Input Mixer", NULL, "MICBIAS" },
|
|
+
|
|
+ { "Left Playback Mixer", NULL, "Left Playback Enable"},
|
|
+ { "Right Playback Mixer", NULL, "Right Playback Enable"},
|
|
+ { "Left LR Playback Mixer", NULL, "Left Playback Enable"},
|
|
+ { "Right LR Playback Mixer", NULL, "Right Playback Enable"},
|
|
+
|
|
+ { "Left Playback Mixer", "Left DAC Switch", "Left DAC" },
|
|
+ { "Left Playback Mixer", "Right DAC Switch", "Right DAC" },
|
|
+
|
|
+ { "Right Playback Mixer", "Left DAC Switch", "Left DAC" },
|
|
+ { "Right Playback Mixer", "Right DAC Switch", "Right DAC" },
|
|
+
|
|
+ { "Left LR Playback Mixer", "Left Volume", "Left Playback Mixer" },
|
|
+ { "Left LR Playback Mixer", "Right Volume", "Right Playback Mixer" },
|
|
+
|
|
+ { "Right LR Playback Mixer", "Left Volume", "Left Playback Mixer" },
|
|
+ { "Right LR Playback Mixer", "Right Volume", "Right Playback Mixer" },
|
|
+
|
|
+ { "LHP", NULL, "Left Playback Mixer" },
|
|
+ { "RHP", NULL, "Right Playback Mixer" },
|
|
+
|
|
+ { "LHP", NULL, "Headphone" },
|
|
+ { "RHP", NULL, "Headphone" },
|
|
+
|
|
+ { "LOUT", NULL, "Left LR Playback Mixer" },
|
|
+ { "ROUT", NULL, "Right LR Playback Mixer" },
|
|
+
|
|
+ { "Left Playback Mixer", "Aux Bypass Volume", "LAUX" },
|
|
+ { "Left Playback Mixer", "Left Bypass Volume", "Left Input Mixer" },
|
|
+ { "Left Playback Mixer", "Right Bypass Volume", "Right Input Mixer" },
|
|
+ { "Right Playback Mixer", "Aux Bypass Volume", "RAUX" },
|
|
+ { "Right Playback Mixer", "Left Bypass Volume", "Left Input Mixer" },
|
|
+ { "Right Playback Mixer", "Right Bypass Volume", "Right Input Mixer" },
|
|
+
|
|
+ { "Left Decimator", NULL, "SYSCLK" },
|
|
+ { "Right Decimator", NULL, "SYSCLK" },
|
|
+ { "Left DAC", NULL, "SYSCLK" },
|
|
+ { "Right DAC", NULL, "SYSCLK" },
|
|
+ { "Capture", NULL, "SYSCLK" },
|
|
+ { "Playback", NULL, "SYSCLK" },
|
|
+
|
|
+ { "Left DAC", NULL, "Left DAC Mode Mux" },
|
|
+ { "Right DAC", NULL, "Right DAC Mode Mux" },
|
|
+
|
|
+ { "Capture", NULL, "AIFCLK" },
|
|
+ { "Playback", NULL, "AIFCLK" },
|
|
+
|
|
+ { "Left DAC Mode Mux", "Stereo", "Playback" },
|
|
+ { "Left DAC Mode Mux", "Mono (L+R)", "Playback" },
|
|
+ { "Left DAC Mode Mux", "Mono Left Channel (L+R)", "Playback" },
|
|
+ { "Right DAC Mode Mux", "Stereo", "Playback" },
|
|
+ { "Right DAC Mode Mux", "Mono (L+R)", "Playback" },
|
|
+ { "Right DAC Mode Mux", "Mono Right Channel (L+R)", "Playback" },
|
|
+ { "Capture", NULL, "Left Decimator" },
|
|
+ { "Capture", NULL, "Right Decimator" },
|
|
+
|
|
+ { "Left Decimator", NULL, "Left Input Mixer" },
|
|
+ { "Right Decimator", NULL, "Right Input Mixer" },
|
|
+};
|
|
+
|
|
+static const struct snd_soc_dapm_route adau1961_mono_dapm_routes[] = {
|
|
+ { "Mono Playback Mixer", NULL, "Left Playback Mixer" },
|
|
+ { "Mono Playback Mixer", NULL, "Right Playback Mixer" },
|
|
+
|
|
+ { "MONOOUT", NULL, "Mono Playback Mixer" },
|
|
+};
|
|
+
|
|
+static const struct snd_soc_dapm_route adau1961_capless_dapm_routes[] = {
|
|
+ { "Headphone", NULL, "Headphone VGND" },
|
|
+};
|
|
+
|
|
+static int adau1961_set_bias_level(struct snd_soc_component *component,
|
|
+ enum snd_soc_bias_level level)
|
|
+{
|
|
+ struct adau *adau = snd_soc_component_get_drvdata(component);
|
|
+
|
|
+ switch (level) {
|
|
+ case SND_SOC_BIAS_ON:
|
|
+ break;
|
|
+ case SND_SOC_BIAS_PREPARE:
|
|
+ break;
|
|
+ case SND_SOC_BIAS_STANDBY:
|
|
+ regcache_cache_only(adau->regmap, false);
|
|
+ regmap_update_bits(adau->regmap, ADAU1961_CLOCK_CONTROL,
|
|
+ ADAU1961_CLOCK_CONTROL_SYSCLK_EN,
|
|
+ ADAU1961_CLOCK_CONTROL_SYSCLK_EN);
|
|
+ if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF)
|
|
+ regcache_sync(adau->regmap);
|
|
+ break;
|
|
+ case SND_SOC_BIAS_OFF:
|
|
+ regmap_update_bits(adau->regmap, ADAU1961_CLOCK_CONTROL,
|
|
+ ADAU1961_CLOCK_CONTROL_SYSCLK_EN, 0);
|
|
+ regcache_cache_only(adau->regmap, true);
|
|
+ break;
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static enum adau1961_output_mode adau1961_get_lineout_mode(struct snd_soc_component *component)
|
|
+{
|
|
+ struct adau1961_platform_data *pdata = component->dev->platform_data;
|
|
+
|
|
+ if (pdata)
|
|
+ return pdata->lineout_mode;
|
|
+
|
|
+ return ADAU1961_OUTPUT_MODE_LINE;
|
|
+}
|
|
+
|
|
+static enum adau1961_output_mode adau1961_get_monoout_mode(struct snd_soc_component *component)
|
|
+{
|
|
+ struct adau1961_platform_data *pdata = component->dev->platform_data;
|
|
+
|
|
+ if (pdata)
|
|
+ return pdata->monoout_mode;
|
|
+
|
|
+ return ADAU1961_OUTPUT_MODE_LINE;
|
|
+}
|
|
+
|
|
+static int adau1961_setup_headphone_mode(struct snd_soc_component *component)
|
|
+{
|
|
+ struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component);
|
|
+ struct adau *adau = snd_soc_component_get_drvdata(component);
|
|
+ struct adau1961_platform_data *pdata = component->dev->platform_data;
|
|
+ enum adau1961_output_mode mode;
|
|
+ int ret;
|
|
+
|
|
+ if (pdata)
|
|
+ mode = pdata->headphone_mode;
|
|
+ else
|
|
+ mode = ADAU1961_OUTPUT_MODE_HEADPHONE;
|
|
+
|
|
+ switch (mode) {
|
|
+ case ADAU1961_OUTPUT_MODE_LINE:
|
|
+ break;
|
|
+ case ADAU1961_OUTPUT_MODE_HEADPHONE_CAPLESS:
|
|
+ regmap_update_bits(adau->regmap, ADAU1961_PLAY_MONO_OUTPUT_VOL,
|
|
+ ADAU1961_PLAY_MONO_OUTPUT_VOL_MODE_HP |
|
|
+ ADAU1961_PLAY_MONO_OUTPUT_VOL_UNMUTE,
|
|
+ ADAU1961_PLAY_MONO_OUTPUT_VOL_MODE_HP |
|
|
+ ADAU1961_PLAY_MONO_OUTPUT_VOL_UNMUTE);
|
|
+ fallthrough;
|
|
+ case ADAU1961_OUTPUT_MODE_HEADPHONE:
|
|
+ regmap_update_bits(adau->regmap, ADAU1961_PLAY_HP_RIGHT_VOL,
|
|
+ ADAU1961_PLAY_HP_RIGHT_VOL_MODE_HP,
|
|
+ ADAU1961_PLAY_HP_RIGHT_VOL_MODE_HP);
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ if (mode == ADAU1961_OUTPUT_MODE_HEADPHONE_CAPLESS) {
|
|
+ ret = snd_soc_dapm_new_controls(dapm,
|
|
+ adau1961_capless_dapm_widgets,
|
|
+ ARRAY_SIZE(adau1961_capless_dapm_widgets));
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ ret = snd_soc_dapm_add_routes(dapm,
|
|
+ adau1961_capless_dapm_routes,
|
|
+ ARRAY_SIZE(adau1961_capless_dapm_routes));
|
|
+ } else {
|
|
+ ret = snd_soc_add_component_controls(component, adau1961_mono_controls,
|
|
+ ARRAY_SIZE(adau1961_mono_controls));
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ ret = snd_soc_dapm_new_controls(dapm,
|
|
+ adau1961_mono_dapm_widgets,
|
|
+ ARRAY_SIZE(adau1961_mono_dapm_widgets));
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ ret = snd_soc_dapm_add_routes(dapm,
|
|
+ adau1961_mono_dapm_routes,
|
|
+ ARRAY_SIZE(adau1961_mono_dapm_routes));
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static bool adau1961_readable_register(struct device *dev, unsigned int reg)
|
|
+{
|
|
+ switch (reg) {
|
|
+ case ADAU1961_REC_MIXER_LEFT0:
|
|
+ case ADAU1961_REC_MIXER_LEFT1:
|
|
+ case ADAU1961_REC_MIXER_RIGHT0:
|
|
+ case ADAU1961_REC_MIXER_RIGHT1:
|
|
+ case ADAU1961_LEFT_DIFF_INPUT_VOL:
|
|
+ case ADAU1961_RIGHT_DIFF_INPUT_VOL:
|
|
+ case ADAU1961_PLAY_LR_MIXER_LEFT:
|
|
+ case ADAU1961_PLAY_MIXER_LEFT0:
|
|
+ case ADAU1961_PLAY_MIXER_LEFT1:
|
|
+ case ADAU1961_PLAY_MIXER_RIGHT0:
|
|
+ case ADAU1961_PLAY_MIXER_RIGHT1:
|
|
+ case ADAU1961_PLAY_LR_MIXER_RIGHT:
|
|
+ case ADAU1961_PLAY_MIXER_MONO:
|
|
+ case ADAU1961_PLAY_HP_LEFT_VOL:
|
|
+ case ADAU1961_PLAY_HP_RIGHT_VOL:
|
|
+ case ADAU1961_PLAY_LINE_LEFT_VOL:
|
|
+ case ADAU1961_PLAY_LINE_RIGHT_VOL:
|
|
+ case ADAU1961_PLAY_MONO_OUTPUT_VOL:
|
|
+ case ADAU1961_POP_CLICK_SUPPRESS:
|
|
+ case ADAU1961_JACK_DETECT_PIN:
|
|
+ case ADAU1961_DEJITTER:
|
|
+ case ADAU1961_ALC_CTRL0:
|
|
+ case ADAU1961_ALC_CTRL1:
|
|
+ case ADAU1961_ALC_CTRL2:
|
|
+ case ADAU1961_ALC_CTRL3:
|
|
+ case ADAU1961_CLOCK_CONTROL:
|
|
+ case ADAU1961_PLL_CONTROL:
|
|
+ case ADAU1961_MICBIAS:
|
|
+ case ADAU1961_SERIAL_PORT0:
|
|
+ case ADAU1961_SERIAL_PORT1:
|
|
+ case ADAU1961_CONVERTER0:
|
|
+ case ADAU1961_CONVERTER1:
|
|
+ case ADAU1961_LEFT_INPUT_DIGITAL_VOL:
|
|
+ case ADAU1961_RIGHT_INPUT_DIGITAL_VOL:
|
|
+ case ADAU1961_ADC_CONTROL:
|
|
+ case ADAU1961_PLAY_POWER_MGMT:
|
|
+ case ADAU1961_DAC_CONTROL0:
|
|
+ case ADAU1961_DAC_CONTROL1:
|
|
+ case ADAU1961_DAC_CONTROL2:
|
|
+ case ADAU1961_SERIAL_PORT_PAD:
|
|
+ case ADAU1961_CONTROL_PORT_PAD0:
|
|
+ case ADAU1961_CONTROL_PORT_PAD1:
|
|
+ return true;
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ return false;
|
|
+}
|
|
+
|
|
+static int adau1961_component_probe(struct snd_soc_component *component)
|
|
+{
|
|
+ struct adau1961_platform_data *pdata = component->dev->platform_data;
|
|
+ struct adau *adau = snd_soc_component_get_drvdata(component);
|
|
+ int ret;
|
|
+
|
|
+ if (pdata && pdata->input_differential) {
|
|
+ regmap_update_bits(adau->regmap, ADAU1961_LEFT_DIFF_INPUT_VOL,
|
|
+ ADAU1961_DIFF_INPUT_VOL_LDEN,
|
|
+ ADAU1961_DIFF_INPUT_VOL_LDEN);
|
|
+ regmap_update_bits(adau->regmap, ADAU1961_RIGHT_DIFF_INPUT_VOL,
|
|
+ ADAU1961_DIFF_INPUT_VOL_LDEN,
|
|
+ ADAU1961_DIFF_INPUT_VOL_LDEN);
|
|
+ ret = snd_soc_add_component_controls(component,
|
|
+ adau1961_differential_mode_controls,
|
|
+ ARRAY_SIZE(adau1961_differential_mode_controls));
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ } else {
|
|
+ ret = snd_soc_add_component_controls(component,
|
|
+ adau1961_single_mode_controls,
|
|
+ ARRAY_SIZE(adau1961_single_mode_controls));
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ switch (adau1961_get_lineout_mode(component)) {
|
|
+ case ADAU1961_OUTPUT_MODE_LINE:
|
|
+ break;
|
|
+ case ADAU1961_OUTPUT_MODE_HEADPHONE:
|
|
+ regmap_update_bits(adau->regmap, ADAU1961_PLAY_LINE_LEFT_VOL,
|
|
+ ADAU1961_PLAY_LINE_LEFT_VOL_MODE_HP,
|
|
+ ADAU1961_PLAY_LINE_LEFT_VOL_MODE_HP);
|
|
+ regmap_update_bits(adau->regmap, ADAU1961_PLAY_LINE_RIGHT_VOL,
|
|
+ ADAU1961_PLAY_LINE_RIGHT_VOL_MODE_HP,
|
|
+ ADAU1961_PLAY_LINE_RIGHT_VOL_MODE_HP);
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ switch (adau1961_get_monoout_mode(component)) {
|
|
+ case ADAU1961_OUTPUT_MODE_LINE:
|
|
+ break;
|
|
+ case ADAU1961_OUTPUT_MODE_HEADPHONE:
|
|
+ regmap_update_bits(adau->regmap, ADAU1961_PLAY_MONO_OUTPUT_VOL,
|
|
+ ADAU1961_PLAY_MONO_OUTPUT_VOL_MODE_HP,
|
|
+ ADAU1961_PLAY_MONO_OUTPUT_VOL_MODE_HP);
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ ret = adau1961_setup_headphone_mode(component);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ ret = adau1961_add_routes(component);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct snd_soc_component_driver adau1961_component_driver = {
|
|
+ .probe = adau1961_component_probe,
|
|
+ .resume = adau1961_resume,
|
|
+ .set_bias_level = adau1961_set_bias_level,
|
|
+ .controls = adau1961_controls,
|
|
+ .num_controls = ARRAY_SIZE(adau1961_controls),
|
|
+ .dapm_widgets = adau1961_dapm_widgets,
|
|
+ .num_dapm_widgets = ARRAY_SIZE(adau1961_dapm_widgets),
|
|
+ .dapm_routes = adau1961_dapm_routes,
|
|
+ .num_dapm_routes = ARRAY_SIZE(adau1961_dapm_routes),
|
|
+ .suspend_bias_off = 1,
|
|
+ .idle_bias_on = 1,
|
|
+ .use_pmdown_time = 1,
|
|
+ .endianness = 1,
|
|
+ .legacy_dai_naming = 0,
|
|
+};
|
|
+
|
|
+#define ADAU1961_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | \
|
|
+ SNDRV_PCM_FMTBIT_S32_LE)
|
|
+
|
|
+static struct snd_soc_dai_driver adau1961_dai_driver = {
|
|
+ .name = "adau-hifi",
|
|
+ .playback = {
|
|
+ .stream_name = "Playback",
|
|
+ .channels_min = 2,
|
|
+ .channels_max = 2,
|
|
+ .rates = SNDRV_PCM_RATE_8000_96000,
|
|
+ .formats = ADAU1961_FORMATS,
|
|
+ },
|
|
+ .capture = {
|
|
+ .stream_name = "Capture",
|
|
+ .channels_min = 2,
|
|
+ .channels_max = 2,
|
|
+ .rates = SNDRV_PCM_RATE_8000_96000,
|
|
+ .formats = ADAU1961_FORMATS,
|
|
+ },
|
|
+ .ops = &adau1961_dai_ops,
|
|
+};
|
|
+
|
|
+static int adau1961_probe(struct device *dev, struct regmap *regmap,
|
|
+ void (*switch_mode)(struct device *dev))
|
|
+{
|
|
+ struct snd_soc_dai_driver *dai_drv;
|
|
+ int ret;
|
|
+
|
|
+ dai_drv = &adau1961_dai_driver;
|
|
+
|
|
+ struct adau *adau;
|
|
+
|
|
+ if (IS_ERR(regmap))
|
|
+ return PTR_ERR(regmap);
|
|
+
|
|
+ adau = devm_kzalloc(dev, sizeof(*adau), GFP_KERNEL);
|
|
+ if (!adau)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ adau->mclk = devm_clk_get(dev, "mclk");
|
|
+ if (IS_ERR(adau->mclk)) {
|
|
+ if (PTR_ERR(adau->mclk) != -ENOENT)
|
|
+ return PTR_ERR(adau->mclk);
|
|
+ /* Clock is optional (for the driver) */
|
|
+ adau->mclk = NULL;
|
|
+ } else if (adau->mclk) {
|
|
+ adau->clk_src = ADAU1961_CLK_SRC_PLL_AUTO;
|
|
+
|
|
+ /*
|
|
+ * Any valid PLL output rate will work at this point, use one
|
|
+ * that is likely to be chosen later as well. The register will
|
|
+ * be written when the PLL is powered up for the first time.
|
|
+ */
|
|
+ ret = adau_calc_pll_cfg(clk_get_rate(adau->mclk), 48000 * 1024,
|
|
+ adau->pll_regs);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ ret = clk_prepare_enable(adau->mclk);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ adau->regmap = regmap;
|
|
+ adau->switch_mode = switch_mode;
|
|
+
|
|
+ dev_set_drvdata(dev, adau);
|
|
+
|
|
+ if (switch_mode)
|
|
+ switch_mode(dev);
|
|
+
|
|
+ // Enable cache only mode as we could miss writes before bias level
|
|
+ // reaches standby and the core clock is enabled
|
|
+ regcache_cache_only(regmap, true);
|
|
+
|
|
+ return devm_snd_soc_register_component(dev, &adau1961_component_driver, dai_drv, 1);
|
|
+}
|
|
+
|
|
+static const struct regmap_config adau1961_regmap_config = {
|
|
+ .val_bits = 8,
|
|
+ .reg_bits = 16,
|
|
+ .max_register = 0x4036,
|
|
+ .reg_defaults = adau1961_reg_defaults,
|
|
+ .num_reg_defaults = ARRAY_SIZE(adau1961_reg_defaults),
|
|
+ .readable_reg = adau1961_readable_register,
|
|
+ .volatile_reg = adau1961_volatile_register,
|
|
+ .precious_reg = adau1961_precious_register,
|
|
+ .cache_type = REGCACHE_RBTREE,
|
|
+};
|
|
+
|
|
+static int upisnd_codec_fill_pdata(struct device_node *of_node,
|
|
+ struct adau1961_platform_data *pdata)
|
|
+{
|
|
+ const char *input_mode = "differential";
|
|
+ const char *hp_out_mode = "capless-headphone";
|
|
+ const char *line_out_mode = "line-out";
|
|
+ const char *mono_out_mode = "line-out";
|
|
+
|
|
+ of_property_read_string(of_node, "input-mode", &input_mode);
|
|
+ of_property_read_string(of_node, "hp-out-mode", &hp_out_mode);
|
|
+ of_property_read_string(of_node, "line-out-mode", &line_out_mode);
|
|
+ of_property_read_string(of_node, "mono-out-mode", &mono_out_mode);
|
|
+
|
|
+ if (strncmp(input_mode, "differential", 13) == 0) {
|
|
+ pdata->input_differential = true;
|
|
+ } else if (strncmp(input_mode, "single-ended", 13) == 0) {
|
|
+ pdata->input_differential = false;
|
|
+ } else {
|
|
+ printe("Invalid input mode: %s", input_mode);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ if (strncmp(hp_out_mode, "headphone", 10) == 0) {
|
|
+ pdata->headphone_mode = ADAU1961_OUTPUT_MODE_HEADPHONE;
|
|
+ } else if (strncmp(hp_out_mode, "capless-headphone", 18) == 0) {
|
|
+ pdata->headphone_mode = ADAU1961_OUTPUT_MODE_HEADPHONE_CAPLESS;
|
|
+ } else if (strncmp(hp_out_mode, "line-out", 9) == 0) {
|
|
+ pdata->headphone_mode = ADAU1961_OUTPUT_MODE_LINE;
|
|
+ } else {
|
|
+ printe("Invalid headphone output mode: %s", hp_out_mode);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ if (strncmp(line_out_mode, "line-out", 9) == 0) {
|
|
+ pdata->lineout_mode = ADAU1961_OUTPUT_MODE_LINE;
|
|
+ } else if (strncmp(line_out_mode, "headphone", 10) == 0) {
|
|
+ pdata->lineout_mode = ADAU1961_OUTPUT_MODE_HEADPHONE;
|
|
+ } else {
|
|
+ printe("Invalid line-out mode: %s", line_out_mode);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ if (strncmp(mono_out_mode, "line-out", 9) == 0) {
|
|
+ pdata->monoout_mode = ADAU1961_OUTPUT_MODE_LINE;
|
|
+ } else if (strncmp(line_out_mode, "headphone", 10) == 0) {
|
|
+ pdata->monoout_mode = ADAU1961_OUTPUT_MODE_HEADPHONE;
|
|
+ } else {
|
|
+ printe("Invalid mono-out mode: %s", mono_out_mode);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int upisnd_codec_i2c_probe(struct i2c_client *client)
|
|
+{
|
|
+ struct adau1961_platform_data *pdata = NULL;
|
|
+
|
|
+ if (client->dev.of_node) {
|
|
+ pdata = devm_kzalloc(&client->dev,
|
|
+ sizeof(struct adau1961_platform_data), GFP_KERNEL);
|
|
+
|
|
+ if (!pdata)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ int err = upisnd_codec_fill_pdata(client->dev.of_node, pdata);
|
|
+
|
|
+ if (err < 0) {
|
|
+ printe("Failed to fill platform data from device tree! (%d)", err);
|
|
+ return err;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ client->dev.platform_data = pdata;
|
|
+
|
|
+ struct regmap_config config;
|
|
+
|
|
+ config = adau1961_regmap_config;
|
|
+ config.val_bits = 8;
|
|
+ config.reg_bits = 16;
|
|
+
|
|
+ return adau1961_probe(&client->dev, devm_regmap_init_i2c(client, &config), NULL);
|
|
+}
|
|
+
|
|
+static void upisnd_codec_i2c_remove(struct i2c_client *client)
|
|
+{
|
|
+ adau1961_remove(&client->dev);
|
|
+}
|
|
+
|
|
+static const struct i2c_device_id upisnd_codec_i2c_ids[] = {
|
|
+ { "upisnd-codec", 0 },
|
|
+ { }
|
|
+};
|
|
+MODULE_DEVICE_TABLE(i2c, upisnd_codec_i2c_ids);
|
|
+
|
|
+static const struct of_device_id upisnd_codec_i2c_dt_ids[] = {
|
|
+ { .compatible = "blokas,upisnd-codec", },
|
|
+ { },
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, upisnd_codec_i2c_dt_ids);
|
|
+
|
|
+struct i2c_driver upisnd_codec_i2c_driver = {
|
|
+ .driver = {
|
|
+ .name = "upisnd_codec",
|
|
+ .owner = THIS_MODULE,
|
|
+ .of_match_table = of_match_ptr(upisnd_codec_i2c_dt_ids),
|
|
+ },
|
|
+ .probe = upisnd_codec_i2c_probe,
|
|
+ .remove = upisnd_codec_i2c_remove,
|
|
+ .id_table = upisnd_codec_i2c_ids,
|
|
+};
|
|
+module_i2c_driver(upisnd_codec_i2c_driver);
|
|
+
|
|
+MODULE_DESCRIPTION("Codec Driver for Pisound Micro");
|
|
+MODULE_AUTHOR("Giedrius Trainavi\xc4\x8dius <giedrius@blokas.io>");
|
|
+MODULE_LICENSE("GPL v2");
|
|
+
|
|
+/* vim: set ts=8 sw=8 noexpandtab: */
|
|
--- /dev/null
|
|
+++ b/sound/drivers/upisnd/upisnd_codec.h
|
|
@@ -0,0 +1,23 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0-only */
|
|
+/*
|
|
+ * Pisound Micro Linux kernel module.
|
|
+ * Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/
|
|
+ *
|
|
+ * 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.
|
|
+ */
|
|
+
|
|
+ #ifndef UPISOUND_CODEC_H
|
|
+ #define UPISOUND_CODEC_H
|
|
+
|
|
+extern void adau1961_set_vgnd_shorted(struct snd_soc_component *component, bool shorted);
|
|
+extern bool adau1961_is_hp_capless(struct snd_soc_component *component);
|
|
+
|
|
+#endif // UPISOUND_CODEC_H
|
|
--- /dev/null
|
|
+++ b/sound/drivers/upisnd/upisnd_comm.c
|
|
@@ -0,0 +1,638 @@
|
|
+// SPDX-License-Identifier: GPL-2.0-only
|
|
+/*
|
|
+ * Pisound Micro Linux kernel module.
|
|
+ * Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/
|
|
+ *
|
|
+ * 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.
|
|
+ */
|
|
+
|
|
+#include "upisnd_common.h"
|
|
+
|
|
+DECLARE_CRC8_TABLE(upisnd_crc8_table);
|
|
+enum { UPISND_CRC8_POLYNOMIAL = 0x83 };
|
|
+
|
|
+static upisnd_msg_id_t upisnd_next_msg_id(struct upisnd_instance *instance)
|
|
+{
|
|
+ instance->comm.msg_id_counter = instance->comm.msg_id_counter != 127 ?
|
|
+ instance->comm.msg_id_counter + 1 : 1;
|
|
+
|
|
+ return instance->comm.msg_id_counter;
|
|
+}
|
|
+
|
|
+struct upisnd_command_task_t {
|
|
+ struct list_head list;
|
|
+ upisnd_msg_id_t msg_id;
|
|
+ struct completion done;
|
|
+ int result;
|
|
+ u8 cmd[UPISND_MAX_PACKET_LENGTH];
|
|
+ u8 response[UPISND_MAX_PACKET_LENGTH];
|
|
+};
|
|
+
|
|
+static void upisnd_command_response(struct upisnd_command_task_t *task,
|
|
+ const struct upisnd_cmd_t *response,
|
|
+ unsigned int n)
|
|
+{
|
|
+ memcpy(task->response, response, min_t(unsigned int, n, UPISND_MAX_PACKET_LENGTH));
|
|
+ if (upisnd_cmd_matches(response, UPISND_CMD_RESULT, sizeof(struct upisnd_cmd_result_t))) {
|
|
+ int response_code = ((const struct upisnd_cmd_result_t *)response)->result;
|
|
+
|
|
+ printd("hi %p %d %d", task, response_code == -ETIME, response_code);
|
|
+ task->result = response_code;
|
|
+ } else {
|
|
+ task->result = 0;
|
|
+ }
|
|
+}
|
|
+
|
|
+static void upisnd_command_timeout(struct upisnd_command_task_t *task)
|
|
+{
|
|
+ struct upisnd_cmd_result_t result;
|
|
+
|
|
+ result.cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_RESULT, sizeof(result));
|
|
+ result.cmd.flags_and_msg_id = upisnd_msg_id_encode(task->msg_id, true);
|
|
+ result.result = -ETIME;
|
|
+ upisnd_command_response(task, &result.cmd, sizeof(result));
|
|
+}
|
|
+
|
|
+static int upisnd_i2c_send(struct i2c_client *client, const void *data, int n)
|
|
+{
|
|
+ u8 buffer[UPISND_MAX_PACKET_LENGTH + 1];
|
|
+
|
|
+ memcpy(buffer, data, n);
|
|
+ buffer[n] = ~crc8(upisnd_crc8_table, buffer, n, CRC8_INIT_VALUE);
|
|
+ ++n;
|
|
+
|
|
+#ifdef UPISOUND_DEBUG
|
|
+ printk(KERN_DEBUG "%s: Sending %d bytes:", __func__, n);
|
|
+ int i;
|
|
+
|
|
+ for (i = 0; i < n; ++i)
|
|
+ printk(KERN_CONT " %02x", buffer[i]);
|
|
+ printk(KERN_CONT "\n");
|
|
+
|
|
+ u8 check = crc8(upisnd_crc8_table, buffer, n, CRC8_INIT_VALUE);
|
|
+
|
|
+ printd("CRC CHECK: %u, good: %u", check, CRC8_GOOD_VALUE(upisnd_crc8_table));
|
|
+#endif
|
|
+
|
|
+ return i2c_master_send(client, buffer, n);
|
|
+}
|
|
+
|
|
+static void upisnd_command_send(struct upisnd_instance *instance,
|
|
+ struct upisnd_command_task_t *task,
|
|
+ struct upisnd_cmd_t *cmd)
|
|
+{
|
|
+ mutex_lock(&instance->comm.comm_lock);
|
|
+ upisnd_i2c_send(instance->comm.client, cmd, upisnd_cmd_decode_length(cmd->cmd_and_size));
|
|
+ mutex_unlock(&instance->comm.comm_lock);
|
|
+}
|
|
+
|
|
+static void upisnd_init_command_task(struct upisnd_command_task_t *task)
|
|
+{
|
|
+ memset(task, 0, sizeof(struct upisnd_command_task_t));
|
|
+ init_completion(&task->done);
|
|
+ task->result = -EINPROGRESS;
|
|
+ task->cmd[0] = UPISND_CMD_INVALID;
|
|
+}
|
|
+
|
|
+static int upisnd_execute_void_command(struct upisnd_instance *instance, const void *cmd)
|
|
+{
|
|
+ mutex_lock(&instance->comm.comm_lock);
|
|
+ int result = upisnd_i2c_send(instance->comm.client, cmd,
|
|
+ upisnd_cmd_decode_length(*(const uint8_t *)cmd));
|
|
+
|
|
+ mutex_unlock(&instance->comm.comm_lock);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+static int upisnd_execute_command(struct upisnd_instance *instance,
|
|
+ struct upisnd_command_task_t *task)
|
|
+{
|
|
+ struct upisnd_cmd_t *cmd = (struct upisnd_cmd_t *)task->cmd;
|
|
+
|
|
+ mutex_lock(&instance->comm.msg_lock);
|
|
+ upisnd_msg_id_t msg_id = upisnd_next_msg_id(instance);
|
|
+
|
|
+ cmd->flags_and_msg_id = upisnd_msg_id_encode(msg_id, false);
|
|
+ task->msg_id = msg_id;
|
|
+ list_add_tail(&task->list, &instance->comm.msg_handlers);
|
|
+ mutex_unlock(&instance->comm.msg_lock);
|
|
+
|
|
+ upisnd_command_send(instance, task, cmd);
|
|
+
|
|
+ printd("Before wait");
|
|
+ unsigned long t = wait_for_completion_io_timeout(&task->done, msecs_to_jiffies(5000u));
|
|
+ (void)t;
|
|
+
|
|
+ mutex_lock(&instance->comm.msg_lock);
|
|
+ if (!completion_done(&task->done)) {
|
|
+ printe("Message %d timed out!", msg_id);
|
|
+ upisnd_command_timeout(task);
|
|
+ }
|
|
+ list_del(&task->list);
|
|
+ mutex_unlock(&instance->comm.msg_lock);
|
|
+ printd("Wait complete! %lu %d", t, task->result);
|
|
+
|
|
+ return task->result;
|
|
+}
|
|
+
|
|
+static void upisnd_handle_response(struct upisnd_instance *instance,
|
|
+ const struct upisnd_cmd_t *response,
|
|
+ unsigned int n)
|
|
+{
|
|
+ upisnd_msg_id_t msg_id = upisnd_msg_id_decode_id(response->flags_and_msg_id);
|
|
+
|
|
+ printd("Response to %d", msg_id);
|
|
+
|
|
+ struct list_head *p;
|
|
+ struct upisnd_command_task_t *t;
|
|
+
|
|
+ mutex_lock(&instance->comm.msg_lock);
|
|
+ list_for_each(p, &instance->comm.msg_handlers) {
|
|
+ t = list_entry(p, struct upisnd_command_task_t, list);
|
|
+ if (t->msg_id == msg_id) {
|
|
+ upisnd_command_response(t, response, n);
|
|
+ t->msg_id = UPISND_MSG_ID_INVALID;
|
|
+ complete_all(&t->done);
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ mutex_unlock(&instance->comm.msg_lock);
|
|
+}
|
|
+
|
|
+void upisnd_handle_comm_interrupt(struct upisnd_instance *instance)
|
|
+{
|
|
+ printd("Comm handler");
|
|
+
|
|
+ u8 data[UPISND_MAX_PACKET_LENGTH + 1];
|
|
+
|
|
+ mutex_lock(&instance->comm.comm_lock);
|
|
+ int i, n = 0, err;
|
|
+
|
|
+ err = i2c_master_recv(instance->comm.client, data, 3);
|
|
+
|
|
+ if (err == 3 && data[0] != UPISND_CMD_INVALID) {
|
|
+ n = upisnd_cmd_decode_length(data[0]) + 1; // Includes CRC8 byte at the end.
|
|
+ if (n > 3) {
|
|
+ err = i2c_master_recv(instance->comm.client, data + 3, n - 3);
|
|
+ if (err != n - 3) {
|
|
+ printe("Error occurred when receiving data over I2C! (%d)", err);
|
|
+ mutex_unlock(&instance->comm.comm_lock);
|
|
+ return;
|
|
+ }
|
|
+ }
|
|
+ } else {
|
|
+ printe("Error occurred when receiving data over I2C! (%d)", err);
|
|
+ }
|
|
+
|
|
+#ifdef UPISOUND_DEBUG
|
|
+ printk(KERN_DEBUG "upisnd_handle_comm_interrupt: Read %d bytes:", n);
|
|
+ for (i = 0; i < n; ++i)
|
|
+ printk(KERN_CONT " %02x", data[i]);
|
|
+ printk(KERN_CONT "\n");
|
|
+#endif
|
|
+
|
|
+ mutex_unlock(&instance->comm.comm_lock);
|
|
+
|
|
+ if (n <= 0 || data[0] == UPISND_CMD_INVALID) {
|
|
+ printe("Error occurred when receiving data over I2C! (%d)", n);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ u8 crc = crc8(upisnd_crc8_table, data, n, CRC8_INIT_VALUE);
|
|
+
|
|
+ if (crc != CRC8_GOOD_VALUE(upisnd_crc8_table)) {
|
|
+ printe("CRC check failed, calculated value: %02x (expected value: %02x)",
|
|
+ crc, CRC8_GOOD_VALUE(upisnd_crc8_table));
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ enum upisnd_cmd_type_e type = upisnd_cmd_decode_type(data[0]);
|
|
+
|
|
+ if (upisnd_cmd_type_has_msg_id(type)) {
|
|
+ upisnd_handle_response(instance, (const struct upisnd_cmd_t *)data, n);
|
|
+ } else {
|
|
+ switch (type) {
|
|
+ case UPISND_CMD_MIDI:
|
|
+ instance->comm.handler_ops->handle_midi_data(instance,
|
|
+ &data[1],
|
|
+ upisnd_cmd_decode_length(data[0]) - 1
|
|
+ );
|
|
+ break;
|
|
+ case UPISND_CMD_IRQ_EVENT:
|
|
+ {
|
|
+ int j = 0;
|
|
+ int k = 0;
|
|
+ struct irq_event_t gpio_events[15];
|
|
+ struct irq_event_t sound_events[15];
|
|
+ unsigned int n = upisnd_cmd_decode_length(data[0]) - 1;
|
|
+
|
|
+ for (i = 0; i < n; ++i) {
|
|
+ struct irq_event_t e;
|
|
+
|
|
+ e.num = data[i + 1] & UPISND_IRQ_NUM_MASK;
|
|
+ e.high = (data[i + 1] & UPISND_ON_BIT_MASK) != 0;
|
|
+
|
|
+ switch (e.num) {
|
|
+ case UPISND_IRQ_VGND_SHORT_ALERT:
|
|
+ sound_events[j++] = e;
|
|
+ break;
|
|
+ case UPISND_IRQ_GPIO_START...UPISND_IRQ_GPIO_END:
|
|
+ gpio_events[k++] = e;
|
|
+ break;
|
|
+ default:
|
|
+ printe("Unknown IRQ event %d", e.num);
|
|
+ continue;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (j > 0) {
|
|
+ instance->comm.handler_ops->handle_sound_irq_events
|
|
+ (instance,
|
|
+ sound_events,
|
|
+ j);
|
|
+ }
|
|
+ if (k > 0) {
|
|
+ instance->comm.handler_ops->handle_gpio_irq_events
|
|
+ (instance,
|
|
+ gpio_events,
|
|
+ k);
|
|
+ }
|
|
+ }
|
|
+ break;
|
|
+ case UPISND_CMD_CONTROL_EVENT:
|
|
+ {
|
|
+ const struct upisnd_cmd_control_event_t *cmd =
|
|
+ (const struct upisnd_cmd_control_event_t *)data;
|
|
+ int i;
|
|
+ unsigned int n = upisnd_cmd_decode_length(data[0])
|
|
+ / sizeof(uint16_t);
|
|
+ struct control_event_t events[7];
|
|
+
|
|
+ printd("Received %u control events", n);
|
|
+ for (i = 0; i < n; ++i) {
|
|
+ u16 e = ntohs(cmd->values[i]);
|
|
+
|
|
+ events[i].pin = e >> 10;
|
|
+ events[i].raw_value = e & 0x3ff;
|
|
+ printd("%d: %d 0x%04x u=%u d=%d", i, events[i].pin,
|
|
+ events[i].raw_value, events[i].raw_value,
|
|
+ events[i].raw_value);
|
|
+ }
|
|
+
|
|
+ printd("Start handling control events");
|
|
+ instance->comm.handler_ops->handle_control_events(instance,
|
|
+ events,
|
|
+ n);
|
|
+ printd("Done handling control events");
|
|
+ }
|
|
+ break;
|
|
+ default:
|
|
+ printe("Unknown command received (%02x)", data[0]);
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+int upisnd_comm_module_init(void)
|
|
+{
|
|
+ crc8_populate_msb(upisnd_crc8_table, UPISND_CRC8_POLYNOMIAL);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int upisnd_comm_init(struct upisnd_instance *instance,
|
|
+ struct i2c_client *client,
|
|
+ const struct upisnd_comm_handler_ops *ops)
|
|
+{
|
|
+ struct upisnd_comm *comm = &instance->comm;
|
|
+
|
|
+ mutex_init(&comm->comm_lock);
|
|
+ mutex_init(&comm->msg_lock);
|
|
+
|
|
+ comm->client = client;
|
|
+ comm->handler_ops = ops;
|
|
+
|
|
+ INIT_LIST_HEAD(&comm->msg_handlers);
|
|
+ comm->msg_id_counter = 0;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int upisnd_comm_send_midi(struct upisnd_instance *instance, const void *data, unsigned int n)
|
|
+{
|
|
+ if (n >= UPISND_MAX_PACKET_LENGTH)
|
|
+ return -EINVAL;
|
|
+
|
|
+ u8 buffer[UPISND_MAX_PACKET_LENGTH];
|
|
+
|
|
+ buffer[0] = upisnd_cmd_encode(UPISND_CMD_MIDI, n + 1);
|
|
+ memcpy(&buffer[1], data, n);
|
|
+
|
|
+ mutex_lock(&instance->comm.comm_lock);
|
|
+ int err = upisnd_i2c_send(instance->comm.client, buffer, n + 1);
|
|
+
|
|
+ mutex_unlock(&instance->comm.comm_lock);
|
|
+
|
|
+ if (err < 0)
|
|
+ printe("Error occurred when sending MIDI data over I2C! (%d)", err);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int upisnd_comm_commit_setup(struct upisnd_instance *instance, upisnd_setup_t setup)
|
|
+{
|
|
+ struct upisnd_command_task_t task;
|
|
+
|
|
+ upisnd_init_command_task(&task);
|
|
+
|
|
+ struct upisnd_cmd_setup_t *cmd = (struct upisnd_cmd_setup_t *)task.cmd;
|
|
+
|
|
+ cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_SETUP, sizeof(*cmd));
|
|
+ cmd->setup = htonl(setup);
|
|
+
|
|
+ int result = upisnd_execute_command(instance, &task);
|
|
+
|
|
+ return result <= 0 ? result : 0;
|
|
+}
|
|
+
|
|
+int upisnd_comm_gpio_set(struct upisnd_instance *instance, uint8_t pin, bool high)
|
|
+{
|
|
+ struct upisnd_cmd_set_gpio_t cmd;
|
|
+
|
|
+ cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_SET_GPIO, sizeof(cmd));
|
|
+ cmd.on_and_pin = pin | (high ? UPISND_ON_BIT_MASK : 0x00);
|
|
+
|
|
+ return upisnd_execute_void_command(instance, &cmd);
|
|
+}
|
|
+
|
|
+int upisnd_comm_gpio_get(struct upisnd_instance *instance, uint8_t pin)
|
|
+{
|
|
+ struct upisnd_command_task_t task;
|
|
+
|
|
+ upisnd_init_command_task(&task);
|
|
+
|
|
+ struct upisnd_cmd_get_gpio_t *cmd = (struct upisnd_cmd_get_gpio_t *)task.cmd;
|
|
+
|
|
+ cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_GET_GPIO, sizeof(*cmd));
|
|
+ cmd->pin = pin;
|
|
+
|
|
+ return upisnd_execute_command(instance, &task);
|
|
+}
|
|
+
|
|
+int upisnd_comm_gpio_set_all(struct upisnd_instance *instance,
|
|
+ const struct upisnd_all_gpio_state_t *state)
|
|
+{
|
|
+ struct upisnd_cmd_set_all_gpios_t cmd;
|
|
+
|
|
+ cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_SET_ALL_GPIOS, sizeof(cmd));
|
|
+
|
|
+ memcpy(cmd.state.state, state->state, sizeof(cmd.state));
|
|
+
|
|
+ return upisnd_execute_void_command(instance, &cmd);
|
|
+}
|
|
+
|
|
+int upisnd_comm_gpio_get_all(struct upisnd_instance *instance,
|
|
+ struct upisnd_all_gpio_state_t *result)
|
|
+{
|
|
+ int err;
|
|
+ struct upisnd_command_task_t task;
|
|
+
|
|
+ upisnd_init_command_task(&task);
|
|
+
|
|
+ struct upisnd_cmd_get_all_gpios_t *cmd = (struct upisnd_cmd_get_all_gpios_t *)task.cmd;
|
|
+
|
|
+ cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_GET_ALL_GPIOS,
|
|
+ sizeof(struct upisnd_cmd_get_all_gpios_t));
|
|
+
|
|
+ err = upisnd_execute_command(instance, &task);
|
|
+ if (err >= 0) {
|
|
+ const struct upisnd_cmd_get_all_gpios_response_t *resp =
|
|
+ (const struct upisnd_cmd_get_all_gpios_response_t *)task.response;
|
|
+
|
|
+ if (upisnd_cmd_is_response(&resp->cmd) &&
|
|
+ upisnd_cmd_decode_type(resp->cmd.cmd_and_size) == UPISND_CMD_GET_ALL_GPIOS &&
|
|
+ upisnd_cmd_decode_length(resp->cmd.cmd_and_size) ==
|
|
+ sizeof(struct upisnd_cmd_get_all_gpios_response_t)) {
|
|
+ memcpy(result->state, resp->state.state, sizeof(result->state));
|
|
+ err = 0;
|
|
+ } else {
|
|
+ err = -EINVAL;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int upisnd_comm_set_irq_types(struct upisnd_instance *instance,
|
|
+ const struct upisnd_irq_type_config_t *irq_type_config)
|
|
+{
|
|
+ struct upisnd_command_task_t task;
|
|
+
|
|
+ upisnd_init_command_task(&task);
|
|
+
|
|
+ struct upisnd_cmd_set_irq_types_t *cmd = (struct upisnd_cmd_set_irq_types_t *)task.cmd;
|
|
+
|
|
+ cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_SET_IRQ_TYPES, sizeof(*cmd));
|
|
+ memcpy(&cmd->config, irq_type_config, sizeof(struct upisnd_irq_type_config_t));
|
|
+
|
|
+ return upisnd_execute_command(instance, &task);
|
|
+}
|
|
+
|
|
+int upisnd_comm_set_irq_masks(struct upisnd_instance *instance,
|
|
+ const struct upisnd_irq_mask_config_t *irq_mask_config)
|
|
+{
|
|
+ struct upisnd_command_task_t task;
|
|
+
|
|
+ upisnd_init_command_task(&task);
|
|
+
|
|
+ struct upisnd_cmd_set_irq_masks_t *cmd = (struct upisnd_cmd_set_irq_masks_t *)task.cmd;
|
|
+
|
|
+ cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_SET_IRQ_MASKS, sizeof(*cmd));
|
|
+ memcpy(&cmd->config, irq_mask_config, sizeof(struct upisnd_irq_mask_config_t));
|
|
+
|
|
+ return upisnd_execute_command(instance, &task);
|
|
+}
|
|
+
|
|
+int upisnd_comm_set_subscription(struct upisnd_instance *instance, upisnd_irq_num_t irq, bool on)
|
|
+{
|
|
+ struct upisnd_command_task_t task;
|
|
+
|
|
+ upisnd_init_command_task(&task);
|
|
+
|
|
+ struct upisnd_cmd_set_subscription_t *cmd =
|
|
+ (struct upisnd_cmd_set_subscription_t *)task.cmd;
|
|
+
|
|
+ cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_SET_SUBSCRIPTION, sizeof(*cmd));
|
|
+ cmd->on_and_irq_num = (irq & UPISND_IRQ_NUM_MASK) | (on ? UPISND_ON_BIT_MASK : 0);
|
|
+
|
|
+ return upisnd_execute_command(instance, &task);
|
|
+}
|
|
+
|
|
+int upisnd_comm_get_version(struct upisnd_instance *instance, struct upisnd_version_t *result)
|
|
+{
|
|
+ memset(result, 0, sizeof(*result));
|
|
+
|
|
+ struct upisnd_command_task_t task;
|
|
+
|
|
+ upisnd_init_command_task(&task);
|
|
+
|
|
+ struct upisnd_cmd_get_t *cmd = (struct upisnd_cmd_get_t *)task.cmd;
|
|
+
|
|
+ cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_GET, sizeof(*cmd));
|
|
+ cmd->value_id = UPISND_VALUE_VERSION_INFO;
|
|
+
|
|
+ int err = upisnd_execute_command(instance, &task);
|
|
+
|
|
+ if (err >= 0) {
|
|
+ const struct upisnd_cmd_get_response_t *cmd =
|
|
+ (const struct upisnd_cmd_get_response_t *)task.response;
|
|
+
|
|
+ if (upisnd_cmd_decode_length(cmd->cmd_get.cmd.cmd_and_size) ==
|
|
+ UPISND_CMD_GET_RESPONSE_INT32_SIZE &&
|
|
+ upisnd_cmd_is_response(&cmd->cmd_get.cmd) &&
|
|
+ cmd->cmd_get.value_id == UPISND_VALUE_VERSION_INFO) {
|
|
+ u32 ver = (uint32_t)ntohl(cmd->value);
|
|
+
|
|
+ result->bootloader_mode = (ver & 0x80000000) >> 31;
|
|
+ result->hwrev = (ver & 0x7f000000) >> 24;
|
|
+ result->major = (ver & 0x00ff0000) >> 16;
|
|
+ result->minor = (ver & 0x0000ff00) >> 8;
|
|
+ result->build = ver & 0x000000ff;
|
|
+ err = cmd->result;
|
|
+ } else {
|
|
+ err = -EINVAL;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int upisnd_comm_get_serial_number(struct upisnd_instance *instance, char result[12])
|
|
+{
|
|
+ memset(result, 0, 12);
|
|
+
|
|
+ struct upisnd_command_task_t task;
|
|
+
|
|
+ upisnd_init_command_task(&task);
|
|
+
|
|
+ struct upisnd_cmd_get_t *cmd = (struct upisnd_cmd_get_t *)task.cmd;
|
|
+
|
|
+ cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_GET, sizeof(*cmd));
|
|
+ cmd->value_id = UPISND_VALUE_SERIAL_NUMBER;
|
|
+
|
|
+ int err = upisnd_execute_command(instance, &task);
|
|
+
|
|
+ if (err >= 0) {
|
|
+ const struct upisnd_cmd_get_response_t *cmd =
|
|
+ (const struct upisnd_cmd_get_response_t *)task.response;
|
|
+
|
|
+ if (upisnd_cmd_decode_length(cmd->cmd_get.cmd.cmd_and_size) ==
|
|
+ (UPISND_CMD_GET_RESPONSE_DATA_MIN_SIZE + 11) &&
|
|
+ upisnd_cmd_is_response(&cmd->cmd_get.cmd) &&
|
|
+ cmd->cmd_get.value_id == UPISND_VALUE_SERIAL_NUMBER) {
|
|
+ memcpy(result, cmd->data, 11);
|
|
+ err = cmd->result;
|
|
+ } else {
|
|
+ err = -EINVAL;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int upisnd_comm_get_element_value(struct upisnd_instance *instance, uint8_t pin, int32_t *result)
|
|
+{
|
|
+ if (pin >= UPISND_NUM_GPIOS)
|
|
+ return -EINVAL;
|
|
+
|
|
+ *result = 0;
|
|
+
|
|
+ struct upisnd_command_task_t task;
|
|
+
|
|
+ upisnd_init_command_task(&task);
|
|
+
|
|
+ struct upisnd_cmd_get_t *cmd = (struct upisnd_cmd_get_t *)task.cmd;
|
|
+
|
|
+ cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_GET, sizeof(*cmd));
|
|
+ cmd->value_id = UPISND_VALUE_ELEMENT_VAL_BASE + pin;
|
|
+
|
|
+ int err = upisnd_execute_command(instance, &task);
|
|
+
|
|
+ if (err >= 0) {
|
|
+ const struct upisnd_cmd_get_response_t *cmd =
|
|
+ (const struct upisnd_cmd_get_response_t *)task.response;
|
|
+
|
|
+ if (upisnd_cmd_decode_length(cmd->cmd_get.cmd.cmd_and_size) ==
|
|
+ UPISND_CMD_GET_RESPONSE_INT32_SIZE &&
|
|
+ upisnd_cmd_is_response(&cmd->cmd_get.cmd) &&
|
|
+ cmd->cmd_get.value_id == UPISND_VALUE_ELEMENT_VAL_BASE + pin) {
|
|
+ *result = (int32_t)ntohl(cmd->value);
|
|
+ err = cmd->result;
|
|
+ } else {
|
|
+ err = -EINVAL;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int upisnd_comm_get_value(struct upisnd_instance *instance,
|
|
+ enum upisnd_value_id_t value_id,
|
|
+ int32_t *result)
|
|
+{
|
|
+ memset(result, 0, sizeof(*result));
|
|
+
|
|
+ struct upisnd_command_task_t task;
|
|
+
|
|
+ upisnd_init_command_task(&task);
|
|
+
|
|
+ struct upisnd_cmd_get_t *cmd = (struct upisnd_cmd_get_t *)task.cmd;
|
|
+
|
|
+ cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_GET, sizeof(*cmd));
|
|
+ cmd->value_id = value_id;
|
|
+
|
|
+ int err = upisnd_execute_command(instance, &task);
|
|
+
|
|
+ if (err >= 0) {
|
|
+ const struct upisnd_cmd_get_response_t *cmd =
|
|
+ (const struct upisnd_cmd_get_response_t *)task.response;
|
|
+
|
|
+ if (upisnd_cmd_decode_length(cmd->cmd_get.cmd.cmd_and_size) ==
|
|
+ UPISND_CMD_GET_RESPONSE_INT32_SIZE &&
|
|
+ upisnd_cmd_is_response(&cmd->cmd_get.cmd) &&
|
|
+ cmd->cmd_get.value_id == value_id) {
|
|
+ *result = (int32_t)ntohl(cmd->value);
|
|
+ err = cmd->result;
|
|
+ } else {
|
|
+ err = -EINVAL;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int upisnd_comm_set_value(struct upisnd_instance *instance,
|
|
+ enum upisnd_value_id_t value_id,
|
|
+ int32_t value)
|
|
+{
|
|
+ struct upisnd_command_task_t task;
|
|
+
|
|
+ upisnd_init_command_task(&task);
|
|
+
|
|
+ struct upisnd_cmd_set_t *cmd = (struct upisnd_cmd_set_t *)task.cmd;
|
|
+
|
|
+ cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_SET, sizeof(*cmd));
|
|
+ cmd->value_id = value_id;
|
|
+ cmd->value = htonl(value);
|
|
+
|
|
+ return upisnd_execute_command(instance, &task);
|
|
+}
|
|
+
|
|
+/* vim: set ts=8 sw=8 noexpandtab: */
|
|
--- /dev/null
|
|
+++ b/sound/drivers/upisnd/upisnd_comm.h
|
|
@@ -0,0 +1,104 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0-only */
|
|
+/*
|
|
+ * Pisound Micro Linux kernel module.
|
|
+ * Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/
|
|
+ *
|
|
+ * 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.
|
|
+ */
|
|
+
|
|
+#ifndef UPISOUND_COMM_H
|
|
+#define UPISOUND_COMM_H
|
|
+
|
|
+struct upisnd_instance;
|
|
+
|
|
+struct upisnd_version_t {
|
|
+ uint8_t bootloader_mode:1;
|
|
+ uint8_t hwrev:7;
|
|
+ u8 major;
|
|
+ u8 minor;
|
|
+ u8 build;
|
|
+};
|
|
+
|
|
+struct irq_event_t {
|
|
+ upisnd_irq_num_t num;
|
|
+ bool high;
|
|
+};
|
|
+
|
|
+struct control_event_t {
|
|
+ upisnd_pin_t pin:6;
|
|
+ int16_t raw_value:10;
|
|
+};
|
|
+
|
|
+struct upisnd_comm_handler_ops {
|
|
+ void (*handle_midi_data)(struct upisnd_instance *instance,
|
|
+ const u8 *data,
|
|
+ unsigned int n);
|
|
+ void (*handle_gpio_irq_events)(struct upisnd_instance *instance,
|
|
+ const struct irq_event_t *events,
|
|
+ unsigned int n);
|
|
+ void (*handle_sound_irq_events)(struct upisnd_instance *instance,
|
|
+ const struct irq_event_t *events,
|
|
+ unsigned int n);
|
|
+ void (*handle_control_events)(struct upisnd_instance *instance,
|
|
+ const struct control_event_t *events,
|
|
+ unsigned int n);
|
|
+};
|
|
+
|
|
+struct upisnd_comm {
|
|
+ struct mutex comm_lock;
|
|
+ // comm_lock serializes I2C communication.
|
|
+ struct i2c_client *client;
|
|
+
|
|
+ const struct upisnd_comm_handler_ops *handler_ops;
|
|
+
|
|
+ // msg_lock protects the message handlers list and id.
|
|
+ struct mutex msg_lock;
|
|
+ struct list_head msg_handlers;
|
|
+ upisnd_msg_id_t msg_id_counter;
|
|
+};
|
|
+
|
|
+int upisnd_comm_module_init(void);
|
|
+int upisnd_comm_init(struct upisnd_instance *instance,
|
|
+ struct i2c_client *client,
|
|
+ const struct upisnd_comm_handler_ops *ops);
|
|
+int upisnd_comm_send_midi(struct upisnd_instance *instance, const void *data, unsigned int n);
|
|
+int upisnd_comm_commit_setup(struct upisnd_instance *instance, upisnd_setup_t setup);
|
|
+int upisnd_comm_gpio_set(struct upisnd_instance *instance, u8 pin, bool high);
|
|
+int upisnd_comm_gpio_get(struct upisnd_instance *instance, u8 pin);
|
|
+int upisnd_comm_gpio_set_all(struct upisnd_instance *instance,
|
|
+ const struct upisnd_all_gpio_state_t *state);
|
|
+int upisnd_comm_gpio_get_all(struct upisnd_instance *instance,
|
|
+ struct upisnd_all_gpio_state_t *result);
|
|
+int upisnd_comm_set_irq_types(struct upisnd_instance *instance,
|
|
+ const struct upisnd_irq_type_config_t *irq_type_config);
|
|
+int upisnd_comm_set_irq_masks(struct upisnd_instance *instance,
|
|
+ const struct upisnd_irq_mask_config_t *irq_mask_config);
|
|
+int upisnd_comm_set_subscription(struct upisnd_instance *instance,
|
|
+ upisnd_irq_num_t irq, bool on);
|
|
+int upisnd_comm_get_version(struct upisnd_instance *instance,
|
|
+ struct upisnd_version_t *result);
|
|
+int upisnd_comm_get_element_value(struct upisnd_instance *instance,
|
|
+ u8 pin,
|
|
+ int32_t *result);
|
|
+int upisnd_comm_get_serial_number(struct upisnd_instance *instance,
|
|
+ char result[12]);
|
|
+int upisnd_comm_get_value(struct upisnd_instance *instance,
|
|
+ enum upisnd_value_id_t value_id,
|
|
+ int32_t *result);
|
|
+int upisnd_comm_set_value(struct upisnd_instance *instance,
|
|
+ enum upisnd_value_id_t value_id,
|
|
+ int32_t value);
|
|
+
|
|
+void upisnd_handle_comm_interrupt(struct upisnd_instance *instance);
|
|
+
|
|
+#endif // UPISOUND_COMM_H
|
|
+
|
|
+/* vim: set ts=8 sw=8 noexpandtab: */
|
|
--- /dev/null
|
|
+++ b/sound/drivers/upisnd/upisnd_common.h
|
|
@@ -0,0 +1,97 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0-only */
|
|
+/*
|
|
+ * Pisound Micro Linux kernel module.
|
|
+ * Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/
|
|
+ *
|
|
+ * 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.
|
|
+ */
|
|
+
|
|
+#ifndef UPISOUND_COMMON_H
|
|
+#define UPISOUND_COMMON_H
|
|
+
|
|
+#include <linux/init.h>
|
|
+#include <linux/mutex.h>
|
|
+#include <linux/rwsem.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/input.h>
|
|
+#include <linux/i2c.h>
|
|
+#include <linux/slab.h>
|
|
+#include <linux/interrupt.h>
|
|
+#include <linux/gpio.h>
|
|
+#include <linux/workqueue.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/regmap.h>
|
|
+#include <linux/kfifo.h>
|
|
+#include <linux/kobject.h>
|
|
+#include <linux/completion.h>
|
|
+#include <linux/list.h>
|
|
+#include <linux/crc8.h>
|
|
+#include <linux/rbtree.h>
|
|
+#include <linux/kref.h>
|
|
+#include <linux/irq.h>
|
|
+#include <linux/irqdesc.h>
|
|
+#include <linux/of_irq.h>
|
|
+#include <linux/gpio/driver.h>
|
|
+#include <linux/gpio/machine.h>
|
|
+#include <linux/gpio/consumer.h>
|
|
+
|
|
+#include <linux/pinctrl/pinconf-generic.h>
|
|
+
|
|
+#include <sound/asound.h>
|
|
+#include <sound/asequencer.h>
|
|
+#include <sound/soc.h>
|
|
+#include <sound/rawmidi.h>
|
|
+#include <sound/pcm_params.h>
|
|
+
|
|
+#include "upisnd_protocol.h"
|
|
+#include "upisnd_comm.h"
|
|
+#include "upisnd_debug.h"
|
|
+#include "upisnd_midi.h"
|
|
+#include "upisnd_sound.h"
|
|
+#include "upisnd_sysfs.h"
|
|
+#include "upisnd_ctrl.h"
|
|
+#include "upisnd_pins.h"
|
|
+#include "upisnd_gpio.h"
|
|
+#include "upisnd_utils.h"
|
|
+
|
|
+enum upisnd_flags_e {
|
|
+ UPISND_FLAG_DUMMY = 1 << 0,
|
|
+ UPISND_FLAG_ADC_CALIBRATION = 1 << 1,
|
|
+};
|
|
+
|
|
+struct upisnd_instance {
|
|
+ struct kref refcount;
|
|
+ struct platform_device *pdev;
|
|
+ struct device *ctrl_dev;
|
|
+ struct device *codec_dev;
|
|
+ struct workqueue_struct *work_queue;
|
|
+
|
|
+ struct upisnd_ctrl ctrl;
|
|
+ struct upisnd_comm comm;
|
|
+ struct upisnd_midi midi;
|
|
+ struct upisnd_gpio gpio;
|
|
+
|
|
+ struct rw_semaphore rw_gpio_config_sem;
|
|
+ struct upisnd_config *config;
|
|
+ DECLARE_KFIFO(ctrl_event_fifo, struct control_event_t, 128);
|
|
+ struct work_struct ctrl_event_handler;
|
|
+
|
|
+ struct snd_soc_card sound_card;
|
|
+ struct snd_soc_dai_link dai_link;
|
|
+
|
|
+ u32 flags;
|
|
+};
|
|
+
|
|
+void upisnd_instance_release(struct kref *kref);
|
|
+
|
|
+#endif // UPISOUND_COMMON_H
|
|
+
|
|
+/* vim: set ts=8 sw=8 noexpandtab: */
|
|
--- /dev/null
|
|
+++ b/sound/drivers/upisnd/upisnd_ctrl.c
|
|
@@ -0,0 +1,246 @@
|
|
+// SPDX-License-Identifier: GPL-2.0-only
|
|
+/*
|
|
+ * Pisound Micro Linux kernel module.
|
|
+ * Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/
|
|
+ *
|
|
+ * 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.
|
|
+ */
|
|
+
|
|
+#include "upisnd_common.h"
|
|
+
|
|
+static void upisnd_ctrl_handle_irq_events(struct upisnd_instance *instance,
|
|
+ const struct irq_event_t *events,
|
|
+ unsigned int n);
|
|
+static void upisnd_ctrl_handle_control_events(struct upisnd_instance *instance,
|
|
+ const struct control_event_t *events,
|
|
+ unsigned int n);
|
|
+
|
|
+static const struct upisnd_comm_handler_ops upisnd_comm_handlers = {
|
|
+ .handle_midi_data = &upisnd_handle_midi_data,
|
|
+ .handle_gpio_irq_events = &upisnd_ctrl_handle_irq_events,
|
|
+ .handle_sound_irq_events = &upisnd_sound_handle_irq_events,
|
|
+ .handle_control_events = &upisnd_ctrl_handle_control_events,
|
|
+};
|
|
+
|
|
+static void upisnd_ctrl_cleanup(struct upisnd_instance *instance);
|
|
+
|
|
+static inline bool upisnd_has_data(struct upisnd_instance *instance)
|
|
+{
|
|
+ bool x = gpiod_get_value(instance->ctrl.data_available);
|
|
+
|
|
+ printd("data_available = %d", x);
|
|
+ return x;
|
|
+}
|
|
+
|
|
+static irqreturn_t upisnd_data_available_interrupt_handler(int irq, void *dev_id)
|
|
+{
|
|
+ printd("irq_handler called");
|
|
+
|
|
+ struct upisnd_instance *instance = dev_id;
|
|
+
|
|
+ if (irq == instance->ctrl.client->irq) {
|
|
+ printd("handling comm interrupt");
|
|
+ upisnd_handle_comm_interrupt(instance);
|
|
+ printd("handling done");
|
|
+ return IRQ_HANDLED;
|
|
+ }
|
|
+
|
|
+ return IRQ_NONE;
|
|
+}
|
|
+
|
|
+static void upisnd_reset(struct upisnd_instance *instance)
|
|
+{
|
|
+ printd("Resetting...");
|
|
+
|
|
+ gpiod_direction_output(instance->ctrl.reset, 1);
|
|
+ usleep_range(1000, 5000);
|
|
+#ifdef UPISOUND_DEV
|
|
+ gpiod_direction_input(ctrl->reset);
|
|
+#else
|
|
+ gpiod_set_value(instance->ctrl.reset, 0);
|
|
+#endif
|
|
+ msleep(30);
|
|
+
|
|
+ upisnd_gpio_reset(instance);
|
|
+
|
|
+ printd("Done!");
|
|
+}
|
|
+
|
|
+int upisnd_ctrl_probe(struct i2c_client *client)
|
|
+{
|
|
+ int err = 0;
|
|
+
|
|
+ printd("Ctrl %p irq %d", client, client->irq);
|
|
+ printd("Dev %p", &client->dev);
|
|
+ printd("Data %p", dev_get_platdata(&client->dev));
|
|
+
|
|
+ struct upisnd_instance *instance = dev_get_platdata(&client->dev);
|
|
+
|
|
+ if (!instance)
|
|
+ return -EPROBE_DEFER;
|
|
+
|
|
+ struct upisnd_ctrl *ctrl = &instance->ctrl;
|
|
+
|
|
+ ctrl->client = client;
|
|
+
|
|
+ err = upisnd_gpio_init(instance);
|
|
+ if (err < 0) {
|
|
+ printe("GPIO init failed! %d", err);
|
|
+ goto cleanup;
|
|
+ }
|
|
+
|
|
+ ctrl->reset = devm_gpiod_get_index(&client->dev, "reset", 0, GPIOD_ASIS);
|
|
+ if (IS_ERR(ctrl->reset)) {
|
|
+ printe("Failed getting reset gpio!");
|
|
+ err = PTR_ERR(ctrl->reset);
|
|
+ ctrl->reset = NULL;
|
|
+ goto cleanup;
|
|
+ }
|
|
+
|
|
+ upisnd_reset(instance);
|
|
+
|
|
+ err = upisnd_comm_init(instance, client, &upisnd_comm_handlers);
|
|
+ if (err < 0) {
|
|
+ printe("Communication init failed! (%d)", err);
|
|
+ goto cleanup;
|
|
+ }
|
|
+
|
|
+ ctrl->data_available = devm_gpiod_get_index(&client->dev, "data_available", 0, GPIOD_IN);
|
|
+ if (IS_ERR(ctrl->data_available)) {
|
|
+ printe("Failed getting data_available gpio!");
|
|
+ err = PTR_ERR(ctrl->data_available);
|
|
+ ctrl->data_available = NULL;
|
|
+ goto cleanup;
|
|
+ }
|
|
+
|
|
+ err = devm_request_threaded_irq(&client->dev,
|
|
+ ctrl->client->irq,
|
|
+ NULL,
|
|
+ upisnd_data_available_interrupt_handler,
|
|
+ IRQF_SHARED | IRQF_ONESHOT | IRQF_TRIGGER_LOW,
|
|
+ "data_available_int",
|
|
+ instance);
|
|
+ if (err != 0) {
|
|
+ printe("data_available IRQ request failed! %d", err);
|
|
+ goto cleanup;
|
|
+ }
|
|
+
|
|
+ err = upisnd_comm_get_version(instance, &instance->ctrl.version);
|
|
+ if (err < 0) {
|
|
+ printe("Failed getting version! %d", err);
|
|
+ goto cleanup;
|
|
+ }
|
|
+ err = upisnd_comm_get_serial_number(instance, instance->ctrl.serial);
|
|
+ if (err < 0) {
|
|
+ printe("Failed getting serial number! %d", err);
|
|
+ goto cleanup;
|
|
+ }
|
|
+
|
|
+ if (instance->ctrl.serial[0] != 'P' ||
|
|
+ instance->ctrl.serial[1] != 'S' ||
|
|
+ instance->ctrl.serial[2] != 'M' ||
|
|
+ strlen(instance->ctrl.serial) != 11) {
|
|
+ printe("Unexpected serial number %s!", instance->ctrl.serial);
|
|
+ err = -EINVAL;
|
|
+ goto cleanup;
|
|
+ }
|
|
+
|
|
+ printi("Pisound Micro %s version %d.%d.%d, hw rev: %d",
|
|
+ instance->ctrl.serial,
|
|
+ instance->ctrl.version.major,
|
|
+ instance->ctrl.version.minor,
|
|
+ instance->ctrl.version.build,
|
|
+ instance->ctrl.version.hwrev);
|
|
+
|
|
+ if (instance->ctrl.version.bootloader_mode) {
|
|
+ printe("Pisound Micro is in bootloader mode! Please reflash the firmware, refer to documentation on https://blokas.io/");
|
|
+ err = -EINVAL;
|
|
+ goto cleanup;
|
|
+ }
|
|
+
|
|
+ const char *name = NULL;
|
|
+
|
|
+ if (client->dev.of_node) {
|
|
+ err = of_property_read_string(client->dev.of_node, "kobj-name", &name);
|
|
+
|
|
+ if (of_property_read_bool(client->dev.of_node, "adc-calibration"))
|
|
+ instance->flags |= UPISND_FLAG_ADC_CALIBRATION;
|
|
+ }
|
|
+ err = upisnd_sysfs_init(instance, name);
|
|
+ if (err != 0) {
|
|
+ printe("sysfs init failed! %d", err);
|
|
+ goto cleanup;
|
|
+ }
|
|
+
|
|
+cleanup:
|
|
+ if (err != 0) {
|
|
+ printe("Error %d!", err);
|
|
+ upisnd_ctrl_cleanup(instance);
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+void upisnd_ctrl_remove(struct i2c_client *client)
|
|
+{
|
|
+ printd("Ctrl %p", client);
|
|
+
|
|
+ struct upisnd_instance *instance = dev_get_platdata(&client->dev);
|
|
+
|
|
+ if (!instance)
|
|
+ return;
|
|
+
|
|
+ upisnd_ctrl_cleanup(instance);
|
|
+
|
|
+ kref_put(&instance->refcount, &upisnd_instance_release);
|
|
+ client->dev.platform_data = NULL;
|
|
+}
|
|
+
|
|
+static void upisnd_off(struct upisnd_instance *instance)
|
|
+{
|
|
+ printd("Turning off...");
|
|
+
|
|
+#ifndef UPISOUND_DEV
|
|
+ gpiod_direction_output(instance->ctrl.reset, 1);
|
|
+#else
|
|
+ upisnd_reset(&instance->ctrl);
|
|
+#endif
|
|
+ printd("Done!");
|
|
+}
|
|
+
|
|
+static void upisnd_ctrl_cleanup(struct upisnd_instance *instance)
|
|
+{
|
|
+ printd("cleanup");
|
|
+ upisnd_sysfs_uninit(instance);
|
|
+
|
|
+ if (instance->ctrl.reset) {
|
|
+ upisnd_off(instance);
|
|
+ instance->ctrl.reset = NULL;
|
|
+ }
|
|
+}
|
|
+
|
|
+static void upisnd_ctrl_handle_irq_events(struct upisnd_instance *instance,
|
|
+ const struct irq_event_t *events,
|
|
+ unsigned int n)
|
|
+{
|
|
+ upisnd_gpio_handle_irq_event(instance, events, n);
|
|
+ upisnd_sysfs_handle_irq_event(instance, events, n);
|
|
+}
|
|
+
|
|
+static void upisnd_ctrl_handle_control_events(struct upisnd_instance *instance,
|
|
+ const struct control_event_t *events,
|
|
+ unsigned int n)
|
|
+{
|
|
+ upisnd_sysfs_handle_control_event(instance, events, n);
|
|
+}
|
|
+
|
|
+/* vim: set ts=8 sw=8 noexpandtab: */
|
|
--- /dev/null
|
|
+++ b/sound/drivers/upisnd/upisnd_ctrl.h
|
|
@@ -0,0 +1,36 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0-only */
|
|
+/*
|
|
+ * Pisound Micro Linux kernel module.
|
|
+ * Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/
|
|
+ *
|
|
+ * 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.
|
|
+ */
|
|
+
|
|
+#ifndef UPISOUND_CTRL_H
|
|
+#define UPISOUND_CTRL_H
|
|
+
|
|
+struct upisnd_ctrl {
|
|
+ struct i2c_client *client;
|
|
+ struct upisnd_gpio *gpio;
|
|
+
|
|
+ struct upisnd_version_t version;
|
|
+ char serial[12];
|
|
+
|
|
+ struct gpio_desc *data_available;
|
|
+ struct gpio_desc *reset;
|
|
+};
|
|
+
|
|
+int upisnd_ctrl_probe(struct i2c_client *client);
|
|
+void upisnd_ctrl_remove(struct i2c_client *client);
|
|
+
|
|
+#endif // UPISOUND_CTRL_H
|
|
+
|
|
+/* vim: set ts=8 sw=8 noexpandtab: */
|
|
--- /dev/null
|
|
+++ b/sound/drivers/upisnd/upisnd_debug.h
|
|
@@ -0,0 +1,34 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0-only */
|
|
+/*
|
|
+ * Pisound Micro Linux kernel module.
|
|
+ * Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/
|
|
+ *
|
|
+ * 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.
|
|
+ */
|
|
+
|
|
+#ifndef UPISOUND_DEBUG_H
|
|
+#define UPISOUND_DEBUG_H
|
|
+
|
|
+#define UPISOUND_LOG_IMPL(log_func, msg, ...) \
|
|
+ log_func("pisound-micro(%s): " msg "\n", __func__, ## __VA_ARGS__)
|
|
+
|
|
+#ifdef UPISOUND_DEBUG
|
|
+# define printd(...) UPISOUND_LOG_IMPL(pr_alert, __VA_ARGS__)
|
|
+#else
|
|
+# define printd(...) do {} while (0)
|
|
+#endif
|
|
+
|
|
+#define printe(...) UPISOUND_LOG_IMPL(pr_err, __VA_ARGS__)
|
|
+#define printi(...) UPISOUND_LOG_IMPL(pr_info, __VA_ARGS__)
|
|
+
|
|
+#endif // UPISOUND_DEBUG_H
|
|
+
|
|
+/* vim: set ts=8 sw=8 noexpandtab: */
|
|
--- /dev/null
|
|
+++ b/sound/drivers/upisnd/upisnd_gpio.c
|
|
@@ -0,0 +1,867 @@
|
|
+// SPDX-License-Identifier: GPL-2.0-only
|
|
+/*
|
|
+ * Pisound Micro Linux kernel module.
|
|
+ * Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/
|
|
+ *
|
|
+ * 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.
|
|
+ */
|
|
+
|
|
+#include "upisnd_common.h"
|
|
+
|
|
+enum upisnd_pinconf_dir_e {
|
|
+ UPISND_PINCONF_DIR_INPUT_PULL_AS_IS_OR_NONE = 0,
|
|
+ UPISND_PINCONF_DIR_INPUT_PULL_UP = 1,
|
|
+ UPISND_PINCONF_DIR_INPUT_PULL_DOWN = 2,
|
|
+ UPISND_PINCONF_DIR_OUTPUT_LOW = 4,
|
|
+ UPISND_PINCONF_DIR_OUTPUT_HIGH = 5,
|
|
+
|
|
+ // If 3rd bit is set, direction is output, otherwise it's input.
|
|
+ UPISND_PINCONF_DIR_MASK = 0x04,
|
|
+};
|
|
+
|
|
+enum { UPISND_PINCONF_DIRECTION = (PIN_CONFIG_END + 1) }; // enum upisnd_pinconf_dir_e
|
|
+
|
|
+static int upisnd_gpio_chip_get_direction(struct gpio_chip *gc, unsigned int offset);
|
|
+static int upisnd_gpio_chip_get(struct gpio_chip *gc, unsigned int offset);
|
|
+static void upisnd_gpio_chip_set(struct gpio_chip *gc, unsigned int offset, int val);
|
|
+static int upisnd_gpio_chip_get_multiple(struct gpio_chip *gc,
|
|
+ unsigned long *mask,
|
|
+ unsigned long *bits);
|
|
+static void upisnd_gpio_chip_set_multiple(struct gpio_chip *gc,
|
|
+ unsigned long *mask,
|
|
+ unsigned long *bits);
|
|
+static int upisnd_gpio_chip_set_config(struct gpio_chip *gc,
|
|
+ unsigned int offset,
|
|
+ unsigned long config);
|
|
+static int upisnd_gpio_chip_dir_in(struct gpio_chip *gc, unsigned int offset);
|
|
+static int upisnd_gpio_chip_dir_out(struct gpio_chip *gc, unsigned int offset, int val);
|
|
+static int upisnd_gpio_chip_request(struct gpio_chip *gc, unsigned int offset);
|
|
+static void upisnd_gpio_chip_free(struct gpio_chip *gc, unsigned int offset);
|
|
+
|
|
+static const char * const upisnd_gpio_names[UPISND_NUM_GPIOS] = {
|
|
+ "A27", "A28", "A29", "A30", "A31", "A32", "B03", "B04",
|
|
+ "B05", "B06", "B07", "B08", "B09", "B10", "B11", "B12",
|
|
+ "B13", "B14", "B15", "B16", "B17", "B18", "B23", "B24",
|
|
+ "B25", "B26", "B27", "B28", "B29", "B30", "B31", "B32",
|
|
+ "B33", "B34", "B37", "B38", "B39"
|
|
+};
|
|
+
|
|
+static const struct gpio_chip upisnd_gpio_chip = {
|
|
+ .label = "pisound-micro-gpio",
|
|
+ .owner = THIS_MODULE,
|
|
+ .request = &upisnd_gpio_chip_request,
|
|
+ .free = &upisnd_gpio_chip_free,
|
|
+ .direction_input = &upisnd_gpio_chip_dir_in,
|
|
+ .direction_output = &upisnd_gpio_chip_dir_out,
|
|
+ .get_direction = &upisnd_gpio_chip_get_direction,
|
|
+ .get = &upisnd_gpio_chip_get,
|
|
+ .set = &upisnd_gpio_chip_set,
|
|
+ .get_multiple = &upisnd_gpio_chip_get_multiple,
|
|
+ .set_multiple = &upisnd_gpio_chip_set_multiple,
|
|
+ .set_config = &upisnd_gpio_chip_set_config,
|
|
+ .base = -1,
|
|
+ .ngpio = UPISND_NUM_GPIOS,
|
|
+ .can_sleep = true,
|
|
+ .names = upisnd_gpio_names,
|
|
+};
|
|
+
|
|
+enum {
|
|
+ IRQ_FLAG_TYPES_CHANGED_BIT = 0,
|
|
+ IRQ_FLAG_MASKS_CHANGED_BIT = 1,
|
|
+ IRQ_FLAG_HANDLING_IRQ_BIT = 2,
|
|
+};
|
|
+
|
|
+static int upisnd_gpio_chip_get_direction(struct gpio_chip *gc, unsigned int offset)
|
|
+{
|
|
+ struct upisnd_instance *instance = gpiochip_get_data(gc);
|
|
+ struct upisnd_gpio *gpio = &instance->gpio;
|
|
+
|
|
+ down_read(&instance->rw_gpio_config_sem);
|
|
+ upisnd_setup_t setup = gpio->pin_configs[offset].setup;
|
|
+
|
|
+ up_read(&instance->rw_gpio_config_sem);
|
|
+
|
|
+ int dir = -EINVAL;
|
|
+
|
|
+ if (upisnd_setup_get_element_type(setup) == UPISND_ELEMENT_TYPE_GPIO)
|
|
+ dir = upisnd_setup_get_gpio_dir(setup) == UPISND_PIN_DIR_INPUT ? 1 : 0;
|
|
+
|
|
+ printd("%p %u %d %08x", gc, offset, dir, setup);
|
|
+ return dir;
|
|
+}
|
|
+
|
|
+static const char *upisnd_gpio_pull_to_str(enum upisnd_pin_pull_e pull)
|
|
+{
|
|
+ switch (pull) {
|
|
+ default:
|
|
+ case UPISND_PIN_PULL_NONE: return "pull_none";
|
|
+ case UPISND_PIN_PULL_UP: return "pull_up";
|
|
+ case UPISND_PIN_PULL_DOWN: return "pull_down";
|
|
+ }
|
|
+}
|
|
+
|
|
+static void upisnd_print_setup(const upisnd_setup_t setup)
|
|
+{
|
|
+ switch (upisnd_setup_get_element_type(setup)) {
|
|
+ case UPISND_ELEMENT_TYPE_NONE:
|
|
+ printi("Setup None %d", upisnd_setup_get_pin_id(setup));
|
|
+ break;
|
|
+ case UPISND_ELEMENT_TYPE_ENCODER:
|
|
+ printi("Setup Encoder %d %s %d %s",
|
|
+ upisnd_setup_get_pin_id(setup),
|
|
+ upisnd_gpio_pull_to_str(upisnd_setup_get_gpio_pull(setup)),
|
|
+ upisnd_setup_get_encoder_pin_b_id(setup),
|
|
+ upisnd_gpio_pull_to_str(upisnd_setup_get_encoder_pin_b_pull(setup)));
|
|
+ break;
|
|
+ case UPISND_ELEMENT_TYPE_ANALOG_IN:
|
|
+ printi("Setup Analog In %d (%d)",
|
|
+ upisnd_setup_get_pin_id(setup),
|
|
+ upisnd_setup_get_pin_id(setup));
|
|
+ break;
|
|
+ case UPISND_ELEMENT_TYPE_GPIO:
|
|
+ if (upisnd_setup_get_gpio_dir(setup) == UPISND_PIN_DIR_INPUT)
|
|
+ printi("Setup GPIO Input %d %s",
|
|
+ upisnd_setup_get_pin_id(setup),
|
|
+ upisnd_gpio_pull_to_str(upisnd_setup_get_gpio_pull(setup)));
|
|
+ else
|
|
+ printi("Setup GPIO Output %d %s",
|
|
+ upisnd_setup_get_pin_id(setup),
|
|
+ upisnd_setup_get_gpio_output(setup) ? "high" : "low");
|
|
+ break;
|
|
+ case UPISND_ELEMENT_TYPE_ACTIVITY:
|
|
+ printi("Setup Activity %d %d",
|
|
+ upisnd_setup_get_pin_id(setup),
|
|
+ upisnd_setup_get_activity_type(setup));
|
|
+ break;
|
|
+ default:
|
|
+ printe("Unknown Setup type %d", upisnd_setup_get_element_type(setup));
|
|
+ break;
|
|
+ }
|
|
+}
|
|
+
|
|
+int upisnd_gpio_setup(struct upisnd_instance *instance, upisnd_setup_t setup)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+#ifdef UPISND_DEBUG
|
|
+ printd("Committing setup:");
|
|
+ upisnd_print_setup(setup);
|
|
+ err = upisnd_comm_commit_setup(instance, setup);
|
|
+ printd("Result: %d", err)
|
|
+#else
|
|
+ err = upisnd_comm_commit_setup(instance, setup);
|
|
+
|
|
+ if (err < 0) {
|
|
+ printe("Failed to commit setup (%d), failed request:", err);
|
|
+ upisnd_print_setup(setup);
|
|
+ }
|
|
+#endif
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int upisnd_gpio_chip_get(struct gpio_chip *gc, unsigned int offset)
|
|
+{
|
|
+ printd("%p %u", gc, offset);
|
|
+
|
|
+ int value = -EINVAL;
|
|
+
|
|
+ struct upisnd_instance *instance = gpiochip_get_data(gc);
|
|
+
|
|
+ unsigned long state[2];
|
|
+
|
|
+ down_read(&instance->rw_gpio_config_sem);
|
|
+ upisnd_setup_t setup = instance->gpio.pin_configs[offset].setup;
|
|
+
|
|
+ state[0] = instance->gpio.gpio_state[0];
|
|
+ state[1] = instance->gpio.gpio_state[1];
|
|
+ up_read(&instance->rw_gpio_config_sem);
|
|
+
|
|
+ if (upisnd_setup_get_element_type(setup) == UPISND_ELEMENT_TYPE_GPIO) {
|
|
+ switch (upisnd_setup_get_gpio_dir(setup)) {
|
|
+ case UPISND_PIN_DIR_OUTPUT:
|
|
+ value = test_bit(offset, state) ? 1 : 0;
|
|
+ break;
|
|
+ case UPISND_PIN_DIR_INPUT:
|
|
+ if (test_bit(IRQ_FLAG_HANDLING_IRQ_BIT, &instance->gpio.irq_flags)) {
|
|
+ value = test_bit(offset, state) ? 1 : 0;
|
|
+ } else {
|
|
+ down_write(&instance->rw_gpio_config_sem);
|
|
+ value = upisnd_comm_gpio_get(instance, offset);
|
|
+ assign_bit(offset, instance->gpio.gpio_state, value);
|
|
+ up_write(&instance->rw_gpio_config_sem);
|
|
+ }
|
|
+ break;
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return value;
|
|
+}
|
|
+
|
|
+static void upisnd_gpio_chip_set(struct gpio_chip *gc, unsigned int offset, int val)
|
|
+{
|
|
+ printd("%p %u %d", gc, offset, val);
|
|
+
|
|
+ struct upisnd_instance *instance = gpiochip_get_data(gc);
|
|
+
|
|
+ down_write(&instance->rw_gpio_config_sem);
|
|
+ upisnd_setup_t setup = instance->gpio.pin_configs[offset].setup;
|
|
+
|
|
+ if (upisnd_setup_get_element_type(setup) == UPISND_ELEMENT_TYPE_GPIO) {
|
|
+ if (upisnd_setup_get_gpio_dir(setup) == UPISND_PIN_DIR_OUTPUT) {
|
|
+ bool on = val != 0;
|
|
+
|
|
+ assign_bit(offset, instance->gpio.gpio_state, on);
|
|
+ upisnd_comm_gpio_set(instance, offset, on);
|
|
+ }
|
|
+ }
|
|
+ up_write(&instance->rw_gpio_config_sem);
|
|
+}
|
|
+
|
|
+static void encode_gpio_state(struct upisnd_all_gpio_state_t *s, const unsigned long *bits)
|
|
+{
|
|
+ u8 *p = (uint8_t *)s->state;
|
|
+
|
|
+ p[0] = (bits[0]) & 0xff;
|
|
+ p[1] = (bits[0] >> 8) & 0xff;
|
|
+ p[2] = (bits[0] >> 16) & 0xff;
|
|
+ p[3] = (bits[0] >> 24) & 0xff;
|
|
+ p[4] = (bits[1]) & 0xff;
|
|
+}
|
|
+
|
|
+static void decode_gpio_state(unsigned long *bits, const struct upisnd_all_gpio_state_t *s)
|
|
+{
|
|
+ const u8 *p = (const uint8_t *)s->state;
|
|
+
|
|
+ bits[0] =
|
|
+ (p[0]) |
|
|
+ (p[1] << 8) |
|
|
+ (p[2] << 16) |
|
|
+ (p[3] << 24);
|
|
+ bits[1] =
|
|
+ (p[4]);
|
|
+}
|
|
+
|
|
+static int upisnd_gpio_chip_get_multiple(struct gpio_chip *gc,
|
|
+ unsigned long *mask,
|
|
+ unsigned long *bits)
|
|
+{
|
|
+ unsigned int i;
|
|
+ int err = 0;
|
|
+
|
|
+ memset(bits, 0, sizeof(bits[0]) * 2);
|
|
+
|
|
+ printd("%p %02lx%08lx", gc, mask[1], mask[0]);
|
|
+
|
|
+ struct upisnd_instance *instance = gpiochip_get_data(gc);
|
|
+
|
|
+ unsigned long state[2];
|
|
+
|
|
+ down_read(&instance->rw_gpio_config_sem);
|
|
+ for_each_set_bit(i, mask, UPISND_NUM_GPIOS) {
|
|
+ upisnd_setup_t setup = instance->gpio.pin_configs[i].setup;
|
|
+
|
|
+ if (upisnd_setup_get_element_type(setup) != UPISND_ELEMENT_TYPE_GPIO) {
|
|
+ printe("Pin %u is not configured as GPIO!", i);
|
|
+ err = -EINVAL;
|
|
+ }
|
|
+ }
|
|
+ up_read(&instance->rw_gpio_config_sem);
|
|
+
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ if (test_bit(IRQ_FLAG_HANDLING_IRQ_BIT, &instance->gpio.irq_flags)) {
|
|
+ state[0] = instance->gpio.gpio_state[0];
|
|
+ state[1] = instance->gpio.gpio_state[1];
|
|
+ } else {
|
|
+ struct upisnd_all_gpio_state_t s;
|
|
+
|
|
+ down_write(&instance->rw_gpio_config_sem);
|
|
+
|
|
+ err = upisnd_comm_gpio_get_all(instance, &s);
|
|
+
|
|
+ if (err < 0) {
|
|
+ up_write(&instance->rw_gpio_config_sem);
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ decode_gpio_state(state, &s);
|
|
+
|
|
+ instance->gpio.gpio_state[0] = state[0];
|
|
+ instance->gpio.gpio_state[1] = state[1];
|
|
+ up_write(&instance->rw_gpio_config_sem);
|
|
+ }
|
|
+
|
|
+ bits[0] = state[0] & mask[0];
|
|
+ bits[1] = state[1] & mask[1];
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static void upisnd_gpio_chip_set_multiple(struct gpio_chip *gc,
|
|
+ unsigned long *mask,
|
|
+ unsigned long *bits)
|
|
+{
|
|
+ unsigned long state[2];
|
|
+
|
|
+ printd("%p %02lx%08lx %02lx%08lx", gc, mask[1], mask[0], bits[1], bits[0]);
|
|
+
|
|
+ struct upisnd_instance *instance = gpiochip_get_data(gc);
|
|
+
|
|
+ down_write(&instance->rw_gpio_config_sem);
|
|
+
|
|
+ state[0] = (instance->gpio.gpio_state[0] & ~mask[0]) | (bits[0] & mask[0]);
|
|
+ state[1] = (instance->gpio.gpio_state[1] & ~mask[1]) | (bits[1] & mask[1]);
|
|
+
|
|
+ struct upisnd_all_gpio_state_t s;
|
|
+
|
|
+ encode_gpio_state(&s, state);
|
|
+
|
|
+ upisnd_comm_gpio_set_all(instance, &s);
|
|
+
|
|
+ instance->gpio.gpio_state[0] = state[0];
|
|
+ instance->gpio.gpio_state[1] = state[1];
|
|
+
|
|
+ up_write(&instance->rw_gpio_config_sem);
|
|
+}
|
|
+
|
|
+static int upisnd_gpio_chip_dir_in(struct gpio_chip *gc, unsigned int offset)
|
|
+{
|
|
+ printd("%p %u", gc, offset);
|
|
+ return upisnd_gpio_chip_set_config(gc, offset, pinconf_to_config_packed
|
|
+ ((enum pin_config_param)UPISND_PINCONF_DIRECTION,
|
|
+ UPISND_PINCONF_DIR_INPUT_PULL_AS_IS_OR_NONE));
|
|
+}
|
|
+
|
|
+static int upisnd_gpio_chip_dir_out(struct gpio_chip *gc, unsigned int offset, int val)
|
|
+{
|
|
+ printd("%p %u %d", gc, offset, val);
|
|
+ return upisnd_gpio_chip_set_config(gc, offset, pinconf_to_config_packed
|
|
+ ((enum pin_config_param)UPISND_PINCONF_DIRECTION,
|
|
+ val ? UPISND_PINCONF_DIR_OUTPUT_HIGH : UPISND_PINCONF_DIR_OUTPUT_LOW));
|
|
+}
|
|
+
|
|
+static int upisnd_gpio_chip_request(struct gpio_chip *gc, unsigned int offset)
|
|
+{
|
|
+ printd("(%p, %u)", gc, offset);
|
|
+
|
|
+ struct upisnd_instance *instance = gpiochip_get_data(gc);
|
|
+
|
|
+ int err;
|
|
+ upisnd_setup_t setup = 0;
|
|
+
|
|
+ down_write(&instance->rw_gpio_config_sem);
|
|
+
|
|
+ if (upisnd_setup_get_element_type(instance->gpio.pin_configs[offset].setup) ==
|
|
+ UPISND_ELEMENT_TYPE_GPIO) {
|
|
+ setup = instance->gpio.pin_configs[offset].setup;
|
|
+ } else {
|
|
+ upisnd_setup_set_element_type(&setup, UPISND_ELEMENT_TYPE_GPIO);
|
|
+ upisnd_setup_set_gpio_dir(&setup, UPISND_PIN_DIR_INPUT);
|
|
+ upisnd_setup_set_gpio_pull(&setup, UPISND_PIN_PULL_NONE);
|
|
+ upisnd_setup_set_pin_id(&setup, offset);
|
|
+ }
|
|
+
|
|
+ // If the pin is already in use via setup interface.
|
|
+ if (instance->gpio.pin_configs[offset].element) {
|
|
+ // If the pin is not being requested via setup interface, deny the request.
|
|
+ if (!(instance->gpio.pin_configs[offset].flags &
|
|
+ UPISND_PIN_FLAG_CONFIGURING_THROUGH_SYSFS)) {
|
|
+ printe("Pin %u already exported through pisound-micro sysfs!", offset);
|
|
+ err = -EBUSY;
|
|
+ } else {
|
|
+ err = 0;
|
|
+ }
|
|
+
|
|
+ up_write(&instance->rw_gpio_config_sem);
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ err = upisnd_gpio_setup(instance, setup);
|
|
+
|
|
+ if (err >= 0) {
|
|
+ instance->gpio.pin_configs[offset].element = NULL;
|
|
+ instance->gpio.pin_configs[offset].flags = 0;
|
|
+ instance->gpio.pin_configs[offset].setup = setup;
|
|
+ instance->gpio.pin_configs[offset].gpio_desc = NULL;
|
|
+ }
|
|
+
|
|
+ up_write(&instance->rw_gpio_config_sem);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static void upisnd_gpio_chip_free(struct gpio_chip *gc, unsigned int offset)
|
|
+{
|
|
+ printd("(%p, %u)", gc, offset);
|
|
+
|
|
+ struct upisnd_instance *instance = gpiochip_get_data(gc);
|
|
+
|
|
+ down_write(&instance->rw_gpio_config_sem);
|
|
+ if (instance->gpio.pin_configs[offset].element ||
|
|
+ (instance->gpio.pin_configs[offset].flags & UPISND_PIN_FLAG_CONFIGURING_THROUGH_SYSFS)
|
|
+ ) {
|
|
+ // Unsetup will be taken care of in upisnd_element_cleanup,
|
|
+ // ignore requests that come through gpiochip_free_own_desc.
|
|
+ up_write(&instance->rw_gpio_config_sem);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ upisnd_setup_t setup = instance->gpio.pin_configs[offset].setup;
|
|
+
|
|
+ upisnd_setup_set_element_type(&setup, UPISND_ELEMENT_TYPE_NONE);
|
|
+ upisnd_gpio_setup(instance, setup);
|
|
+
|
|
+ memset(&instance->gpio.pin_configs[offset], 0, sizeof(struct upisnd_pin_config_t));
|
|
+
|
|
+ up_write(&instance->rw_gpio_config_sem);
|
|
+
|
|
+ printd("Done");
|
|
+}
|
|
+
|
|
+static int upisnd_gpio_chip_set_config(struct gpio_chip *gc,
|
|
+ unsigned int offset,
|
|
+ unsigned long config)
|
|
+{
|
|
+ int p = pinconf_to_config_param(config);
|
|
+ u32 arg = pinconf_to_config_argument(config);
|
|
+
|
|
+ printd("%p %u %08lx p=%d arg=%u", gc, offset, config, p, arg);
|
|
+
|
|
+ struct upisnd_instance *instance = gpiochip_get_data(gc);
|
|
+
|
|
+ down_write(&instance->rw_gpio_config_sem);
|
|
+ if (instance->gpio.pin_configs[offset].flags & UPISND_PIN_FLAG_CONFIGURING_THROUGH_SYSFS) {
|
|
+ // Setup has taken care of configuring the pin already,
|
|
+ // ignore requests that come through gpiochip_request_own_desc.
|
|
+ up_write(&instance->rw_gpio_config_sem);
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ int err = 0;
|
|
+
|
|
+ upisnd_setup_t setup = instance->gpio.pin_configs[offset].setup;
|
|
+
|
|
+ if (upisnd_setup_get_element_type(setup) != UPISND_ELEMENT_TYPE_GPIO) {
|
|
+ err = -EINVAL;
|
|
+ goto cleanup;
|
|
+ }
|
|
+
|
|
+ switch (p) {
|
|
+ case PIN_CONFIG_BIAS_PULL_DOWN:
|
|
+ case PIN_CONFIG_BIAS_PULL_UP:
|
|
+ if (upisnd_setup_get_gpio_dir(setup) != UPISND_PIN_DIR_INPUT) {
|
|
+ err = -EINVAL;
|
|
+ goto cleanup;
|
|
+ }
|
|
+ if (arg != 0)
|
|
+ upisnd_setup_set_gpio_pull(&setup, p == PIN_CONFIG_BIAS_PULL_UP ?
|
|
+ UPISND_PIN_PULL_UP : UPISND_PIN_PULL_DOWN);
|
|
+ else
|
|
+ upisnd_setup_set_gpio_pull(&setup, UPISND_PIN_PULL_NONE);
|
|
+ break;
|
|
+ case PIN_CONFIG_BIAS_DISABLE:
|
|
+ case PIN_CONFIG_BIAS_HIGH_IMPEDANCE:
|
|
+ if (upisnd_setup_get_gpio_dir(setup) != UPISND_PIN_DIR_INPUT) {
|
|
+ err = -EINVAL;
|
|
+ goto cleanup;
|
|
+ }
|
|
+ upisnd_setup_set_gpio_pull(&setup, UPISND_PIN_PULL_NONE);
|
|
+ break;
|
|
+ case UPISND_PINCONF_DIRECTION:
|
|
+ if (arg & UPISND_PINCONF_DIR_MASK) {
|
|
+ // Output.
|
|
+ upisnd_setup_set_gpio_dir(&setup, UPISND_PIN_DIR_OUTPUT);
|
|
+ upisnd_setup_set_gpio_output(&setup, arg == UPISND_PINCONF_DIR_OUTPUT_HIGH);
|
|
+ } else {
|
|
+ // Input.
|
|
+ if (arg == UPISND_PINCONF_DIR_INPUT_PULL_AS_IS_OR_NONE) {
|
|
+ if (upisnd_setup_get_gpio_dir(setup) == UPISND_PIN_DIR_OUTPUT)
|
|
+ upisnd_setup_set_gpio_pull(&setup, UPISND_PIN_PULL_NONE);
|
|
+ } else {
|
|
+ upisnd_setup_set_gpio_pull(&setup, (enum upisnd_pin_pull_e)arg);
|
|
+ }
|
|
+ upisnd_setup_set_gpio_dir(&setup, UPISND_PIN_DIR_INPUT);
|
|
+ }
|
|
+ break;
|
|
+ default:
|
|
+ printe("Not supported param %d", p);
|
|
+ err = -ENOTSUPP;
|
|
+ goto cleanup;
|
|
+ }
|
|
+
|
|
+ err = upisnd_gpio_setup(instance, setup);
|
|
+
|
|
+ if (err >= 0)
|
|
+ instance->gpio.pin_configs[offset].setup = setup;
|
|
+
|
|
+ printd("Done");
|
|
+
|
|
+cleanup:
|
|
+ up_write(&instance->rw_gpio_config_sem);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int upisnd_gpio_irq_set_type(struct irq_data *data, unsigned int type)
|
|
+{
|
|
+ printd("%p %u", data, type);
|
|
+
|
|
+ if (!(type & IRQ_TYPE_EDGE_BOTH)) {
|
|
+ printe("IRQ %d: unsupported type %u\n", data->irq, type);
|
|
+ return -ENOTSUPP;
|
|
+ }
|
|
+
|
|
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
|
|
+ struct upisnd_instance *instance = gpiochip_get_data(gc);
|
|
+
|
|
+ unsigned long pin = data->hwirq;
|
|
+
|
|
+ down_read(&instance->rw_gpio_config_sem);
|
|
+ if (upisnd_setup_get_element_type(instance->gpio.pin_configs[pin].setup) !=
|
|
+ UPISND_ELEMENT_TYPE_GPIO ||
|
|
+ upisnd_setup_get_gpio_dir(instance->gpio.pin_configs[pin].setup) !=
|
|
+ UPISND_PIN_DIR_INPUT) {
|
|
+ printe("Pin %s is not set up as an input!", upisnd_pin_name(pin));
|
|
+ up_read(&instance->rw_gpio_config_sem);
|
|
+ return -EACCES;
|
|
+ }
|
|
+ up_read(&instance->rw_gpio_config_sem);
|
|
+
|
|
+ upisnd_irq_config_set_irq_type(&instance->gpio.irq_type_config, pin, type);
|
|
+ set_bit(IRQ_FLAG_TYPES_CHANGED_BIT, &instance->gpio.irq_flags);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void upisnd_gpio_irq_ack(struct irq_data *data)
|
|
+{
|
|
+ printd("%p mask=%08x irq=%u hwirq=%lu", data, data->mask, data->irq, data->hwirq);
|
|
+}
|
|
+
|
|
+static void upisnd_gpio_irq_mask(struct irq_data *data)
|
|
+{
|
|
+ printd("%p mask=%08x irq=%u hwirq=%lu", data, data->mask, data->irq, data->hwirq);
|
|
+
|
|
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
|
|
+ struct upisnd_instance *instance = gpiochip_get_data(gc);
|
|
+
|
|
+ unsigned long pin = data->hwirq;
|
|
+
|
|
+ down_read(&instance->rw_gpio_config_sem);
|
|
+ if (upisnd_setup_get_element_type(instance->gpio.pin_configs[pin].setup) !=
|
|
+ UPISND_ELEMENT_TYPE_GPIO ||
|
|
+ upisnd_setup_get_gpio_dir(instance->gpio.pin_configs[pin].setup) !=
|
|
+ UPISND_PIN_DIR_INPUT) {
|
|
+ printe("Pin %s is not set up as an input!", upisnd_pin_name(pin));
|
|
+ up_read(&instance->rw_gpio_config_sem);
|
|
+ return;
|
|
+ }
|
|
+ up_read(&instance->rw_gpio_config_sem);
|
|
+
|
|
+ upisnd_irq_mask_config_set(&instance->gpio.irq_mask_config, pin, true);
|
|
+ set_bit(IRQ_FLAG_MASKS_CHANGED_BIT, &instance->gpio.irq_flags);
|
|
+}
|
|
+
|
|
+static void upisnd_gpio_irq_unmask(struct irq_data *data)
|
|
+{
|
|
+ printd("%p mask=%08x irq=%u hwirq=%lu", data, data->mask, data->irq, data->hwirq);
|
|
+
|
|
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
|
|
+ struct upisnd_instance *instance = gpiochip_get_data(gc);
|
|
+
|
|
+ unsigned long pin = data->hwirq;
|
|
+
|
|
+ down_read(&instance->rw_gpio_config_sem);
|
|
+ if (upisnd_setup_get_element_type(instance->gpio.pin_configs[pin].setup) !=
|
|
+ UPISND_ELEMENT_TYPE_GPIO ||
|
|
+ upisnd_setup_get_gpio_dir(instance->gpio.pin_configs[pin].setup) !=
|
|
+ UPISND_PIN_DIR_INPUT) {
|
|
+ printe("Pin %s is not set up as an input!", upisnd_pin_name(pin));
|
|
+ up_read(&instance->rw_gpio_config_sem);
|
|
+ return;
|
|
+ }
|
|
+ up_read(&instance->rw_gpio_config_sem);
|
|
+
|
|
+ upisnd_irq_mask_config_set(&instance->gpio.irq_mask_config, pin, false);
|
|
+ set_bit(IRQ_FLAG_MASKS_CHANGED_BIT, &instance->gpio.irq_flags);
|
|
+}
|
|
+
|
|
+static void upisnd_gpio_irq_bus_lock(struct irq_data *data)
|
|
+{
|
|
+ printd("%p mask=%08x irq=%u hwirq=%lu", data, data->mask, data->irq, data->hwirq);
|
|
+
|
|
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
|
|
+ struct upisnd_instance *instance = gpiochip_get_data(gc);
|
|
+
|
|
+ mutex_lock(&instance->gpio.gpio_irq_lock);
|
|
+}
|
|
+
|
|
+static void upisnd_gpio_irq_bus_sync_unlock(struct irq_data *data)
|
|
+{
|
|
+ printd("%p mask=%08x irq=%u hwirq=%lu", data, data->mask, data->irq, data->hwirq);
|
|
+
|
|
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
|
|
+ struct upisnd_instance *instance = gpiochip_get_data(gc);
|
|
+
|
|
+ int result = 0;
|
|
+
|
|
+ if (test_bit(IRQ_FLAG_TYPES_CHANGED_BIT, &instance->gpio.irq_flags)) {
|
|
+ printd("Types changed");
|
|
+ result = upisnd_comm_set_irq_types(instance, &instance->gpio.irq_type_config);
|
|
+ printd("result: %d", result);
|
|
+
|
|
+ clear_bit(IRQ_FLAG_TYPES_CHANGED_BIT, &instance->gpio.irq_flags);
|
|
+ }
|
|
+
|
|
+ if (test_bit(IRQ_FLAG_MASKS_CHANGED_BIT, &instance->gpio.irq_flags)) {
|
|
+ printd("Masks changed");
|
|
+ result = upisnd_comm_set_irq_masks(instance, &instance->gpio.irq_mask_config);
|
|
+ printd("result: %d", result);
|
|
+
|
|
+ clear_bit(IRQ_FLAG_MASKS_CHANGED_BIT, &instance->gpio.irq_flags);
|
|
+ }
|
|
+
|
|
+ printd("done");
|
|
+ mutex_unlock(&instance->gpio.gpio_irq_lock);
|
|
+}
|
|
+
|
|
+static const struct irq_chip upisnd_gpio_irq_chip = {
|
|
+ .name = "pisound-micro-gpio",
|
|
+ .irq_set_type = upisnd_gpio_irq_set_type,
|
|
+ .irq_ack = upisnd_gpio_irq_ack,
|
|
+ .irq_mask = upisnd_gpio_irq_mask,
|
|
+ .irq_unmask = upisnd_gpio_irq_unmask,
|
|
+ .irq_bus_lock = upisnd_gpio_irq_bus_lock,
|
|
+ .irq_bus_sync_unlock = upisnd_gpio_irq_bus_sync_unlock,
|
|
+ .flags = IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_IMMUTABLE,
|
|
+};
|
|
+
|
|
+static void upisnd_gpio_irq_event_handler(struct work_struct *work)
|
|
+{
|
|
+ struct upisnd_instance *instance = container_of(work,
|
|
+ struct upisnd_instance,
|
|
+ gpio.irq_event_handler);
|
|
+
|
|
+ int n;
|
|
+ struct irq_event_t events[128];
|
|
+
|
|
+ set_bit(IRQ_FLAG_HANDLING_IRQ_BIT, &instance->gpio.irq_flags);
|
|
+
|
|
+ unsigned int offset;
|
|
+ unsigned long masked[2];
|
|
+
|
|
+ memset(masked, 0, sizeof(masked));
|
|
+ u8 i;
|
|
+
|
|
+ down_write(&instance->rw_gpio_config_sem);
|
|
+ n = kfifo_out(&instance->gpio.irq_event_fifo, events, ARRAY_SIZE(events));
|
|
+ if (n > 0) {
|
|
+ printd("Handling %u IRQ events:", n);
|
|
+ for (i = 0; i < n; ++i) {
|
|
+ offset = events[i].num;
|
|
+
|
|
+ if (offset == 0x7f) {
|
|
+ printi("\tAlert event %s", events[i].high ? "on" : "off");
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ printd("\t%d %s %s", offset, upisnd_pin_name(offset),
|
|
+ events[i].high ? "up" : "down");
|
|
+
|
|
+ assign_bit(offset, instance->gpio.gpio_state, events[i].high);
|
|
+
|
|
+ if (upisnd_irq_mask_config_get(&instance->gpio.irq_mask_config, offset))
|
|
+ set_bit(offset, masked);
|
|
+ }
|
|
+ }
|
|
+ up_write(&instance->rw_gpio_config_sem);
|
|
+
|
|
+ for (i = 0; i < n; ++i) {
|
|
+ if (!test_bit(i, masked)) {
|
|
+ offset = events[i].num;
|
|
+ int nested_irq = irq_find_mapping(instance->gpio.gpio_chip.irq.domain,
|
|
+ offset);
|
|
+
|
|
+ if (unlikely(nested_irq <= 0)) {
|
|
+ dev_warn_ratelimited(instance->gpio.gpio_chip.parent,
|
|
+ "unmapped interrupt %d\n",
|
|
+ offset);
|
|
+ continue;
|
|
+ } else {
|
|
+ printd("Before handle_nested_irq");
|
|
+ handle_nested_irq(nested_irq);
|
|
+ printd("After handle_nested_irq");
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ clear_bit(IRQ_FLAG_HANDLING_IRQ_BIT, &instance->gpio.irq_flags);
|
|
+
|
|
+ if (!kfifo_is_empty(&instance->gpio.irq_event_fifo))
|
|
+ queue_work(instance->work_queue, &instance->gpio.irq_event_handler);
|
|
+}
|
|
+
|
|
+void upisnd_gpio_handle_irq_event(struct upisnd_instance *instance,
|
|
+ const struct irq_event_t *events,
|
|
+ unsigned int n)
|
|
+{
|
|
+ printd("Pushing %u events", n);
|
|
+ kfifo_in(&instance->gpio.irq_event_fifo, events, n);
|
|
+ if (!work_pending(&instance->gpio.irq_event_handler))
|
|
+ queue_work(instance->work_queue, &instance->gpio.irq_event_handler);
|
|
+}
|
|
+
|
|
+void upisnd_gpio_reset(struct upisnd_instance *instance)
|
|
+{
|
|
+ down_write(&instance->rw_gpio_config_sem);
|
|
+
|
|
+ memset(&instance->gpio.gpio_state, 0, sizeof(instance->gpio.gpio_state));
|
|
+ memset(instance->gpio.pin_configs, 0, sizeof(instance->gpio.pin_configs));
|
|
+ memset(&instance->gpio.irq_mask_config, 0xff, sizeof(instance->gpio.irq_mask_config));
|
|
+
|
|
+ up_write(&instance->rw_gpio_config_sem);
|
|
+}
|
|
+
|
|
+int upisnd_gpio_init(struct upisnd_instance *instance)
|
|
+{
|
|
+ struct upisnd_gpio *gpio = &instance->gpio;
|
|
+
|
|
+ mutex_init(&gpio->gpio_irq_lock);
|
|
+
|
|
+ // Set all IRQs as masked initially.
|
|
+ memset(&gpio->irq_mask_config, 0xff, sizeof(gpio->irq_mask_config));
|
|
+
|
|
+ INIT_KFIFO(gpio->irq_event_fifo);
|
|
+ INIT_WORK(&gpio->irq_event_handler, upisnd_gpio_irq_event_handler);
|
|
+
|
|
+ gpio->gpio_chip = upisnd_gpio_chip;
|
|
+ gpio->gpio_chip.parent = instance->ctrl_dev;
|
|
+
|
|
+ gpio->gpio_irq = &upisnd_gpio_irq_chip;
|
|
+
|
|
+ struct gpio_irq_chip *girq = &gpio->gpio_chip.irq;
|
|
+
|
|
+ gpio_irq_chip_set_chip(girq, gpio->gpio_irq);
|
|
+ girq->threaded = true;
|
|
+ girq->num_parents = 1;
|
|
+ girq->parents = devm_kzalloc(instance->ctrl_dev, sizeof(*girq->parents), GFP_KERNEL);
|
|
+
|
|
+ if (!girq->parents)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ *girq->parents = irq_of_parse_and_map(instance->ctrl_dev->of_node, 0);
|
|
+
|
|
+ printd("Doing devm_gpiochip_add_data");
|
|
+ int err = devm_gpiochip_add_data(instance->ctrl_dev, &gpio->gpio_chip, instance);
|
|
+
|
|
+ printd("result: %d, base = %d", err, gpio->gpio_chip.base);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+void upisnd_gpio_uninit(struct upisnd_instance *instance)
|
|
+{
|
|
+ kfifo_free(&instance->gpio.irq_event_fifo);
|
|
+}
|
|
+
|
|
+void upisnd_gpio_set(struct upisnd_instance *instance, unsigned int offset, bool value)
|
|
+{
|
|
+ upisnd_gpio_chip_set(&instance->gpio.gpio_chip, offset, value);
|
|
+}
|
|
+
|
|
+int upisnd_gpio_get(struct upisnd_instance *instance, unsigned int offset)
|
|
+{
|
|
+ return upisnd_gpio_chip_get(&instance->gpio.gpio_chip, offset);
|
|
+}
|
|
+
|
|
+int upisnd_gpio_get_direction(struct upisnd_instance *instance, unsigned int offset)
|
|
+{
|
|
+ return upisnd_gpio_chip_get_direction(&instance->gpio.gpio_chip, offset);
|
|
+}
|
|
+
|
|
+int upisnd_gpio_set_irq_type(struct upisnd_instance *instance,
|
|
+ unsigned int offset,
|
|
+ unsigned int irq_type)
|
|
+{
|
|
+ printd("(%d, %d)", offset, irq_type);
|
|
+ if (offset >= UPISND_NUM_GPIOS)
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (!(irq_type & IRQ_TYPE_EDGE_BOTH)) {
|
|
+ printe("IRQ %d: unsupported type %u\n", offset, irq_type);
|
|
+ return -ENOTSUPP;
|
|
+ }
|
|
+
|
|
+ mutex_lock(&instance->gpio.gpio_irq_lock);
|
|
+
|
|
+ if (upisnd_setup_get_element_type(instance->gpio.pin_configs[offset].setup) !=
|
|
+ UPISND_ELEMENT_TYPE_GPIO ||
|
|
+ upisnd_setup_get_gpio_dir(instance->gpio.pin_configs[offset].setup) !=
|
|
+ UPISND_PIN_DIR_INPUT) {
|
|
+ printe("Pin %s is not set up as an input!", upisnd_pin_name(offset));
|
|
+ return -EACCES;
|
|
+ }
|
|
+ upisnd_irq_config_set_irq_type(&instance->gpio.irq_type_config, offset, irq_type);
|
|
+ int result = upisnd_comm_set_irq_types(instance, &instance->gpio.irq_type_config);
|
|
+ (void)result;
|
|
+
|
|
+ mutex_unlock(&instance->gpio.gpio_irq_lock);
|
|
+
|
|
+ printd("result: %d", result);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int upisnd_gpio_set_subscription(struct upisnd_instance *instance, unsigned int offset, bool on)
|
|
+{
|
|
+ printd("(%d, %d)", offset, on);
|
|
+
|
|
+ if (offset >= UPISND_NUM_GPIOS)
|
|
+ return -EINVAL;
|
|
+
|
|
+ int result = 0;
|
|
+ int *rc = &instance->gpio.pin_subscription_refcounts[offset];
|
|
+ int c;
|
|
+
|
|
+ mutex_lock(&instance->gpio.gpio_irq_lock);
|
|
+
|
|
+ if (on)
|
|
+ c = (*rc)++;
|
|
+ else
|
|
+ c = --(*rc);
|
|
+
|
|
+ if (c == 0) {
|
|
+ printd("Count was 0, setting %d sub to %d", offset, on);
|
|
+ result = upisnd_comm_set_subscription(instance, offset, on);
|
|
+ }
|
|
+
|
|
+ mutex_unlock(&instance->gpio.gpio_irq_lock);
|
|
+
|
|
+ return result;
|
|
+}
|
|
+
|
|
+enum upisnd_element_type_e upisnd_gpio_get_type(struct upisnd_instance *instance,
|
|
+ unsigned int offset)
|
|
+{
|
|
+ if (offset >= UPISND_NUM_GPIOS)
|
|
+ return UPISND_ELEMENT_TYPE_NONE;
|
|
+
|
|
+ down_read(&instance->rw_gpio_config_sem);
|
|
+ upisnd_setup_t setup = instance->gpio.pin_configs[offset].setup;
|
|
+
|
|
+ up_read(&instance->rw_gpio_config_sem);
|
|
+
|
|
+ return upisnd_setup_get_element_type(setup);
|
|
+}
|
|
+
|
|
+/* vim: set ts=8 sw=8 noexpandtab: */
|
|
--- /dev/null
|
|
+++ b/sound/drivers/upisnd/upisnd_gpio.h
|
|
@@ -0,0 +1,75 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0-only */
|
|
+/*
|
|
+ * Pisound Micro Linux kernel module.
|
|
+ * Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/
|
|
+ *
|
|
+ * 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.
|
|
+ */
|
|
+
|
|
+#ifndef UPISOUND_GPIO_H
|
|
+#define UPISOUND_GPIO_H
|
|
+
|
|
+enum {
|
|
+ UPISND_PIN_FLAG_IS_ENCODER_B = 1 << 0,
|
|
+ UPISND_PIN_FLAG_CONFIGURING_THROUGH_SYSFS = 1 << 1,
|
|
+ UPISND_PIN_FLAG_IRQ_RISING = 1 << 2,
|
|
+ UPISND_PIN_FLAG_IRQ_FALLING = 1 << 3,
|
|
+};
|
|
+
|
|
+struct upisnd_pin_config_t {
|
|
+ upisnd_setup_t setup;
|
|
+ u32 flags;
|
|
+ struct gpio_desc *gpio_desc;
|
|
+ struct upisnd_element *element;
|
|
+};
|
|
+
|
|
+struct upisnd_gpio {
|
|
+ struct gpio_chip gpio_chip;
|
|
+ const struct irq_chip *gpio_irq;
|
|
+ // Protects IRQ state and bus.
|
|
+ struct mutex gpio_irq_lock;
|
|
+ unsigned long gpio_state[2];
|
|
+ unsigned long irq_flags;
|
|
+ struct upisnd_irq_type_config_t irq_type_config;
|
|
+ struct upisnd_irq_mask_config_t irq_mask_config;
|
|
+ struct upisnd_pin_config_t pin_configs[UPISND_NUM_GPIOS];
|
|
+ int pin_subscription_refcounts[UPISND_NUM_GPIOS];
|
|
+ DECLARE_KFIFO(irq_event_fifo, struct irq_event_t, 128);
|
|
+ struct work_struct irq_event_handler;
|
|
+};
|
|
+
|
|
+int upisnd_gpio_init(struct upisnd_instance *instance);
|
|
+void upisnd_gpio_uninit(struct upisnd_instance *instance);
|
|
+
|
|
+void upisnd_gpio_reset(struct upisnd_instance *instance);
|
|
+
|
|
+int upisnd_gpio_setup(struct upisnd_instance *instance, upisnd_setup_t setup);
|
|
+
|
|
+void upisnd_gpio_handle_irq_event(struct upisnd_instance *instance,
|
|
+ const struct irq_event_t *events,
|
|
+ unsigned int n);
|
|
+
|
|
+void upisnd_gpio_set(struct upisnd_instance *instance, unsigned int offset, bool value);
|
|
+int upisnd_gpio_get(struct upisnd_instance *instance, unsigned int offset);
|
|
+int upisnd_gpio_get_direction(struct upisnd_instance *instance, unsigned int offset);
|
|
+
|
|
+int upisnd_gpio_set_irq_type(struct upisnd_instance *instance,
|
|
+ unsigned int offset,
|
|
+ unsigned int irq_type);
|
|
+
|
|
+int upisnd_gpio_set_subscription(struct upisnd_instance *instance, unsigned int offset, bool on);
|
|
+
|
|
+enum upisnd_element_type_e upisnd_gpio_get_type(struct upisnd_instance *instance,
|
|
+ unsigned int offset);
|
|
+
|
|
+#endif // UPISOUND_GPIO_H
|
|
+
|
|
+/* vim: set ts=8 sw=8 noexpandtab: */
|
|
--- /dev/null
|
|
+++ b/sound/drivers/upisnd/upisnd_midi.c
|
|
@@ -0,0 +1,326 @@
|
|
+// SPDX-License-Identifier: GPL-2.0-only
|
|
+/*
|
|
+ * Pisound Micro Linux kernel module.
|
|
+ * Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/
|
|
+ *
|
|
+ * 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.
|
|
+ */
|
|
+
|
|
+#include "upisnd_common.h"
|
|
+
|
|
+static int upisnd_midi_output_open(struct snd_rawmidi_substream *substream)
|
|
+{
|
|
+ struct upisnd_instance *instance = substream->rmidi->private_data;
|
|
+
|
|
+ mutex_lock(&instance->midi.out_lock);
|
|
+ instance->midi.midi_output = substream;
|
|
+ mutex_unlock(&instance->midi.out_lock);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void upisnd_midi_output_drain(struct snd_rawmidi_substream *substream);
|
|
+
|
|
+static int upisnd_midi_output_close(struct snd_rawmidi_substream *substream)
|
|
+{
|
|
+ struct upisnd_instance *instance = substream->rmidi->private_data;
|
|
+
|
|
+ printd("Close: draining output");
|
|
+ upisnd_midi_output_drain(substream);
|
|
+ printd("Close: setting to null");
|
|
+ mutex_lock(&instance->midi.out_lock);
|
|
+ instance->midi.midi_output = NULL;
|
|
+ mutex_unlock(&instance->midi.out_lock);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void upisnd_midi_output_trigger(struct snd_rawmidi_substream *substream, int up)
|
|
+{
|
|
+ if (up == 0)
|
|
+ return;
|
|
+
|
|
+ struct upisnd_instance *instance = substream->rmidi->private_data;
|
|
+
|
|
+ if (!delayed_work_pending(&instance->midi.midi_out_handler))
|
|
+ queue_delayed_work(instance->work_queue, &instance->midi.midi_out_handler, 0);
|
|
+}
|
|
+
|
|
+static void upisnd_midi_output_drain(struct snd_rawmidi_substream *substream)
|
|
+{
|
|
+ struct upisnd_instance *instance = substream->rmidi->private_data;
|
|
+
|
|
+ printd("Begin draining!");
|
|
+
|
|
+ do {
|
|
+ printd("Before flush");
|
|
+ while (delayed_work_pending(&instance->midi.midi_out_handler))
|
|
+ flush_delayed_work(&instance->midi.midi_out_handler);
|
|
+ printd("Flushed");
|
|
+ } while (!snd_rawmidi_transmit_empty(substream));
|
|
+
|
|
+ printd("Done!");
|
|
+}
|
|
+
|
|
+static int upisnd_midi_input_open(struct snd_rawmidi_substream *substream)
|
|
+{
|
|
+ struct upisnd_instance *instance = substream->rmidi->private_data;
|
|
+
|
|
+ mutex_lock(&instance->midi.in_lock);
|
|
+ instance->midi.midi_input = substream;
|
|
+ mutex_unlock(&instance->midi.in_lock);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int upisnd_midi_input_close(struct snd_rawmidi_substream *substream)
|
|
+{
|
|
+ struct upisnd_instance *instance = substream->rmidi->private_data;
|
|
+
|
|
+ mutex_lock(&instance->midi.in_lock);
|
|
+ instance->midi.midi_input = NULL;
|
|
+ mutex_unlock(&instance->midi.in_lock);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void upisnd_midi_input_trigger(struct snd_rawmidi_substream *substream, int up)
|
|
+{
|
|
+ if (up == 0)
|
|
+ return;
|
|
+
|
|
+ struct upisnd_instance *instance = substream->rmidi->private_data;
|
|
+
|
|
+ if (!work_pending(&instance->midi.midi_in_handler))
|
|
+ queue_work(instance->work_queue, &instance->midi.midi_in_handler);
|
|
+}
|
|
+
|
|
+static const struct snd_rawmidi_ops upisnd_midi_output_ops = {
|
|
+ .open = upisnd_midi_output_open,
|
|
+ .close = upisnd_midi_output_close,
|
|
+ .trigger = upisnd_midi_output_trigger,
|
|
+ .drain = upisnd_midi_output_drain,
|
|
+};
|
|
+
|
|
+static const struct snd_rawmidi_ops upisnd_midi_input_ops = {
|
|
+ .open = upisnd_midi_input_open,
|
|
+ .close = upisnd_midi_input_close,
|
|
+ .trigger = upisnd_midi_input_trigger,
|
|
+};
|
|
+
|
|
+static void upisnd_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 const struct snd_rawmidi_global_ops upisnd_midi_ops = {
|
|
+ .get_port_info = upisnd_get_port_info,
|
|
+};
|
|
+
|
|
+static void upisnd_midi_in_handler(struct work_struct *work)
|
|
+{
|
|
+ int i, n, err;
|
|
+
|
|
+ printd("In handler");
|
|
+ struct upisnd_instance *instance = container_of(work,
|
|
+ struct upisnd_instance,
|
|
+ midi.midi_in_handler);
|
|
+
|
|
+ mutex_lock(&instance->midi.in_lock);
|
|
+ if (!instance->midi.midi_input)
|
|
+ goto cleanup;
|
|
+
|
|
+ u8 data[512];
|
|
+
|
|
+ n = kfifo_out_peek(&instance->midi.midi_in_fifo, data, sizeof(data));
|
|
+ err = snd_rawmidi_receive(instance->midi.midi_input, data, n);
|
|
+
|
|
+ if (err < 0)
|
|
+ printe("snd_rawmidi_receive failed! (%d)", err);
|
|
+
|
|
+ if (err > 0) {
|
|
+ printd("Received %d MIDI bytes", err);
|
|
+ instance->midi.rx_cnt += err;
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < err; ++i)
|
|
+ kfifo_skip(&instance->midi.midi_in_fifo);
|
|
+
|
|
+ if (!kfifo_is_empty(&instance->midi.midi_in_fifo) &&
|
|
+ !work_pending(&instance->midi.midi_in_handler)) {
|
|
+ queue_work(instance->work_queue, &instance->midi.midi_in_handler);
|
|
+ }
|
|
+cleanup:
|
|
+ mutex_unlock(&instance->midi.in_lock);
|
|
+ printd("Done");
|
|
+}
|
|
+
|
|
+static void upisnd_midi_out_handler(struct work_struct *work)
|
|
+{
|
|
+ printd("Out handler");
|
|
+ struct upisnd_instance *instance = container_of(work,
|
|
+ struct upisnd_instance,
|
|
+ midi.midi_out_handler.work);
|
|
+
|
|
+ mutex_lock(&instance->midi.out_lock);
|
|
+ printd("midi_output = %p", instance->midi.midi_output);
|
|
+ if (!instance->midi.midi_output)
|
|
+ goto cleanup;
|
|
+
|
|
+ enum { MIDI_MILLI_BYTES_PER_JIFFY = 3125000 / HZ };
|
|
+ enum { MIDI_MAX_OUTPUT_BUFFER_SIZE_IN_MILLIBYTES = 4096000 };
|
|
+
|
|
+ unsigned int now = jiffies;
|
|
+ unsigned int millibytes_became_available = (MIDI_MILLI_BYTES_PER_JIFFY) *
|
|
+ (now - instance->midi.last_midi_output_at);
|
|
+
|
|
+ instance->midi.output_buffer_used_in_millibytes =
|
|
+ instance->midi.output_buffer_used_in_millibytes <= millibytes_became_available ?
|
|
+ 0 : instance->midi.output_buffer_used_in_millibytes - millibytes_became_available;
|
|
+ instance->midi.last_midi_output_at = now;
|
|
+
|
|
+ unsigned int output_buffer_available = (MIDI_MAX_OUTPUT_BUFFER_SIZE_IN_MILLIBYTES -
|
|
+ instance->midi.output_buffer_used_in_millibytes) / 1000;
|
|
+
|
|
+ u8 buffer[UPISND_MAX_PACKET_LENGTH - 1];
|
|
+
|
|
+ printd("Available: %u", output_buffer_available);
|
|
+ int n = snd_rawmidi_transmit_peek(instance->midi.midi_output,
|
|
+ buffer,
|
|
+ min(output_buffer_available, sizeof(buffer)));
|
|
+
|
|
+ if (n > 0) {
|
|
+ printd("Peeked: %d", n);
|
|
+ snd_rawmidi_transmit_ack(instance->midi.midi_output, n);
|
|
+ n = upisnd_comm_send_midi(instance, buffer, (unsigned int)n);
|
|
+ if (n < 0)
|
|
+ printe("Error occurred when sending MIDI data over I2C! (%d)", n);
|
|
+ } else {
|
|
+ printe("snd_rawmidi_transmit_peek returned error %d!", n);
|
|
+ goto cleanup;
|
|
+ }
|
|
+
|
|
+ instance->midi.tx_cnt += n;
|
|
+ instance->midi.output_buffer_used_in_millibytes += n * 1000;
|
|
+
|
|
+ printd("Checking if empty %p", instance->midi.midi_output);
|
|
+ if (!snd_rawmidi_transmit_empty(instance->midi.midi_output)) {
|
|
+ unsigned int delay = 0;
|
|
+
|
|
+ if (instance->midi.output_buffer_used_in_millibytes >
|
|
+ MIDI_MAX_OUTPUT_BUFFER_SIZE_IN_MILLIBYTES - 127000)
|
|
+ delay = 127000 / MIDI_MILLI_BYTES_PER_JIFFY;
|
|
+ printd("Queue more work after %u jiffies", delay);
|
|
+ queue_delayed_work(instance->work_queue, &instance->midi.midi_out_handler, delay);
|
|
+ }
|
|
+
|
|
+cleanup:
|
|
+ mutex_unlock(&instance->midi.out_lock);
|
|
+ printd("Done");
|
|
+}
|
|
+
|
|
+static void upisnd_proc_stat_show(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
|
|
+{
|
|
+ const unsigned int *d = entry->private_data;
|
|
+
|
|
+ snd_iprintf(buffer, "%u\n", *d);
|
|
+}
|
|
+
|
|
+int upisnd_midi_init(struct upisnd_instance *instance)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ mutex_init(&instance->midi.in_lock);
|
|
+ mutex_init(&instance->midi.out_lock);
|
|
+
|
|
+ err = snd_card_ro_proc_new(instance->sound_card.snd_card, "tx", &instance->midi.tx_cnt,
|
|
+ upisnd_proc_stat_show);
|
|
+ err = snd_card_ro_proc_new(instance->sound_card.snd_card, "rx", &instance->midi.rx_cnt,
|
|
+ upisnd_proc_stat_show);
|
|
+
|
|
+ err = snd_rawmidi_new(instance->sound_card.snd_card,
|
|
+ "pisoundmicro", 0, 1, 1,
|
|
+ &instance->midi.rawmidi);
|
|
+
|
|
+ struct snd_rawmidi *rawmidi = instance->midi.rawmidi;
|
|
+
|
|
+ if (err < 0) {
|
|
+ printe("snd_rawmidi_new failed: %d\n", err);
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ strscpy(rawmidi->name, "pisound-micro ", sizeof(rawmidi->name));
|
|
+ strcat(rawmidi->name, instance->ctrl.serial);
|
|
+
|
|
+ rawmidi->info_flags =
|
|
+ SNDRV_RAWMIDI_INFO_OUTPUT |
|
|
+ SNDRV_RAWMIDI_INFO_INPUT |
|
|
+ SNDRV_RAWMIDI_INFO_DUPLEX;
|
|
+
|
|
+ rawmidi->ops = &upisnd_midi_ops;
|
|
+
|
|
+ rawmidi->private_data = instance;
|
|
+
|
|
+ snd_rawmidi_set_ops(rawmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &upisnd_midi_output_ops);
|
|
+
|
|
+ snd_rawmidi_set_ops(rawmidi, SNDRV_RAWMIDI_STREAM_INPUT, &upisnd_midi_input_ops);
|
|
+
|
|
+ INIT_KFIFO(instance->midi.midi_in_fifo);
|
|
+ instance->midi.last_midi_output_at = jiffies;
|
|
+
|
|
+ INIT_WORK(&instance->midi.midi_in_handler, upisnd_midi_in_handler);
|
|
+ INIT_DELAYED_WORK(&instance->midi.midi_out_handler, upisnd_midi_out_handler);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+void upisnd_midi_uninit(struct upisnd_instance *instance)
|
|
+{
|
|
+ if (!instance->midi.rawmidi)
|
|
+ return;
|
|
+
|
|
+ cancel_work_sync(&instance->midi.midi_in_handler);
|
|
+ cancel_delayed_work_sync(&instance->midi.midi_out_handler);
|
|
+
|
|
+ instance->midi.rawmidi->private_data = NULL;
|
|
+
|
|
+ instance->midi.rawmidi = NULL;
|
|
+ mutex_lock(&instance->midi.in_lock);
|
|
+ instance->midi.midi_input = NULL;
|
|
+ mutex_unlock(&instance->midi.in_lock);
|
|
+ mutex_lock(&instance->midi.out_lock);
|
|
+ instance->midi.midi_output = NULL;
|
|
+ mutex_unlock(&instance->midi.out_lock);
|
|
+
|
|
+ kfifo_free(&instance->midi.midi_in_fifo);
|
|
+}
|
|
+
|
|
+void upisnd_handle_midi_data(struct upisnd_instance *instance, const uint8_t *data, unsigned int n)
|
|
+{
|
|
+ printd("%p, %u", instance, n);
|
|
+ if (n == 0)
|
|
+ return;
|
|
+
|
|
+ int i;
|
|
+
|
|
+ for (i = 0; i < n; ++i) {
|
|
+ kfifo_put(&instance->midi.midi_in_fifo, data[i]);
|
|
+ printd("Received MIDI %02x", data[i]);
|
|
+ }
|
|
+
|
|
+ if (!work_pending(&instance->midi.midi_in_handler))
|
|
+ queue_work(instance->work_queue, &instance->midi.midi_in_handler);
|
|
+ printd("Done");
|
|
+}
|
|
+
|
|
+/* vim: set ts=8 sw=8 noexpandtab: */
|
|
--- /dev/null
|
|
+++ b/sound/drivers/upisnd/upisnd_midi.h
|
|
@@ -0,0 +1,47 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0-only */
|
|
+/*
|
|
+ * Pisound Micro Linux kernel module.
|
|
+ * Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/
|
|
+ *
|
|
+ * 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.
|
|
+ */
|
|
+
|
|
+#ifndef UPISOUND_MIDI_H
|
|
+#define UPISOUND_MIDI_H
|
|
+
|
|
+struct upisnd_instance;
|
|
+
|
|
+struct upisnd_midi {
|
|
+ // in_lock protects the input substream.
|
|
+ struct mutex in_lock;
|
|
+ // out_lock protects the output substream.
|
|
+ struct mutex out_lock;
|
|
+ struct work_struct midi_in_handler;
|
|
+ struct delayed_work midi_out_handler;
|
|
+
|
|
+ struct snd_rawmidi *rawmidi;
|
|
+ struct snd_rawmidi_substream *midi_input;
|
|
+ struct snd_rawmidi_substream *midi_output;
|
|
+ DECLARE_KFIFO(midi_in_fifo, uint8_t, 4096);
|
|
+ unsigned int last_midi_output_at;
|
|
+ unsigned int output_buffer_used_in_millibytes;
|
|
+ unsigned int tx_cnt;
|
|
+ unsigned int rx_cnt;
|
|
+};
|
|
+
|
|
+int upisnd_midi_init(struct upisnd_instance *instance);
|
|
+void upisnd_midi_uninit(struct upisnd_instance *instance);
|
|
+
|
|
+void upisnd_handle_midi_data(struct upisnd_instance *instance, const uint8_t *data, unsigned int n);
|
|
+
|
|
+#endif // UPISOUND_MIDI_H
|
|
+
|
|
+/* vim: set ts=8 sw=8 noexpandtab: */
|
|
--- /dev/null
|
|
+++ b/sound/drivers/upisnd/upisnd_module.c
|
|
@@ -0,0 +1,273 @@
|
|
+// SPDX-License-Identifier: GPL-2.0-only
|
|
+/*
|
|
+ * Pisound Micro Linux kernel module.
|
|
+ * Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/
|
|
+ *
|
|
+ * 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.
|
|
+ */
|
|
+
|
|
+#include "upisnd_common.h"
|
|
+
|
|
+void upisnd_instance_release(struct kref *kref)
|
|
+{
|
|
+ struct upisnd_instance *instance = container_of(kref, struct upisnd_instance, refcount);
|
|
+
|
|
+ if (instance->codec_dev) {
|
|
+ put_device(instance->codec_dev);
|
|
+ instance->codec_dev = NULL;
|
|
+ }
|
|
+
|
|
+ if (instance->ctrl_dev) {
|
|
+ instance->ctrl_dev->platform_data = NULL;
|
|
+ put_device(instance->ctrl_dev);
|
|
+ instance->ctrl_dev = NULL;
|
|
+ }
|
|
+
|
|
+ if (instance->sound_card.dev) {
|
|
+ snd_soc_unregister_card(&instance->sound_card);
|
|
+ memset(&instance->sound_card, 0, sizeof(instance->sound_card));
|
|
+ }
|
|
+
|
|
+ if (instance->work_queue) {
|
|
+ flush_workqueue(instance->work_queue);
|
|
+ destroy_workqueue(instance->work_queue);
|
|
+ instance->work_queue = NULL;
|
|
+ }
|
|
+
|
|
+ printd("Releasing instance %p", instance);
|
|
+ kfree(instance);
|
|
+}
|
|
+
|
|
+static int of_dev_node_match(struct device *dev, const void *data)
|
|
+{
|
|
+ return dev->of_node == data;
|
|
+}
|
|
+
|
|
+static int upisnd_probe(struct platform_device *pdev)
|
|
+{
|
|
+ printd("Load %p", pdev);
|
|
+
|
|
+ int err = 0;
|
|
+ struct device_node *node;
|
|
+ struct device_node *i2c_node;
|
|
+ struct device *dev;
|
|
+
|
|
+ struct upisnd_instance *instance = pdev->dev.platform_data;
|
|
+
|
|
+ if (!instance) {
|
|
+ instance = kzalloc(sizeof(*instance), GFP_KERNEL);
|
|
+
|
|
+ if (!instance) {
|
|
+ printe("Failed to allocate instance!");
|
|
+ err = -ENOMEM;
|
|
+ goto cleanup;
|
|
+ }
|
|
+ kref_init(&instance->refcount);
|
|
+
|
|
+ pdev->dev.platform_data = instance;
|
|
+ instance->pdev = pdev;
|
|
+
|
|
+ init_rwsem(&instance->rw_gpio_config_sem);
|
|
+
|
|
+ instance->work_queue = create_singlethread_workqueue("upisnd_workqueue");
|
|
+ if (!instance->work_queue) {
|
|
+ printe("Failed creating single thread work queue!");
|
|
+ err = -ENOMEM;
|
|
+ goto cleanup;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ node = pdev->dev.of_node;
|
|
+ if (!node) {
|
|
+ printe("Device node not found!");
|
|
+ err = -ENODEV;
|
|
+ goto cleanup;
|
|
+ }
|
|
+
|
|
+ i2c_node = of_parse_phandle(node, "ctrl", 0);
|
|
+ if (!i2c_node) {
|
|
+ printe("Failed to read 'ctrl' node!");
|
|
+ err = -ENODEV;
|
|
+ goto cleanup;
|
|
+ }
|
|
+
|
|
+ dev = bus_find_device(&i2c_bus_type, NULL, i2c_node, of_dev_node_match);
|
|
+ of_node_put(i2c_node);
|
|
+
|
|
+ if (!dev) {
|
|
+ printe("Failed to find 'ctrl' device (%pOF)!", i2c_node);
|
|
+ err = -ENODEV;
|
|
+ goto cleanup;
|
|
+ }
|
|
+
|
|
+ bool got_ref = false;
|
|
+
|
|
+ if (!dev->platform_data) {
|
|
+ kref_get(&instance->refcount);
|
|
+ dev->platform_data = instance;
|
|
+ got_ref = true;
|
|
+ }
|
|
+ instance->ctrl_dev = dev;
|
|
+
|
|
+ if (instance->ctrl.serial[0] == '\0') {
|
|
+ printd("Deferring probe until serial is retrieved!");
|
|
+ return -EPROBE_DEFER;
|
|
+ }
|
|
+
|
|
+ i2c_node = of_parse_phandle(node, "codec", 0);
|
|
+
|
|
+ if (!i2c_node) {
|
|
+ printe("Failed to read 'codec' node!");
|
|
+ err = -ENODEV;
|
|
+ goto cleanup;
|
|
+ }
|
|
+
|
|
+ dev = bus_find_device(&i2c_bus_type, NULL, i2c_node, of_dev_node_match);
|
|
+ of_node_put(i2c_node);
|
|
+
|
|
+ if (!dev) {
|
|
+ printe("Failed to find 'codec' device!");
|
|
+ err = -ENODEV;
|
|
+ goto cleanup;
|
|
+ }
|
|
+
|
|
+ instance->codec_dev = dev;
|
|
+
|
|
+ err = upisnd_sound_init(pdev, instance);
|
|
+ if (err != 0) {
|
|
+ if (err != -EPROBE_DEFER)
|
|
+ printe("Failed initializing sound card! (%d)", err);
|
|
+ goto cleanup;
|
|
+ }
|
|
+
|
|
+cleanup:
|
|
+ if (err != 0) {
|
|
+ if (err != -EPROBE_DEFER)
|
|
+ printe("Error %d!", err);
|
|
+
|
|
+ if (instance) {
|
|
+ if (got_ref) {
|
|
+ instance->ctrl_dev->platform_data = NULL;
|
|
+ kref_put(&instance->refcount, &upisnd_instance_release);
|
|
+ }
|
|
+
|
|
+ if (instance->codec_dev) {
|
|
+ put_device(instance->codec_dev);
|
|
+ instance->codec_dev = NULL;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static void upisnd_remove(struct platform_device *pdev)
|
|
+{
|
|
+ printd("Unload %p", pdev);
|
|
+ struct upisnd_instance *instance = dev_get_platdata(&pdev->dev);
|
|
+
|
|
+ kref_put(&instance->refcount, &upisnd_instance_release);
|
|
+ pdev->dev.platform_data = NULL;
|
|
+}
|
|
+
|
|
+static const struct of_device_id upisnd_of_match[] = {
|
|
+ {.compatible = "blokas,pisound-micro" },
|
|
+ {}
|
|
+};
|
|
+
|
|
+static const struct of_device_id upisnd_ctrl_of_match[] = {
|
|
+ {.compatible = "blokas,upisnd-ctrl" },
|
|
+ {}
|
|
+};
|
|
+
|
|
+static struct platform_driver upisnd_driver = {
|
|
+ .driver = {
|
|
+ .name = "snd-pisound-micro",
|
|
+ .owner = THIS_MODULE,
|
|
+ .of_match_table = of_match_ptr(upisnd_of_match),
|
|
+ },
|
|
+ .probe = upisnd_probe,
|
|
+ .remove = upisnd_remove,
|
|
+};
|
|
+
|
|
+static const struct i2c_device_id upisnd_ctrl_idtable[] = {
|
|
+ { "blokas,upisnd-ctrl", 0 },
|
|
+ {}
|
|
+};
|
|
+
|
|
+static struct i2c_driver upisnd_ctrl_driver = {
|
|
+ .driver = {
|
|
+ .name = "snd-pisound-micro-ctrl",
|
|
+ .owner = THIS_MODULE,
|
|
+ .of_match_table = of_match_ptr(upisnd_ctrl_of_match),
|
|
+ },
|
|
+ .id_table = upisnd_ctrl_idtable,
|
|
+ .probe = upisnd_ctrl_probe,
|
|
+ .remove = upisnd_ctrl_remove,
|
|
+};
|
|
+
|
|
+static int upisnd_module_init(void)
|
|
+{
|
|
+ int err, progress = 0;
|
|
+
|
|
+ err = upisnd_comm_module_init();
|
|
+ if (err != 0)
|
|
+ goto cleanup;
|
|
+
|
|
+ ++progress;
|
|
+ err = platform_driver_register(&upisnd_driver);
|
|
+ if (err != 0)
|
|
+ goto cleanup;
|
|
+
|
|
+ ++progress;
|
|
+ err = i2c_add_driver(&upisnd_ctrl_driver);
|
|
+ if (err != 0)
|
|
+ goto cleanup;
|
|
+
|
|
+cleanup:
|
|
+ if (err) {
|
|
+ printe("Error %d occurred, progress: %d", err, progress);
|
|
+ switch (progress) {
|
|
+ case 2:
|
|
+ i2c_del_driver(&upisnd_ctrl_driver);
|
|
+ fallthrough;
|
|
+ case 1:
|
|
+ platform_driver_unregister(&upisnd_driver);
|
|
+ fallthrough;
|
|
+ case 0:
|
|
+ // No comm uninit.
|
|
+ fallthrough;
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static void upisnd_module_exit(void)
|
|
+{
|
|
+ i2c_del_driver(&upisnd_ctrl_driver);
|
|
+ platform_driver_unregister(&upisnd_driver);
|
|
+}
|
|
+
|
|
+module_init(upisnd_module_init);
|
|
+module_exit(upisnd_module_exit);
|
|
+
|
|
+MODULE_DEVICE_TABLE(of, upisnd_of_match);
|
|
+MODULE_DEVICE_TABLE(of, upisnd_ctrl_of_match);
|
|
+MODULE_DEVICE_TABLE(i2c, upisnd_ctrl_idtable);
|
|
+
|
|
+MODULE_AUTHOR("Giedrius Trainavi\xc4\x8dius <giedrius@blokas.io>");
|
|
+MODULE_DESCRIPTION("Audio, MIDI & I/O Driver for Pisound Micro, https://blokas.io/");
|
|
+MODULE_LICENSE("GPL v2");
|
|
+
|
|
+/* vim: set ts=8 sw=8 noexpandtab: */
|
|
--- /dev/null
|
|
+++ b/sound/drivers/upisnd/upisnd_pins.c
|
|
@@ -0,0 +1,113 @@
|
|
+// SPDX-License-Identifier: GPL-2.0-only
|
|
+/*
|
|
+ * Pisound Micro Linux kernel module.
|
|
+ * Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/
|
|
+ *
|
|
+ * 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.
|
|
+ */
|
|
+
|
|
+#include "upisnd_common.h"
|
|
+
|
|
+struct upisnd_pin_def_t {
|
|
+ const char *name;
|
|
+ upisnd_pin_capability_mask_t capabilities;
|
|
+};
|
|
+
|
|
+static const struct upisnd_pin_def_t upisnd_pins[UPISND_PIN_COUNT] = {
|
|
+ { "A27", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY },
|
|
+ { "A28", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY },
|
|
+ { "A29", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY },
|
|
+ { "A30", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY },
|
|
+ { "A31", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY },
|
|
+ { "A32", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY },
|
|
+ { "B03", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
|
|
+ { "B04", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
|
|
+ { "B05", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
|
|
+ { "B06", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
|
|
+ { "B07", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
|
|
+ { "B08", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
|
|
+ { "B09", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
|
|
+ { "B10", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
|
|
+ // Pull down is not available in MCU revisions A, B, C, D, E. F and G are not affected.
|
|
+ { "B11", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
|
|
+ // Pull down is not available in MCU revisions A, B, C, D, E. F and G are not affected.
|
|
+ { "B12", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
|
|
+ { "B13", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
|
|
+ { "B14", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
|
|
+ { "B15", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
|
|
+ { "B16", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
|
|
+ { "B17", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
|
|
+ { "B18", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
|
|
+ { "B23", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
|
|
+ { "B24", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
|
|
+ { "B25", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
|
|
+ { "B26", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
|
|
+ { "B27", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
|
|
+ { "B28", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
|
|
+ { "B29", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
|
|
+ { "B30", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
|
|
+ { "B31", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
|
|
+ { "B32", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
|
|
+ { "B33", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
|
|
+ { "B34", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
|
|
+ { "B37", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY },
|
|
+ { "B38", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY },
|
|
+ { "B39", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY },
|
|
+};
|
|
+
|
|
+upisnd_pin_t upisnd_name_to_pin(const char *name)
|
|
+{
|
|
+ if (!name || strlen(name) != 3 || !isdigit(name[1]) || !isdigit(name[2]))
|
|
+ return UPISND_PIN_INVALID;
|
|
+
|
|
+ char sanitized[4];
|
|
+
|
|
+ switch (*name) {
|
|
+ case 'a': case 'A':
|
|
+ sanitized[0] = 'A';
|
|
+ break;
|
|
+ case 'b': case 'B':
|
|
+ sanitized[0] = 'B';
|
|
+ break;
|
|
+ default:
|
|
+ return UPISND_PIN_INVALID;
|
|
+ }
|
|
+
|
|
+ memcpy(&sanitized[1], &name[1], 2);
|
|
+ sanitized[3] = '\0';
|
|
+
|
|
+ int i;
|
|
+
|
|
+ for (i = 0; i < ARRAY_SIZE(upisnd_pins); ++i) {
|
|
+ if (strcmp(sanitized, upisnd_pins[i].name) == 0)
|
|
+ return i;
|
|
+ }
|
|
+
|
|
+ return UPISND_PIN_INVALID;
|
|
+}
|
|
+
|
|
+const char *upisnd_pin_name(upisnd_pin_t pin)
|
|
+{
|
|
+ if (!upisnd_is_pin_valid(pin))
|
|
+ return "";
|
|
+
|
|
+ return upisnd_pins[pin].name;
|
|
+}
|
|
+
|
|
+int upisnd_check_caps(upisnd_pin_t pin, upisnd_pin_capability_mask_t mask)
|
|
+{
|
|
+ if (!upisnd_is_pin_valid(pin))
|
|
+ return -ENXIO;
|
|
+
|
|
+ return (upisnd_pins[pin].capabilities & mask) == mask ? 0 : -EINVAL;
|
|
+}
|
|
+
|
|
+/* vim: set ts=8 sw=8 noexpandtab: */
|
|
--- /dev/null
|
|
+++ b/sound/drivers/upisnd/upisnd_pins.h
|
|
@@ -0,0 +1,67 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0-only */
|
|
+/*
|
|
+ * Pisound Micro Linux kernel module.
|
|
+ * Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/
|
|
+ *
|
|
+ * 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.
|
|
+ */
|
|
+
|
|
+#ifndef UPISOUND_PINS_H
|
|
+#define UPISOUND_PINS_H
|
|
+
|
|
+enum { UPISND_NUM_GPIOS = 37 };
|
|
+
|
|
+enum upisnd_pin_e {
|
|
+ UPISND_PIN_A27, UPISND_PIN_A28, UPISND_PIN_A29, UPISND_PIN_A30,
|
|
+ UPISND_PIN_A31, UPISND_PIN_A32, UPISND_PIN_B03, UPISND_PIN_B04,
|
|
+ UPISND_PIN_B05, UPISND_PIN_B06, UPISND_PIN_B07, UPISND_PIN_B08,
|
|
+ UPISND_PIN_B09, UPISND_PIN_B10, UPISND_PIN_B11, UPISND_PIN_B12,
|
|
+ UPISND_PIN_B13, UPISND_PIN_B14, UPISND_PIN_B15, UPISND_PIN_B16,
|
|
+ UPISND_PIN_B17, UPISND_PIN_B18, UPISND_PIN_B23, UPISND_PIN_B24,
|
|
+ UPISND_PIN_B25, UPISND_PIN_B26, UPISND_PIN_B27, UPISND_PIN_B28,
|
|
+ UPISND_PIN_B29, UPISND_PIN_B30, UPISND_PIN_B31, UPISND_PIN_B32,
|
|
+ UPISND_PIN_B33, UPISND_PIN_B34, UPISND_PIN_B37, UPISND_PIN_B38,
|
|
+ UPISND_PIN_B39,
|
|
+
|
|
+ UPISND_PIN_COUNT,
|
|
+ UPISND_PIN_INVALID = UPISND_PIN_COUNT
|
|
+};
|
|
+
|
|
+typedef u8 upisnd_pin_t;
|
|
+
|
|
+static inline bool upisnd_is_pin_valid(upisnd_pin_t pin)
|
|
+{
|
|
+ return pin < UPISND_PIN_COUNT;
|
|
+}
|
|
+
|
|
+enum upisnd_pin_capability_flags_e {
|
|
+ UPISND_PIN_CAP_GPIO_DIR_INPUT = 1 << 0,
|
|
+ UPISND_PIN_CAP_GPIO_DIR_OUTPUT = 1 << 1,
|
|
+ UPISND_PIN_CAP_GPIO_PULL_UP = 1 << 2,
|
|
+ UPISND_PIN_CAP_GPIO_PULL_DOWN = 1 << 3,
|
|
+ UPISND_PIN_CAP_GPIO = UPISND_PIN_CAP_GPIO_DIR_INPUT |
|
|
+ UPISND_PIN_CAP_GPIO_DIR_OUTPUT |
|
|
+ UPISND_PIN_CAP_GPIO_PULL_UP |
|
|
+ UPISND_PIN_CAP_GPIO_PULL_DOWN,
|
|
+ UPISND_PIN_CAP_ENCODER = 1 << 4,
|
|
+ UPISND_PIN_CAP_ANALOG_IN = 1 << 5,
|
|
+ UPISND_PIN_CAP_MIDI_ACTIVITY = 1 << 6,
|
|
+};
|
|
+
|
|
+typedef u8 upisnd_pin_capability_mask_t;
|
|
+
|
|
+upisnd_pin_t upisnd_name_to_pin(const char *name);
|
|
+const char *upisnd_pin_name(upisnd_pin_t pin);
|
|
+int upisnd_check_caps(upisnd_pin_t pin, upisnd_pin_capability_mask_t mask);
|
|
+
|
|
+#endif // UPISOUND_PINS_H
|
|
+
|
|
+/* vim: set ts=8 sw=8 noexpandtab: */
|
|
--- /dev/null
|
|
+++ b/sound/drivers/upisnd/upisnd_protocol.h
|
|
@@ -0,0 +1,322 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0-only */
|
|
+/*
|
|
+ * Pisound Micro Linux kernel module.
|
|
+ * Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/
|
|
+ *
|
|
+ * 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.
|
|
+ */
|
|
+
|
|
+#ifndef UPISND_PROTOCOL_H
|
|
+#define UPISND_PROTOCOL_H
|
|
+
|
|
+#ifndef __KERNEL__
|
|
+#include <stdint.h>
|
|
+#include <stdbool.h>
|
|
+
|
|
+enum upisnd_irq_type_e {
|
|
+ IRQ_TYPE_NONE = 0,
|
|
+ IRQ_TYPE_EDGE_RISING = 1,
|
|
+ IRQ_TYPE_EDGE_FALLING = 2,
|
|
+ IRQ_TYPE_EDGES_BOTH = 3,
|
|
+};
|
|
+#endif
|
|
+
|
|
+enum { UPISND_ON_BIT_MASK = 0x80 };
|
|
+enum { UPISND_IRQ_NUM_MASK = 0x7f };
|
|
+enum { UPISND_PIN_MASK = 0x3f };
|
|
+
|
|
+enum upisnd_irq_num_e {
|
|
+ UPISND_IRQ_GPIO_START = 0,
|
|
+ // IRQ numbers match GPIO numbers.
|
|
+ UPISND_IRQ_GPIO_END = UPISND_PIN_MASK,
|
|
+
|
|
+ UPISND_IRQ_VGND_SHORT_ALERT = 0x7f,
|
|
+};
|
|
+
|
|
+typedef u8 upisnd_pin_t;
|
|
+typedef u8 upisnd_irq_num_t;
|
|
+typedef u8 upisnd_msg_id_t;
|
|
+
|
|
+typedef u32 upisnd_setup_t;
|
|
+
|
|
+enum upisnd_activity_type_e {
|
|
+ UPISND_ACTIVITY_TYPE_MIDI_IN = 0,
|
|
+ UPISND_ACTIVITY_TYPE_MIDI_OUT = 1,
|
|
+};
|
|
+
|
|
+enum upisnd_pin_pull_e {
|
|
+ UPISND_PIN_PULL_NONE = 0,
|
|
+ UPISND_PIN_PULL_UP = 1,
|
|
+ UPISND_PIN_PULL_DOWN = 2
|
|
+};
|
|
+
|
|
+enum upisnd_pin_direction_e {
|
|
+ UPISND_PIN_DIR_INPUT = 0,
|
|
+ UPISND_PIN_DIR_OUTPUT = 1
|
|
+};
|
|
+
|
|
+enum upisnd_element_type_e {
|
|
+ UPISND_ELEMENT_TYPE_NONE = 0,
|
|
+ UPISND_ELEMENT_TYPE_ENCODER = 1,
|
|
+ UPISND_ELEMENT_TYPE_ANALOG_IN = 2,
|
|
+ UPISND_ELEMENT_TYPE_GPIO = 3,
|
|
+ UPISND_ELEMENT_TYPE_ACTIVITY = 4,
|
|
+};
|
|
+
|
|
+#define UPISND_DEFINE_SETUP_FIELD(shift, bits, type, name) \
|
|
+ static inline type upisnd_setup_get_ ## name(upisnd_setup_t setup) \
|
|
+ { \
|
|
+ return (type)(((setup) & (((1 << (bits)) - 1) << (shift))) >> (shift)); \
|
|
+ } \
|
|
+ static inline void upisnd_setup_set_ ## name(upisnd_setup_t *setup, type value) \
|
|
+ { \
|
|
+ *(setup) = ((*(setup)) & ~(((1 << (bits)) - 1) << (shift))) | \
|
|
+ (((value) & ((1 << (bits)) - 1)) << (shift)); \
|
|
+ }
|
|
+
|
|
+UPISND_DEFINE_SETUP_FIELD(0, 3, enum upisnd_element_type_e, element_type);
|
|
+UPISND_DEFINE_SETUP_FIELD(3, 8, upisnd_pin_t, pin_id);
|
|
+UPISND_DEFINE_SETUP_FIELD(11, 2, enum upisnd_pin_pull_e, gpio_pull);
|
|
+UPISND_DEFINE_SETUP_FIELD(13, 1, enum upisnd_pin_direction_e, gpio_dir);
|
|
+UPISND_DEFINE_SETUP_FIELD(12, 1, bool, gpio_output);
|
|
+UPISND_DEFINE_SETUP_FIELD(13, 8, upisnd_pin_t, encoder_pin_b_id);
|
|
+UPISND_DEFINE_SETUP_FIELD(21, 2, enum upisnd_pin_pull_e, encoder_pin_b_pull);
|
|
+UPISND_DEFINE_SETUP_FIELD(11, 2, enum upisnd_activity_type_e, activity_type);
|
|
+
|
|
+#undef UPISND_DEFINE_SETUP_FIELD
|
|
+
|
|
+struct upisnd_irq_type_config_t {
|
|
+ u8 irq_types[10];
|
|
+} __packed;
|
|
+
|
|
+static inline void upisnd_irq_config_set_irq_type(struct upisnd_irq_type_config_t *cfg,
|
|
+ upisnd_pin_t pin,
|
|
+ unsigned int type)
|
|
+{
|
|
+ u8 *d = &cfg->irq_types[pin >> 2];
|
|
+ *d = (*d & ~(0x3 << (pin & 0x3))) | ((type & 0x3) << (pin & 0x3));
|
|
+}
|
|
+
|
|
+static inline unsigned int upisnd_irq_config_get_irq_type(const struct upisnd_irq_type_config_t
|
|
+ *cfg, upisnd_pin_t pin)
|
|
+{
|
|
+ return (cfg->irq_types[pin >> 2] >> (pin & 0x3)) & 0x3;
|
|
+}
|
|
+
|
|
+struct upisnd_irq_mask_config_t {
|
|
+ u8 irq_mask[5];
|
|
+} __packed;
|
|
+
|
|
+static inline void upisnd_irq_mask_config_set(struct upisnd_irq_mask_config_t *cfg,
|
|
+ upisnd_pin_t pin,
|
|
+ bool mask)
|
|
+{
|
|
+ u8 *d = &cfg->irq_mask[pin >> 3];
|
|
+
|
|
+ if (mask)
|
|
+ *d |= 1 << (pin & 0x7);
|
|
+ else
|
|
+ *d &= ~(1 << (pin & 0x7));
|
|
+}
|
|
+
|
|
+static inline bool upisnd_irq_mask_config_get(const struct upisnd_irq_mask_config_t *cfg,
|
|
+ upisnd_pin_t pin)
|
|
+{
|
|
+ return (cfg->irq_mask[pin >> 3] & (1 << (pin & 0x7))) != 0;
|
|
+}
|
|
+
|
|
+struct upisnd_all_gpio_state_t {
|
|
+ u8 state[5];
|
|
+} __packed;
|
|
+
|
|
+enum { UPISND_MAX_PACKET_LENGTH = 16 };
|
|
+enum { UPISND_MSG_RESPONSE_FLAG = 0x80 };
|
|
+enum { UPISND_MSG_ID_MASK = 0x7f };
|
|
+enum { UPISND_MSG_ID_INVALID = 0x00 };
|
|
+
|
|
+enum upisnd_cmd_type_e {
|
|
+ UPISND_CMD_MIDI = 0x00,
|
|
+ UPISND_CMD_SETUP = 0x10,
|
|
+ UPISND_CMD_SET_IRQ_TYPES = 0x20,
|
|
+ UPISND_CMD_SET_IRQ_MASKS = 0x30,
|
|
+ UPISND_CMD_SET_GPIO = 0x40,
|
|
+ UPISND_CMD_GET_GPIO = 0x50,
|
|
+ UPISND_CMD_SET_ALL_GPIOS = 0x60,
|
|
+ UPISND_CMD_GET_ALL_GPIOS = 0x70,
|
|
+ UPISND_CMD_RESULT = 0x80,
|
|
+ UPISND_CMD_SET_SUBSCRIPTION = 0x90,
|
|
+ UPISND_CMD_IRQ_EVENT = 0xa0,
|
|
+ UPISND_CMD_GET = 0xb0,
|
|
+ UPISND_CMD_SET = 0xc0,
|
|
+ UPISND_CMD_CONTROL_EVENT = 0xd0,
|
|
+
|
|
+ UPISND_CMD_INVALID = 0xff
|
|
+};
|
|
+
|
|
+enum { UPISND_CMD_TYPE_MASK = 0xf0 };
|
|
+enum { UPISND_CMD_LENGTH_MASK = 0x0f };
|
|
+
|
|
+enum upisnd_value_id_t {
|
|
+ UPISND_VALUE_INVALID = 0x00,
|
|
+ UPISND_VALUE_VERSION_INFO = 0x01,
|
|
+ UPISND_VALUE_ADC_OFFSET = 0x02,
|
|
+ UPISND_VALUE_ADC_GAIN = 0x03,
|
|
+ UPISND_VALUE_SERIAL_NUMBER = 0x04,
|
|
+ UPISND_VALUE_ELEMENT_VAL_BASE = 0x80,
|
|
+};
|
|
+
|
|
+struct upisnd_cmd_t {
|
|
+ u8 cmd_and_size;
|
|
+ u8 flags_and_msg_id;
|
|
+} __packed;
|
|
+
|
|
+static inline bool upisnd_cmd_is_response(const struct upisnd_cmd_t *cmd)
|
|
+{
|
|
+ return (cmd->flags_and_msg_id & UPISND_MSG_RESPONSE_FLAG) != 0;
|
|
+}
|
|
+
|
|
+struct upisnd_cmd_setup_t {
|
|
+ struct upisnd_cmd_t cmd;
|
|
+ upisnd_setup_t setup;
|
|
+} __packed;
|
|
+
|
|
+struct upisnd_cmd_set_gpio_t {
|
|
+ u8 cmd_and_size;
|
|
+ u8 on_and_pin;
|
|
+} __packed;
|
|
+
|
|
+struct upisnd_cmd_get_gpio_t {
|
|
+ struct upisnd_cmd_t cmd;
|
|
+ u8 pin;
|
|
+} __packed;
|
|
+
|
|
+struct upisnd_cmd_set_all_gpios_t {
|
|
+ u8 cmd_and_size;
|
|
+ struct upisnd_all_gpio_state_t state;
|
|
+} __packed;
|
|
+
|
|
+struct upisnd_cmd_get_all_gpios_t {
|
|
+ struct upisnd_cmd_t cmd;
|
|
+} __packed;
|
|
+
|
|
+struct upisnd_cmd_get_all_gpios_response_t {
|
|
+ struct upisnd_cmd_t cmd;
|
|
+ struct upisnd_all_gpio_state_t state;
|
|
+} __packed;
|
|
+
|
|
+struct upisnd_cmd_set_irq_types_t {
|
|
+ struct upisnd_cmd_t cmd;
|
|
+ struct upisnd_irq_type_config_t config;
|
|
+} __packed;
|
|
+
|
|
+struct upisnd_cmd_set_irq_masks_t {
|
|
+ struct upisnd_cmd_t cmd;
|
|
+ struct upisnd_irq_mask_config_t config;
|
|
+} __packed;
|
|
+
|
|
+struct upisnd_cmd_set_subscription_t {
|
|
+ struct upisnd_cmd_t cmd;
|
|
+ u8 on_and_irq_num;
|
|
+} __packed;
|
|
+
|
|
+struct upisnd_cmd_result_t {
|
|
+ struct upisnd_cmd_t cmd;
|
|
+ s8 result;
|
|
+} __packed;
|
|
+
|
|
+struct upisnd_cmd_get_t {
|
|
+ struct upisnd_cmd_t cmd;
|
|
+ u8 value_id;
|
|
+} __packed;
|
|
+
|
|
+enum { UPISND_CMD_GET_RESPONSE_INT32_SIZE = sizeof(struct upisnd_cmd_get_t) + sizeof(int8_t) +
|
|
+ sizeof(int32_t) };
|
|
+enum { UPISND_CMD_GET_RESPONSE_DATA_MIN_SIZE = sizeof(struct upisnd_cmd_get_t) + sizeof(int8_t) };
|
|
+
|
|
+struct upisnd_cmd_get_response_t {
|
|
+ struct upisnd_cmd_get_t cmd_get;
|
|
+ s8 result;
|
|
+ union {
|
|
+ s32 value;
|
|
+ u8 data[12];
|
|
+ };
|
|
+} __packed;
|
|
+
|
|
+struct upisnd_cmd_set_t {
|
|
+ struct upisnd_cmd_t cmd;
|
|
+ u8 value_id;
|
|
+ s32 value;
|
|
+} __packed;
|
|
+
|
|
+struct upisnd_cmd_control_event_t {
|
|
+ u8 cmd_and_size;
|
|
+ u16 values[7];
|
|
+} __packed;
|
|
+
|
|
+static inline enum upisnd_cmd_type_e upisnd_cmd_decode_type(u8 b)
|
|
+{
|
|
+ return (enum upisnd_cmd_type_e)(b & UPISND_CMD_TYPE_MASK);
|
|
+}
|
|
+
|
|
+static inline u8 upisnd_cmd_decode_length(u8 b)
|
|
+{
|
|
+ return (b & UPISND_CMD_LENGTH_MASK) + 1u;
|
|
+}
|
|
+
|
|
+static inline u8 upisnd_cmd_encode(enum upisnd_cmd_type_e type, u8 size)
|
|
+{
|
|
+ return (size < UPISND_MAX_PACKET_LENGTH && size > 0) ? (type | (size - 1)) : 0xff;
|
|
+}
|
|
+
|
|
+static inline u8 upisnd_msg_id_encode(upisnd_msg_id_t msg_id, bool response)
|
|
+{
|
|
+ return (response ? 0x80 : 0x00) | (msg_id & UPISND_MSG_ID_MASK);
|
|
+}
|
|
+
|
|
+static inline upisnd_msg_id_t upisnd_msg_id_decode_id(u8 b)
|
|
+{
|
|
+ return b & UPISND_MSG_ID_MASK;
|
|
+}
|
|
+
|
|
+static inline void upisnd_cmd_prepare(struct upisnd_cmd_t *cmd,
|
|
+ enum upisnd_cmd_type_e type,
|
|
+ u8 size,
|
|
+ upisnd_msg_id_t msg_id,
|
|
+ bool response)
|
|
+{
|
|
+ cmd->cmd_and_size = upisnd_cmd_encode(type, size);
|
|
+ cmd->flags_and_msg_id = upisnd_msg_id_encode(msg_id, response);
|
|
+}
|
|
+
|
|
+#define upisnd_cmd_matches(_cmd, type, size) \
|
|
+ (upisnd_cmd_decode_type((_cmd)->cmd_and_size) == (type) && \
|
|
+ upisnd_cmd_decode_length((_cmd)->cmd_and_size) >= (size))
|
|
+
|
|
+static inline bool upisnd_cmd_type_has_msg_id(enum upisnd_cmd_type_e type)
|
|
+{
|
|
+ switch (type) {
|
|
+ default:
|
|
+ return false;
|
|
+ case UPISND_CMD_SETUP:
|
|
+ case UPISND_CMD_SET_IRQ_TYPES:
|
|
+ case UPISND_CMD_SET_IRQ_MASKS:
|
|
+ case UPISND_CMD_GET_GPIO:
|
|
+ case UPISND_CMD_GET_ALL_GPIOS:
|
|
+ case UPISND_CMD_RESULT:
|
|
+ case UPISND_CMD_GET:
|
|
+ case UPISND_CMD_SET:
|
|
+ return true;
|
|
+ }
|
|
+}
|
|
+
|
|
+#endif // UPISND_PROTOCOL_H
|
|
+
|
|
+/* vim: set ts=8 sw=8 noexpandtab: */
|
|
--- /dev/null
|
|
+++ b/sound/drivers/upisnd/upisnd_sound.c
|
|
@@ -0,0 +1,277 @@
|
|
+// SPDX-License-Identifier: GPL-2.0-only
|
|
+/*
|
|
+ * Pisound Micro Linux kernel module.
|
|
+ * Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/
|
|
+ *
|
|
+ * 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.
|
|
+ */
|
|
+
|
|
+#include "upisnd_common.h"
|
|
+#include "upisnd_codec.h"
|
|
+
|
|
+static void upisnd_proc_serial_show(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
|
|
+{
|
|
+ struct upisnd_instance *instance = entry->private_data;
|
|
+
|
|
+ snd_iprintf(buffer, "%s\n", instance->ctrl.serial);
|
|
+}
|
|
+
|
|
+static void upisnd_proc_version_show(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
|
|
+{
|
|
+ struct upisnd_instance *instance = entry->private_data;
|
|
+
|
|
+ snd_iprintf(buffer,
|
|
+ "%u.%u.%u\n",
|
|
+ instance->ctrl.version.major,
|
|
+ instance->ctrl.version.minor,
|
|
+ instance->ctrl.version.build);
|
|
+}
|
|
+
|
|
+static void upisnd_proc_hwrev_show(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
|
|
+{
|
|
+ struct upisnd_instance *instance = entry->private_data;
|
|
+
|
|
+ snd_iprintf(buffer, "%u\n", instance->ctrl.version.hwrev);
|
|
+}
|
|
+
|
|
+static int upisnd_card_probe(struct snd_soc_card *card)
|
|
+{
|
|
+ struct upisnd_instance *instance = container_of(card, struct upisnd_instance, sound_card);
|
|
+ int err;
|
|
+
|
|
+ err = upisnd_midi_init(instance);
|
|
+
|
|
+ if (err < 0) {
|
|
+ printe("Failed to initialize MIDI subsystem! (%d)", err);
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ err = snd_card_ro_proc_new(card->snd_card, "serial", instance, upisnd_proc_serial_show);
|
|
+
|
|
+ if (err < 0) {
|
|
+ printe("Failed to create serial proc entry! (%d)", err);
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ err = snd_card_ro_proc_new(card->snd_card, "version", instance, upisnd_proc_version_show);
|
|
+
|
|
+ if (err < 0) {
|
|
+ printe("Failed to create version proc entry! (%d)", err);
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ err = snd_card_ro_proc_new(card->snd_card, "hwrev", instance, upisnd_proc_hwrev_show);
|
|
+
|
|
+ if (err < 0) {
|
|
+ printe("Failed to create hwrev proc entry! (%d)", err);
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int upisnd_card_remove(struct snd_soc_card *card)
|
|
+{
|
|
+ struct upisnd_instance *instance = container_of(card, struct upisnd_instance, sound_card);
|
|
+
|
|
+ upisnd_midi_uninit(instance);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int upisnd_startup(struct snd_pcm_substream *substream)
|
|
+{
|
|
+ printd("startup");
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int upisnd_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
|
|
+{
|
|
+ printd("hw_params");
|
|
+
|
|
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
|
+
|
|
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
|
|
+ int ret = 0;
|
|
+
|
|
+ struct upisnd_instance *instance = container_of(rtd->card,
|
|
+ struct upisnd_instance,
|
|
+ sound_card);
|
|
+
|
|
+ if (!(instance->flags & UPISND_FLAG_DUMMY))
|
|
+ ret = snd_soc_dai_set_bclk_ratio(cpu_dai, 64);
|
|
+
|
|
+ if (ret < 0) {
|
|
+ printe("Failed setting dai bclk ratio!\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int upisnd_dai_init(struct snd_soc_pcm_runtime *rtd)
|
|
+{
|
|
+ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
|
|
+
|
|
+ return snd_soc_dai_set_tdm_slot(codec_dai, 0, 0, 0, 0); // Enable I2S mode.
|
|
+}
|
|
+
|
|
+static const struct snd_soc_ops upisnd_ops = {
|
|
+ .startup = upisnd_startup,
|
|
+ .hw_params = upisnd_hw_params,
|
|
+};
|
|
+
|
|
+SND_SOC_DAILINK_DEFS(adau1961,
|
|
+ DAILINK_COMP_ARRAY(COMP_EMPTY()),
|
|
+ DAILINK_COMP_ARRAY(COMP_CODEC("adau1961", "adau-hifi")),
|
|
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
|
|
+
|
|
+static const struct snd_soc_dai_link upisnd_dai_link = {
|
|
+ .name = "pisound-micro",
|
|
+ .stream_name = "pisound-micro PCM",
|
|
+ .dai_fmt =
|
|
+ SND_SOC_DAIFMT_I2S |
|
|
+ SND_SOC_DAIFMT_NB_NF |
|
|
+ SND_SOC_DAIFMT_CBM_CFM,
|
|
+ .ops = &upisnd_ops,
|
|
+ .init = upisnd_dai_init,
|
|
+ SND_SOC_DAILINK_REG(adau1961),
|
|
+};
|
|
+
|
|
+static const struct snd_soc_card upisnd_sound_card = {
|
|
+ .name = "pisoundmicro",
|
|
+ .owner = THIS_MODULE,
|
|
+ .probe = upisnd_card_probe,
|
|
+ .remove = upisnd_card_remove,
|
|
+};
|
|
+
|
|
+int upisnd_sound_init(struct platform_device *pdev, struct upisnd_instance *instance)
|
|
+{
|
|
+ struct snd_soc_dai_link_component *comp = NULL;
|
|
+ struct device_node *i2s_node = NULL;
|
|
+
|
|
+ memcpy(&instance->sound_card, &upisnd_sound_card, sizeof(upisnd_sound_card));
|
|
+ memcpy(&instance->dai_link, &upisnd_dai_link, sizeof(upisnd_dai_link));
|
|
+
|
|
+ if (pdev->dev.of_node) {
|
|
+ of_property_read_string(pdev->dev.of_node, "card-name", &instance->sound_card.name);
|
|
+ i2s_node = of_parse_phandle(pdev->dev.of_node, "i2s-controller", 0);
|
|
+ if (!i2s_node)
|
|
+ printi("'i2s-controller' node not specified, will use dummy one instead!");
|
|
+ }
|
|
+
|
|
+ comp = devm_kzalloc(&pdev->dev, sizeof(*comp) * 3, GFP_KERNEL);
|
|
+ if (!comp)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ char *long_name = devm_kzalloc(&pdev->dev, 26, GFP_KERNEL);
|
|
+
|
|
+ if (!long_name)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ instance->dai_link.cpus = &comp[0];
|
|
+ instance->dai_link.codecs = &comp[1];
|
|
+ instance->dai_link.platforms = &comp[2];
|
|
+
|
|
+ instance->dai_link.num_cpus = 1;
|
|
+ instance->dai_link.num_codecs = 1;
|
|
+ instance->dai_link.num_platforms = 1;
|
|
+
|
|
+ if (i2s_node) {
|
|
+ instance->dai_link.cpus->of_node = i2s_node;
|
|
+ instance->dai_link.platforms->of_node = i2s_node;
|
|
+ } else {
|
|
+ printi("Setting up dummy interface.");
|
|
+ instance->dai_link.cpus->name = "snd-soc-dummy";
|
|
+ instance->dai_link.platforms->name = "snd-soc-dummy";
|
|
+ instance->dai_link.cpus->dai_name = "snd-soc-dummy-dai";
|
|
+ instance->dai_link.platforms->dai_name = "snd-soc-dummy-dai";
|
|
+ instance->dai_link.dai_fmt = (instance->dai_link.dai_fmt & ~SND_SOC_DAIFMT_CBM_CFM)
|
|
+ | SND_SOC_DAIFMT_CBS_CFS;
|
|
+ instance->flags |= UPISND_FLAG_DUMMY;
|
|
+ }
|
|
+ instance->dai_link.codecs->of_node = instance->codec_dev->of_node;
|
|
+ instance->dai_link.codecs->dai_name = "adau-hifi";
|
|
+ instance->dai_link.stream_name = instance->ctrl.serial;
|
|
+
|
|
+ instance->sound_card.dev = &pdev->dev;
|
|
+
|
|
+ instance->sound_card.dai_link = &instance->dai_link;
|
|
+ instance->sound_card.num_links = 1;
|
|
+
|
|
+ snprintf(long_name, 26, "Pisound Micro %s", instance->ctrl.serial);
|
|
+ instance->sound_card.long_name = long_name;
|
|
+ printd("About to register card %s", instance->sound_card.long_name);
|
|
+
|
|
+ int err = snd_soc_register_card(&instance->sound_card);
|
|
+
|
|
+ if (i2s_node)
|
|
+ of_node_put(i2s_node);
|
|
+
|
|
+ if (err < 0) {
|
|
+ instance->sound_card.dev = NULL;
|
|
+ if (err != -EPROBE_DEFER)
|
|
+ printe("snd_soc_register_card failed with %d!", err);
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ struct snd_soc_dai *dai = snd_soc_card_get_codec_dai(&instance->sound_card, "adau-hifi");
|
|
+
|
|
+ if (!dai) {
|
|
+ printe("Failed to get codec dai!");
|
|
+ instance->sound_card.dev = NULL;
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ if (adau1961_is_hp_capless(dai->component)) {
|
|
+ err = upisnd_comm_set_subscription(instance, UPISND_IRQ_VGND_SHORT_ALERT, true);
|
|
+ if (err < 0) {
|
|
+ instance->sound_card.dev = NULL;
|
|
+ printe("Failed to subscribe to VGND short alert IRQ! (%d)", err);
|
|
+ return err;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void upisnd_sound_handle_irq_event(struct upisnd_instance *instance,
|
|
+ const struct irq_event_t *event)
|
|
+{
|
|
+ struct snd_soc_dai *dai = NULL;
|
|
+
|
|
+ switch (event->num) {
|
|
+ case UPISND_IRQ_VGND_SHORT_ALERT:
|
|
+ dai = snd_soc_card_get_codec_dai(&instance->sound_card, "adau-hifi");
|
|
+ printe("VGND short alert %s Headphone output!",
|
|
+ event->high ? "ON, muting" : "OFF, restoring last state");
|
|
+ if (dai)
|
|
+ adau1961_set_vgnd_shorted(dai->component, event->high);
|
|
+ else
|
|
+ printe("Failed to get codec dai!");
|
|
+ break;
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+}
|
|
+
|
|
+void upisnd_sound_handle_irq_events(struct upisnd_instance *instance,
|
|
+ const struct irq_event_t *events,
|
|
+ unsigned int n)
|
|
+{
|
|
+ unsigned int i;
|
|
+
|
|
+ printd("Handling %u sound IRQ events", n);
|
|
+
|
|
+ for (i = 0; i < n; ++i)
|
|
+ upisnd_sound_handle_irq_event(instance, &events[i]);
|
|
+}
|
|
+
|
|
+/* vim: set ts=8 sw=8 noexpandtab: */
|
|
--- /dev/null
|
|
+++ b/sound/drivers/upisnd/upisnd_sound.h
|
|
@@ -0,0 +1,28 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0-only */
|
|
+/*
|
|
+ * Pisound Micro Linux kernel module.
|
|
+ * Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/
|
|
+ *
|
|
+ * 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.
|
|
+ */
|
|
+
|
|
+#ifndef UPISOUND_SOUND_H
|
|
+#define UPISOUND_SOUND_H
|
|
+
|
|
+int upisnd_sound_init(struct platform_device *pdev, struct upisnd_instance *instance);
|
|
+
|
|
+void upisnd_sound_handle_irq_events(struct upisnd_instance *instance,
|
|
+ const struct irq_event_t *events,
|
|
+ unsigned int n);
|
|
+
|
|
+#endif // UPISOUND_SOUND_H
|
|
+
|
|
+/* vim: set ts=8 sw=8 noexpandtab: */
|
|
--- /dev/null
|
|
+++ b/sound/drivers/upisnd/upisnd_sysfs.c
|
|
@@ -0,0 +1,1728 @@
|
|
+// SPDX-License-Identifier: GPL-2.0-only
|
|
+/*
|
|
+ * Pisound Micro Linux kernel module.
|
|
+ * Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/
|
|
+ *
|
|
+ * 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.
|
|
+ */
|
|
+
|
|
+#include "upisnd_common.h"
|
|
+
|
|
+enum {
|
|
+ // Max lengths are with '\0' included.
|
|
+ MAX_ELEMENT_NAME_LENGTH = 64,
|
|
+ MAX_SETUP_REQUEST_LENGTH = 63 + MAX_ELEMENT_NAME_LENGTH,
|
|
+};
|
|
+
|
|
+static void upisnd_config_release(struct kobject *kobj);
|
|
+static void upisnd_element_release(struct kobject *kobj);
|
|
+
|
|
+static ssize_t upisnd_attr_show(struct kobject *kobj, struct attribute *attr, char *buf)
|
|
+{
|
|
+ printd("hi %s %s", kobject_name(kobj), attr->name);
|
|
+ struct kobj_attribute *attribute = container_of(attr, struct kobj_attribute, attr);
|
|
+
|
|
+ if (!attribute->show)
|
|
+ return -EIO;
|
|
+
|
|
+ int err = attribute->show(kobj, attribute, buf);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static ssize_t upisnd_attr_store(struct kobject *kobj,
|
|
+ struct attribute *attr,
|
|
+ const char *buf,
|
|
+ size_t len)
|
|
+{
|
|
+ struct kobj_attribute *attribute = container_of(attr, struct kobj_attribute, attr);
|
|
+
|
|
+ if (!attribute->store)
|
|
+ return -EIO;
|
|
+
|
|
+ int err = attribute->store(kobj, attribute, buf, len);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static const struct sysfs_ops upisnd_attr_ops = {
|
|
+ .show = &upisnd_attr_show,
|
|
+ .store = &upisnd_attr_store
|
|
+};
|
|
+
|
|
+#define to_config(kobj_) container_of(kobj_, struct upisnd_config, kset.kobj)
|
|
+struct upisnd_config {
|
|
+ struct kset kset;
|
|
+ struct upisnd_instance *instance;
|
|
+ struct kset *elements;
|
|
+};
|
|
+
|
|
+static int upisnd_element_cleanup(struct kobject *kobj, struct upisnd_instance *instance);
|
|
+
|
|
+static void upisnd_config_release(struct kobject *kobj)
|
|
+{
|
|
+ printd("hi");
|
|
+ struct upisnd_config *cfg = to_config(kobj);
|
|
+
|
|
+ kfree(cfg);
|
|
+}
|
|
+
|
|
+static const struct kobj_type upisnd_config_type = {
|
|
+ .release = &upisnd_config_release,
|
|
+ .sysfs_ops = &upisnd_attr_ops
|
|
+};
|
|
+
|
|
+static const struct kobj_type upisnd_element_type = {
|
|
+ .release = &upisnd_element_release,
|
|
+ .sysfs_ops = &upisnd_attr_ops
|
|
+};
|
|
+
|
|
+// Used for handling encoder value overflow.
|
|
+enum upisnd_value_mode {
|
|
+ UPISND_VALUE_MODE_CLAMP = 0,
|
|
+ UPISND_VALUE_MODE_WRAP,
|
|
+};
|
|
+
|
|
+#define to_element(kobj_) container_of(kobj_, struct upisnd_element, kobj)
|
|
+struct upisnd_element {
|
|
+ struct kobject kobj;
|
|
+ // Mapped from upisnd pins to gpio pin numbering.
|
|
+ // gpio_pins[1] is used only for encoder B pin.
|
|
+ // Negative number is used to indicate invalid/unused pin.
|
|
+ int gpio_pins[2];
|
|
+ int value;
|
|
+ int raw_value;
|
|
+ int input_min;
|
|
+ int input_max;
|
|
+ int value_low;
|
|
+ int value_high;
|
|
+ enum upisnd_value_mode value_mode;
|
|
+};
|
|
+
|
|
+// Returns true if the value changed.
|
|
+static bool upisnd_element_update_value(struct upisnd_element *el)
|
|
+{
|
|
+ int old_value = el->value;
|
|
+
|
|
+ int value = el->raw_value;
|
|
+
|
|
+ switch (el->value_mode) {
|
|
+ case UPISND_VALUE_MODE_CLAMP:
|
|
+ value = min(max(value, el->input_min), el->input_max);
|
|
+ break;
|
|
+ case UPISND_VALUE_MODE_WRAP:
|
|
+ if (el->input_max != el->input_min) {
|
|
+ while (value < el->input_min)
|
|
+ value += el->input_max - el->input_min;
|
|
+ value = (value - el->input_min) % (el->input_max - el->input_min)
|
|
+ + el->input_min;
|
|
+ } else {
|
|
+ value = el->input_min;
|
|
+ }
|
|
+ break;
|
|
+ default:
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ el->raw_value = value;
|
|
+ el->value = upisnd_map_value_range(value, el->input_min, el->input_max, el->value_low,
|
|
+ el->value_high);
|
|
+
|
|
+ return el->value != old_value;
|
|
+}
|
|
+
|
|
+static int upisnd_parse_gpio_dir(enum upisnd_pin_direction_e *dir, char **s, const char *sep)
|
|
+{
|
|
+ if (!dir || !s || !sep)
|
|
+ return -EINVAL;
|
|
+
|
|
+ char *t = strsep(s, sep);
|
|
+
|
|
+ if (!t || *t == '\0')
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (strncasecmp(t, "input", 6u) == 0) {
|
|
+ *dir = UPISND_PIN_DIR_INPUT;
|
|
+ return 0;
|
|
+ } else if (strncasecmp(t, "output", 7u) == 0) {
|
|
+ *dir = UPISND_PIN_DIR_OUTPUT;
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ return -EINVAL;
|
|
+}
|
|
+
|
|
+static int upisnd_parse_gpio_input_pull(enum upisnd_pin_pull_e *pull, char **s, const char *sep)
|
|
+{
|
|
+ if (!pull || !s || !sep)
|
|
+ return -EINVAL;
|
|
+
|
|
+ char *t = strsep(s, sep);
|
|
+
|
|
+ if (!t || *t == '\0')
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (strncasecmp(t, "pull_up", 8u) == 0) {
|
|
+ *pull = UPISND_PIN_PULL_UP;
|
|
+ return 0;
|
|
+ } else if (strncasecmp(t, "pull_down", 10u) == 0) {
|
|
+ *pull = UPISND_PIN_PULL_DOWN;
|
|
+ return 0;
|
|
+ } else if (strncasecmp(t, "pull_none", 10u) == 0) {
|
|
+ *pull = UPISND_PIN_PULL_NONE;
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ return -EINVAL;
|
|
+}
|
|
+
|
|
+static int upisnd_parse_gpio_output_level(bool *level, char **s, const char *sep)
|
|
+{
|
|
+ if (!level || !s || !sep)
|
|
+ return -EINVAL;
|
|
+
|
|
+ char *t = strsep(s, sep);
|
|
+
|
|
+ if (!t || *t == '\0')
|
|
+ return -EINVAL;
|
|
+
|
|
+ return kstrtobool(t, level);
|
|
+}
|
|
+
|
|
+static int upisnd_parse_pin(upisnd_pin_t *pin, char **s, const char *sep)
|
|
+{
|
|
+ if (!pin || !s || !sep)
|
|
+ return -EINVAL;
|
|
+
|
|
+ char *t = strsep(s, sep);
|
|
+
|
|
+ *pin = upisnd_name_to_pin(t);
|
|
+
|
|
+ return upisnd_is_pin_valid(*pin) ? 0 : -EINVAL;
|
|
+}
|
|
+
|
|
+static int upisnd_validate_setup(upisnd_setup_t setup)
|
|
+{
|
|
+ switch (upisnd_setup_get_element_type(setup)) {
|
|
+ case UPISND_ELEMENT_TYPE_ENCODER:
|
|
+ {
|
|
+ int a = upisnd_check_caps(upisnd_setup_get_pin_id(setup), UPISND_PIN_CAP_ENCODER);
|
|
+ int b = upisnd_check_caps(upisnd_setup_get_encoder_pin_b_id(setup),
|
|
+ UPISND_PIN_CAP_ENCODER);
|
|
+
|
|
+ if (a < 0 || b < 0) {
|
|
+ printe("Invalid or incapable pins for Encoder (%d, %d)", a, b);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ return 0;
|
|
+ }
|
|
+ case UPISND_ELEMENT_TYPE_ANALOG_IN:
|
|
+ {
|
|
+ int err = upisnd_check_caps(upisnd_setup_get_pin_id(setup),
|
|
+ UPISND_PIN_CAP_ANALOG_IN);
|
|
+
|
|
+ if (err < 0) {
|
|
+ printe("Invalid or incapable pin for Analog In (%d)", err);
|
|
+ return err;
|
|
+ }
|
|
+ return 0;
|
|
+ }
|
|
+ case UPISND_ELEMENT_TYPE_GPIO:
|
|
+ {
|
|
+ int err = upisnd_check_caps(upisnd_setup_get_pin_id(setup), UPISND_PIN_CAP_GPIO);
|
|
+
|
|
+ if (err < 0) {
|
|
+ printe("Invalid or incapable pin for GPIO (%d)", err);
|
|
+ return err;
|
|
+ }
|
|
+ return 0;
|
|
+ }
|
|
+ case UPISND_ELEMENT_TYPE_ACTIVITY:
|
|
+ {
|
|
+ int err;
|
|
+
|
|
+ switch (upisnd_setup_get_activity_type(setup)) {
|
|
+ case UPISND_ACTIVITY_TYPE_MIDI_IN:
|
|
+ case UPISND_ACTIVITY_TYPE_MIDI_OUT:
|
|
+ err = upisnd_check_caps(upisnd_setup_get_pin_id(setup),
|
|
+ UPISND_PIN_CAP_MIDI_ACTIVITY);
|
|
+ if (err < 0) {
|
|
+ printe("Invalid or incapable pin for MIDI Activity (%d)", err);
|
|
+ return err;
|
|
+ }
|
|
+ break;
|
|
+ default:
|
|
+ printe("Invalid activity type!");
|
|
+ err = -EINVAL;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ return err;
|
|
+ }
|
|
+ break;
|
|
+ case UPISND_ELEMENT_TYPE_NONE:
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+}
|
|
+
|
|
+static int upisnd_parse_setup(upisnd_setup_t *setup, const char *buf, size_t len)
|
|
+{
|
|
+ if (!setup || !buf || len + 1 >= MAX_SETUP_REQUEST_LENGTH)
|
|
+ return -EINVAL;
|
|
+
|
|
+ *setup = 0;
|
|
+ upisnd_setup_set_element_type(setup, UPISND_ELEMENT_TYPE_NONE);
|
|
+
|
|
+ char b[MAX_SETUP_REQUEST_LENGTH];
|
|
+
|
|
+ strscpy(b, buf, len + 1);
|
|
+
|
|
+ static const char *const SEP = "\n\t ";
|
|
+
|
|
+ char *s = b;
|
|
+ char *token = strsep(&s, SEP);
|
|
+
|
|
+ if (!token || *token == '\0')
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (strncasecmp(token, "encoder", 8u) == 0) {
|
|
+ upisnd_setup_set_element_type(setup, UPISND_ELEMENT_TYPE_ENCODER);
|
|
+ } else if (strncasecmp(token, "analog_in", 10u) == 0) {
|
|
+ upisnd_setup_set_element_type(setup, UPISND_ELEMENT_TYPE_ANALOG_IN);
|
|
+ } else if (strncasecmp(token, "activity_", 9u) == 0) {
|
|
+ if (strncasecmp(&token[9], "midi_in", 8u) == 0)
|
|
+ upisnd_setup_set_activity_type(setup, UPISND_ACTIVITY_TYPE_MIDI_IN);
|
|
+ else if (strncasecmp(&token[9], "midi_out", 9u) == 0)
|
|
+ upisnd_setup_set_activity_type(setup, UPISND_ACTIVITY_TYPE_MIDI_OUT);
|
|
+ else
|
|
+ return -EINVAL;
|
|
+ upisnd_setup_set_element_type(setup, UPISND_ELEMENT_TYPE_ACTIVITY);
|
|
+ } else if (strncasecmp(token, "gpio", 5u) == 0) {
|
|
+ upisnd_setup_set_element_type(setup, UPISND_ELEMENT_TYPE_GPIO);
|
|
+ } else {
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ int err = 0;
|
|
+
|
|
+ upisnd_pin_t pin;
|
|
+ enum upisnd_pin_direction_e dir;
|
|
+ enum upisnd_pin_pull_e pull;
|
|
+ bool output;
|
|
+
|
|
+ switch (upisnd_setup_get_element_type(*setup)) {
|
|
+ case UPISND_ELEMENT_TYPE_ANALOG_IN:
|
|
+ case UPISND_ELEMENT_TYPE_ACTIVITY:
|
|
+ case UPISND_ELEMENT_TYPE_GPIO:
|
|
+ case UPISND_ELEMENT_TYPE_ENCODER:
|
|
+ err = upisnd_parse_pin(&pin, &s, SEP);
|
|
+
|
|
+ if (err != 0)
|
|
+ break;
|
|
+
|
|
+ upisnd_setup_set_pin_id(setup, pin);
|
|
+
|
|
+ switch (upisnd_setup_get_element_type(*setup)) {
|
|
+ case UPISND_ELEMENT_TYPE_ENCODER:
|
|
+ err = upisnd_parse_gpio_input_pull(&pull, &s, SEP);
|
|
+ if (err != 0)
|
|
+ break;
|
|
+ upisnd_setup_set_gpio_pull(setup, pull);
|
|
+
|
|
+ err = upisnd_parse_pin(&pin, &s, SEP);
|
|
+ if (err != 0)
|
|
+ break;
|
|
+ upisnd_setup_set_encoder_pin_b_id(setup, pin);
|
|
+
|
|
+ err = upisnd_parse_gpio_input_pull(&pull, &s, SEP);
|
|
+ if (err == 0)
|
|
+ upisnd_setup_set_encoder_pin_b_pull(setup, pull);
|
|
+ break;
|
|
+ case UPISND_ELEMENT_TYPE_GPIO:
|
|
+ err = upisnd_parse_gpio_dir(&dir, &s, SEP);
|
|
+ if (err != 0)
|
|
+ break;
|
|
+
|
|
+ upisnd_setup_set_gpio_dir(setup, dir);
|
|
+
|
|
+ switch (dir) {
|
|
+ case UPISND_PIN_DIR_INPUT:
|
|
+ err = upisnd_parse_gpio_input_pull(&pull, &s, SEP);
|
|
+ if (err == 0)
|
|
+ upisnd_setup_set_gpio_pull(setup, pull);
|
|
+ break;
|
|
+ case UPISND_PIN_DIR_OUTPUT:
|
|
+ err = upisnd_parse_gpio_output_level(&output, &s, SEP);
|
|
+ if (err == 0)
|
|
+ upisnd_setup_set_gpio_output(setup, output);
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ break;
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ if (err >= 0)
|
|
+ err = upisnd_validate_setup(*setup);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static ssize_t upisnd_element_type_show(struct kobject *kobj, struct kobj_attribute *attr,
|
|
+ char *buf);
|
|
+static ssize_t upisnd_element_value_show(struct kobject *kobj, struct kobj_attribute *attr,
|
|
+ char *buf);
|
|
+static ssize_t upisnd_element_value_store(struct kobject *kobj, struct kobj_attribute *attr,
|
|
+ const char *buf, size_t len);
|
|
+static ssize_t upisnd_element_input_min_show(struct kobject *kobj, struct kobj_attribute *attr,
|
|
+ char *buf);
|
|
+static ssize_t upisnd_element_input_min_store(struct kobject *kobj, struct kobj_attribute *attr,
|
|
+ const char *buf, size_t len);
|
|
+static ssize_t upisnd_element_input_max_show(struct kobject *kobj, struct kobj_attribute *attr,
|
|
+ char *buf);
|
|
+static ssize_t upisnd_element_input_max_store(struct kobject *kobj, struct kobj_attribute *attr,
|
|
+ const char *buf, size_t len);
|
|
+static ssize_t upisnd_element_value_low_show(struct kobject *kobj, struct kobj_attribute *attr,
|
|
+ char *buf);
|
|
+static ssize_t upisnd_element_value_low_store(struct kobject *kobj, struct kobj_attribute *attr,
|
|
+ const char *buf, size_t len);
|
|
+static ssize_t upisnd_element_value_high_show(struct kobject *kobj, struct kobj_attribute *attr,
|
|
+ char *buf);
|
|
+static ssize_t upisnd_element_value_high_store(struct kobject *kobj, struct kobj_attribute *attr,
|
|
+ const char *buf, size_t len);
|
|
+static ssize_t upisnd_element_value_mode_show(struct kobject *kobj, struct kobj_attribute *attr,
|
|
+ char *buf);
|
|
+static ssize_t upisnd_element_value_mode_store(struct kobject *kobj, struct kobj_attribute *attr,
|
|
+ const char *buf, size_t len);
|
|
+static ssize_t upisnd_element_direction_show(struct kobject *kobj, struct kobj_attribute *attr,
|
|
+ char *buf);
|
|
+static ssize_t upisnd_element_pin_show(struct kobject *kobj, struct kobj_attribute *attr,
|
|
+ char *buf);
|
|
+static ssize_t upisnd_element_pin_name_show(struct kobject *kobj, struct kobj_attribute *attr,
|
|
+ char *buf);
|
|
+static ssize_t upisnd_element_pin_b_show(struct kobject *kobj, struct kobj_attribute *attr,
|
|
+ char *buf);
|
|
+static ssize_t upisnd_element_pin_b_name_show(struct kobject *kobj, struct kobj_attribute *attr,
|
|
+ char *buf);
|
|
+static ssize_t upisnd_element_activity_type_show(struct kobject *kobj, struct kobj_attribute *attr,
|
|
+ char *buf);
|
|
+static ssize_t upisnd_element_gpio_export(struct kobject *kobj, struct kobj_attribute *attr,
|
|
+ const char *buf, size_t len);
|
|
+static ssize_t upisnd_element_gpio_unexport(struct kobject *kobj, struct kobj_attribute *attr,
|
|
+ const char *buf, size_t len);
|
|
+
|
|
+static struct kobj_attribute upisnd_element_type_attr = __ATTR(type, 0444, upisnd_element_type_show,
|
|
+ NULL);
|
|
+static struct kobj_attribute upisnd_element_value_attr = __ATTR(value, 0664,
|
|
+ upisnd_element_value_show, upisnd_element_value_store);
|
|
+static struct kobj_attribute upisnd_element_input_min_attr = __ATTR(input_min, 0664,
|
|
+ upisnd_element_input_min_show, upisnd_element_input_min_store);
|
|
+static struct kobj_attribute upisnd_element_input_max_attr = __ATTR(input_max, 0664,
|
|
+ upisnd_element_input_max_show, upisnd_element_input_max_store);
|
|
+static struct kobj_attribute upisnd_element_value_low_attr = __ATTR(value_low, 0664,
|
|
+ upisnd_element_value_low_show, upisnd_element_value_low_store);
|
|
+static struct kobj_attribute upisnd_element_value_high_attr = __ATTR(value_high, 0664,
|
|
+ upisnd_element_value_high_show, upisnd_element_value_high_store);
|
|
+static struct kobj_attribute upisnd_element_value_mode_attr = __ATTR(value_mode, 0664,
|
|
+ upisnd_element_value_mode_show, upisnd_element_value_mode_store);
|
|
+static struct kobj_attribute upisnd_element_direction_attr = __ATTR(direction, 0444,
|
|
+ upisnd_element_direction_show, NULL);
|
|
+static struct kobj_attribute upisnd_element_pin_attr = __ATTR(pin, 0444, upisnd_element_pin_show,
|
|
+ NULL);
|
|
+static struct kobj_attribute upisnd_element_pin_name_attr = __ATTR(pin_name, 0444,
|
|
+ upisnd_element_pin_name_show, NULL);
|
|
+static struct kobj_attribute upisnd_element_pin_b_attr = __ATTR(pin_b, 0444,
|
|
+ upisnd_element_pin_b_show, NULL);
|
|
+static struct kobj_attribute upisnd_element_pin_b_name_attr = __ATTR(pin_b_name, 0444,
|
|
+ upisnd_element_pin_b_name_show, NULL);
|
|
+static struct kobj_attribute upisnd_element_activity_type_attr = __ATTR(activity_type, 0444,
|
|
+ upisnd_element_activity_type_show, NULL);
|
|
+static struct kobj_attribute upisnd_element_gpio_export_attr = __ATTR(gpio_export, 0220, NULL,
|
|
+ upisnd_element_gpio_export);
|
|
+static struct kobj_attribute upisnd_element_gpio_unexport_attr = __ATTR(gpio_unexport, 0220, NULL,
|
|
+ upisnd_element_gpio_unexport);
|
|
+
|
|
+static struct attribute *upisnd_element_attrs[] = {
|
|
+ &upisnd_element_type_attr.attr,
|
|
+ &upisnd_element_value_attr.attr,
|
|
+ &upisnd_element_input_min_attr.attr,
|
|
+ &upisnd_element_input_max_attr.attr,
|
|
+ &upisnd_element_value_low_attr.attr,
|
|
+ &upisnd_element_value_high_attr.attr,
|
|
+ &upisnd_element_value_mode_attr.attr,
|
|
+ &upisnd_element_direction_attr.attr,
|
|
+ &upisnd_element_pin_attr.attr,
|
|
+ &upisnd_element_pin_name_attr.attr,
|
|
+ &upisnd_element_pin_b_attr.attr,
|
|
+ &upisnd_element_pin_b_name_attr.attr,
|
|
+ &upisnd_element_activity_type_attr.attr,
|
|
+ &upisnd_element_gpio_export_attr.attr,
|
|
+ &upisnd_element_gpio_unexport_attr.attr,
|
|
+ NULL
|
|
+};
|
|
+
|
|
+static umode_t upisnd_element_attr_is_visible(struct kobject *kobj, struct attribute *attr, int idx)
|
|
+{
|
|
+ printd("Hi!");
|
|
+
|
|
+ struct upisnd_element *element = to_element(kobj);
|
|
+ struct upisnd_config *cfg = to_config(kobj->parent->parent);
|
|
+ struct upisnd_instance *instance = cfg->instance;
|
|
+
|
|
+ enum upisnd_element_type_e t = upisnd_setup_get_element_type(instance->gpio.pin_configs
|
|
+ [element->gpio_pins[0]].setup);
|
|
+
|
|
+ switch (t) {
|
|
+ case UPISND_ELEMENT_TYPE_ENCODER:
|
|
+ if (attr == &upisnd_element_value_mode_attr.attr ||
|
|
+ attr == &upisnd_element_pin_b_attr.attr ||
|
|
+ attr == &upisnd_element_pin_b_name_attr.attr)
|
|
+ return attr->mode;
|
|
+ fallthrough;
|
|
+ case UPISND_ELEMENT_TYPE_ANALOG_IN:
|
|
+ if (attr == &upisnd_element_input_min_attr.attr ||
|
|
+ attr == &upisnd_element_input_max_attr.attr ||
|
|
+ attr == &upisnd_element_value_low_attr.attr ||
|
|
+ attr == &upisnd_element_value_high_attr.attr)
|
|
+ return attr->mode;
|
|
+ break;
|
|
+ case UPISND_ELEMENT_TYPE_GPIO:
|
|
+ if (attr == &upisnd_element_gpio_export_attr.attr ||
|
|
+ attr == &upisnd_element_gpio_unexport_attr.attr ||
|
|
+ attr == &upisnd_element_direction_attr.attr)
|
|
+ return attr->mode;
|
|
+ break;
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ switch (t) {
|
|
+ case UPISND_ELEMENT_TYPE_ANALOG_IN:
|
|
+ case UPISND_ELEMENT_TYPE_ENCODER:
|
|
+ case UPISND_ELEMENT_TYPE_GPIO:
|
|
+ if (attr == &upisnd_element_value_attr.attr) {
|
|
+ if (t == UPISND_ELEMENT_TYPE_GPIO) {
|
|
+ if (upisnd_setup_get_gpio_dir(instance->gpio.pin_configs
|
|
+ [element->gpio_pins[0]].setup) == UPISND_PIN_DIR_INPUT)
|
|
+ return 0444;
|
|
+ }
|
|
+ return attr->mode;
|
|
+ }
|
|
+ break;
|
|
+ case UPISND_ELEMENT_TYPE_ACTIVITY:
|
|
+ if (attr == &upisnd_element_activity_type_attr.attr)
|
|
+ return attr->mode;
|
|
+ break;
|
|
+ case UPISND_ELEMENT_TYPE_NONE:
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if (attr == &upisnd_element_type_attr.attr ||
|
|
+ attr == &upisnd_element_pin_attr.attr ||
|
|
+ attr == &upisnd_element_pin_name_attr.attr)
|
|
+ return attr->mode;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct attribute_group upisnd_element_group = {
|
|
+ .attrs = upisnd_element_attrs,
|
|
+ .is_visible = &upisnd_element_attr_is_visible
|
|
+};
|
|
+
|
|
+static struct upisnd_element *upisnd_create_element(struct upisnd_config *config, const char *name)
|
|
+{
|
|
+ struct upisnd_element *el = kzalloc(sizeof(*el), GFP_KERNEL);
|
|
+
|
|
+ if (!el) {
|
|
+ printe("Failed allocating upisnd_element!");
|
|
+ return NULL;
|
|
+ }
|
|
+
|
|
+ el->kobj.kset = config->elements;
|
|
+ el->gpio_pins[0] = UPISND_PIN_INVALID;
|
|
+ el->gpio_pins[1] = UPISND_PIN_INVALID;
|
|
+
|
|
+ int err = kobject_init_and_add(&el->kobj, &upisnd_element_type, NULL, "%s", name);
|
|
+
|
|
+ if (err < 0) {
|
|
+ printe("Failed initializing upisnd_element kobject! (err=%d, name='%s')",
|
|
+ err, name);
|
|
+ goto cleanup;
|
|
+ }
|
|
+
|
|
+ return el;
|
|
+
|
|
+cleanup:
|
|
+ kobject_put(&el->kobj);
|
|
+ kfree(el);
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+static void upisnd_element_release(struct kobject *kobj)
|
|
+{
|
|
+ printd("hi");
|
|
+ struct upisnd_element *element = to_element(kobj);
|
|
+
|
|
+ kfree(element);
|
|
+}
|
|
+
|
|
+static int upisnd_setup_get_pins(int pins[2], upisnd_setup_t setup)
|
|
+{
|
|
+ pins[0] = UPISND_PIN_INVALID;
|
|
+ pins[1] = UPISND_PIN_INVALID;
|
|
+
|
|
+ switch (upisnd_setup_get_element_type(setup)) {
|
|
+ case UPISND_ELEMENT_TYPE_ENCODER:
|
|
+ pins[1] = upisnd_setup_get_encoder_pin_b_id(setup);
|
|
+ if (!upisnd_is_pin_valid(pins[1]))
|
|
+ pins[1] = UPISND_PIN_INVALID;
|
|
+ fallthrough;
|
|
+ case UPISND_ELEMENT_TYPE_ANALOG_IN:
|
|
+ case UPISND_ELEMENT_TYPE_GPIO:
|
|
+ case UPISND_ELEMENT_TYPE_NONE:
|
|
+ case UPISND_ELEMENT_TYPE_ACTIVITY:
|
|
+ pins[0] = upisnd_setup_get_pin_id(setup);
|
|
+ if (!upisnd_is_pin_valid(pins[0]))
|
|
+ pins[0] = UPISND_PIN_INVALID;
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int upisnd_setup_do(struct upisnd_instance *instance, struct upisnd_element *element,
|
|
+ upisnd_setup_t setup, int pins[2])
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ element->raw_value = 0;
|
|
+ element->value = 0;
|
|
+ element->input_min = 0;
|
|
+ element->value_low = 0;
|
|
+ element->input_max = upisnd_setup_get_element_type(setup) !=
|
|
+ UPISND_ELEMENT_TYPE_ENCODER ? 1023 : 23;
|
|
+ element->value_high = element->input_max;
|
|
+ element->value_mode = UPISND_VALUE_MODE_CLAMP;
|
|
+
|
|
+ int i;
|
|
+
|
|
+ for (i = 0; i < 2; ++i) {
|
|
+ if (pins[i] == UPISND_PIN_INVALID)
|
|
+ break;
|
|
+
|
|
+ int pin = pins[i];
|
|
+
|
|
+ printd("Check %d %08x %d %p %p", pin, instance->gpio.pin_configs[pin].setup,
|
|
+ upisnd_setup_get_element_type(instance->gpio.pin_configs[pin].setup),
|
|
+ instance->gpio.pin_configs[pin].element, element);
|
|
+
|
|
+ if (upisnd_setup_get_element_type(instance->gpio.pin_configs[pin].setup) !=
|
|
+ UPISND_ELEMENT_TYPE_NONE && instance->gpio.pin_configs[pin].element !=
|
|
+ element) {
|
|
+ printe("Pin %s already used via %s%s!",
|
|
+ upisnd_pin_name(pin),
|
|
+ instance->gpio.pin_configs[pin].element ? "sysfs " : "gpio",
|
|
+ instance->gpio.pin_configs[pin].element ?
|
|
+ instance->gpio.pin_configs[pin].element->kobj.name : "");
|
|
+ return -EBUSY;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // Unsetup previous config if necessary.
|
|
+ for (i = 0; i < 2; ++i) {
|
|
+ if (element->gpio_pins[i] != UPISND_PIN_INVALID &&
|
|
+ element->gpio_pins[i] != pins[i]) {
|
|
+ upisnd_setup_t s = instance->gpio.pin_configs[element->gpio_pins[i]].setup;
|
|
+
|
|
+ upisnd_setup_set_element_type(&s, UPISND_ELEMENT_TYPE_NONE);
|
|
+ upisnd_gpio_setup(instance, s);
|
|
+ instance->gpio.pin_configs[element->gpio_pins[i]].setup = s;
|
|
+ instance->gpio.pin_subscription_refcounts[element->gpio_pins[i]] = 0;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ err = upisnd_gpio_setup(instance, setup);
|
|
+
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ instance->gpio.pin_configs[pins[0]].element = element;
|
|
+ instance->gpio.pin_configs[pins[0]].flags = 0;
|
|
+ instance->gpio.pin_configs[pins[0]].setup = setup;
|
|
+ instance->gpio.pin_configs[pins[0]].gpio_desc = NULL;
|
|
+
|
|
+ element->gpio_pins[0] = pins[0];
|
|
+ element->gpio_pins[1] = pins[1];
|
|
+
|
|
+ switch (upisnd_setup_get_element_type(setup)) {
|
|
+ case UPISND_ELEMENT_TYPE_ENCODER:
|
|
+ upisnd_gpio_set_subscription(instance, pins[0], true);
|
|
+ upisnd_gpio_set_subscription(instance, pins[1], true);
|
|
+ instance->gpio.pin_configs[pins[1]].element = element;
|
|
+ instance->gpio.pin_configs[pins[1]].flags = UPISND_PIN_FLAG_IS_ENCODER_B;
|
|
+ instance->gpio.pin_configs[pins[1]].setup = setup;
|
|
+ instance->gpio.pin_configs[pins[1]].gpio_desc = NULL;
|
|
+ break;
|
|
+ case UPISND_ELEMENT_TYPE_GPIO:
|
|
+ if (upisnd_setup_get_gpio_dir(setup) != UPISND_PIN_DIR_INPUT)
|
|
+ break;
|
|
+ fallthrough;
|
|
+ default:
|
|
+ upisnd_gpio_set_subscription(instance, pins[0], true);
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static ssize_t upisnd_setup(struct kobject *kobj, struct kobj_attribute *attr, const char *buf,
|
|
+ size_t length)
|
|
+{
|
|
+ if (length + 1 >= MAX_SETUP_REQUEST_LENGTH) {
|
|
+ printe("Request exceeds maximum length!");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ int err;
|
|
+
|
|
+ char b[MAX_SETUP_REQUEST_LENGTH];
|
|
+
|
|
+ strscpy(b, buf, length + 1);
|
|
+
|
|
+ char *s = b;
|
|
+ char *name = strsep(&s, " /\n\t");
|
|
+
|
|
+ if (!name || *name == '\0' || !s || *s == '\0')
|
|
+ return -EINVAL;
|
|
+
|
|
+ upisnd_setup_t setup;
|
|
+
|
|
+ err = upisnd_parse_setup(&setup, s, length - (s - name));
|
|
+ if (err < 0) {
|
|
+ printe("Failed parsing setup request! (%d)", err);
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ int pins[2];
|
|
+
|
|
+ upisnd_setup_get_pins(pins, setup);
|
|
+
|
|
+ struct upisnd_config *cfg = to_config(kobj);
|
|
+
|
|
+ struct upisnd_instance *instance = cfg->instance;
|
|
+
|
|
+ down_write(&instance->rw_gpio_config_sem);
|
|
+
|
|
+ struct kobject *existing = kset_find_obj(cfg->elements, name);
|
|
+
|
|
+ if (existing) {
|
|
+ bool setup_matches = false;
|
|
+ struct upisnd_element *el = to_element(existing);
|
|
+
|
|
+ if (upisnd_is_pin_valid(el->gpio_pins[0]))
|
|
+ setup_matches = instance->gpio.pin_configs[el->gpio_pins[0]].setup == setup;
|
|
+
|
|
+ kobject_put(existing);
|
|
+ up_write(&instance->rw_gpio_config_sem);
|
|
+
|
|
+ if (setup_matches) {
|
|
+ printd("%s already existed and requested setup matched, returning success.",
|
|
+ name);
|
|
+ return length;
|
|
+ }
|
|
+
|
|
+ printe("%s is already setup! (%d)", name, -EEXIST);
|
|
+ return -EEXIST;
|
|
+ }
|
|
+
|
|
+ struct upisnd_element *element = upisnd_create_element(cfg, name);
|
|
+
|
|
+ if (!element) {
|
|
+ up_write(&instance->rw_gpio_config_sem);
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ err = upisnd_setup_do(instance, element, setup, pins);
|
|
+
|
|
+ if (err >= 0) {
|
|
+ err = sysfs_create_group(&element->kobj, &upisnd_element_group);
|
|
+ if (err < 0)
|
|
+ printe
|
|
+ ("Failed creating pisound-micro element attributes! (err=%d, name ='%s')",
|
|
+ err, name);
|
|
+ }
|
|
+
|
|
+ up_write(&instance->rw_gpio_config_sem);
|
|
+
|
|
+ if (err < 0) {
|
|
+ kobject_put(&element->kobj);
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ kobject_uevent(&element->kobj, KOBJ_ADD);
|
|
+ return length;
|
|
+}
|
|
+
|
|
+static ssize_t upisnd_unsetup(struct kobject *kobj, struct kobj_attribute *attr, const char *buf,
|
|
+ size_t length)
|
|
+{
|
|
+ if (length + 1 >= MAX_ELEMENT_NAME_LENGTH) {
|
|
+ printe("Element name exceeds maximum length!");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ char b[MAX_ELEMENT_NAME_LENGTH];
|
|
+
|
|
+ strscpy(b, buf, length + 1);
|
|
+
|
|
+ char *s = b;
|
|
+ char *name = strsep(&s, " /\n\t");
|
|
+
|
|
+ if (!name || *name == '\0')
|
|
+ return -EINVAL;
|
|
+
|
|
+ struct upisnd_config *cfg = to_config(kobj);
|
|
+
|
|
+ down_write(&cfg->instance->rw_gpio_config_sem);
|
|
+
|
|
+ struct kobject *existing = kset_find_obj(cfg->elements, name);
|
|
+
|
|
+ if (!existing) {
|
|
+ printe("%s not found!", name);
|
|
+ up_write(&cfg->instance->rw_gpio_config_sem);
|
|
+ return -ENOENT;
|
|
+ }
|
|
+
|
|
+ upisnd_element_cleanup(existing, cfg->instance);
|
|
+
|
|
+ printd("Found %p", existing);
|
|
+ kobject_put(existing); // kset_find_obj increments the
|
|
+ kobject_put(existing); // refcount, so we have to put twice.
|
|
+
|
|
+ up_write(&cfg->instance->rw_gpio_config_sem);
|
|
+
|
|
+ return length;
|
|
+}
|
|
+
|
|
+static ssize_t upisnd_adc_gain_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
|
|
+{
|
|
+ struct upisnd_config *cfg = to_config(kobj);
|
|
+
|
|
+ s32 adc_gain;
|
|
+ int err = upisnd_comm_get_value(cfg->instance, UPISND_VALUE_ADC_GAIN, &adc_gain);
|
|
+
|
|
+ if (err < 0) {
|
|
+ printe("Failed getting ADC gain value! (%d)", err);
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ return sprintf(buf, "%d\n", adc_gain);
|
|
+}
|
|
+
|
|
+static ssize_t upisnd_adc_gain_store(struct kobject *kobj, struct kobj_attribute *attr,
|
|
+ const char *buf, size_t length)
|
|
+{
|
|
+ struct upisnd_config *cfg = to_config(kobj);
|
|
+
|
|
+ int adc_gain;
|
|
+ int err = kstrtoint(buf, 10, &adc_gain);
|
|
+
|
|
+ if (err < 0) {
|
|
+ printe("Failed parsing ADC gain! (%d)", err);
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ err = upisnd_comm_set_value(cfg->instance, UPISND_VALUE_ADC_GAIN, adc_gain);
|
|
+ if (err < 0) {
|
|
+ printe("Failed setting ADC gain value! (%d)", err);
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ return length;
|
|
+}
|
|
+
|
|
+static ssize_t upisnd_adc_offset_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
|
|
+{
|
|
+ struct upisnd_config *cfg = to_config(kobj);
|
|
+
|
|
+ s32 adc_offset;
|
|
+ int err = upisnd_comm_get_value(cfg->instance, UPISND_VALUE_ADC_OFFSET, &adc_offset);
|
|
+
|
|
+ if (err < 0) {
|
|
+ printe("Failed getting ADC offset value! (%d)", err);
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ return sprintf(buf, "%d\n", adc_offset);
|
|
+}
|
|
+
|
|
+static ssize_t upisnd_adc_offset_store(struct kobject *kobj, struct kobj_attribute *attr,
|
|
+ const char *buf, size_t length)
|
|
+{
|
|
+ struct upisnd_config *cfg = to_config(kobj);
|
|
+
|
|
+ int adc_offset;
|
|
+ int err = kstrtoint(buf, 10, &adc_offset);
|
|
+
|
|
+ if (err < 0) {
|
|
+ printe("Failed parsing ADC offset! (%d)", err);
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ err = upisnd_comm_set_value(cfg->instance, UPISND_VALUE_ADC_OFFSET, adc_offset);
|
|
+ if (err < 0) {
|
|
+ printe("Failed setting ADC offset value! (%d)", err);
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ return length;
|
|
+}
|
|
+
|
|
+static struct kobj_attribute upisnd_setup_attr = __ATTR(setup, 0220, NULL, upisnd_setup);
|
|
+static struct kobj_attribute upisnd_unsetup_attr = __ATTR(unsetup, 0220, NULL, upisnd_unsetup);
|
|
+static struct kobj_attribute upisnd_adc_gain_attr = __ATTR(adc_gain, 0664, upisnd_adc_gain_show,
|
|
+ upisnd_adc_gain_store);
|
|
+static struct kobj_attribute upisnd_adc_offset_attr = __ATTR(adc_offset, 0664,
|
|
+ upisnd_adc_offset_show,
|
|
+ upisnd_adc_offset_store);
|
|
+
|
|
+static struct attribute *upisnd_root_attrs[] = {
|
|
+ &upisnd_setup_attr.attr,
|
|
+ &upisnd_unsetup_attr.attr,
|
|
+ &upisnd_adc_gain_attr.attr,
|
|
+ &upisnd_adc_offset_attr.attr,
|
|
+ NULL
|
|
+};
|
|
+
|
|
+static umode_t upisnd_root_is_visible(struct kobject *kobj, struct attribute *attr, int idx)
|
|
+{
|
|
+ struct upisnd_config *cfg = to_config(kobj);
|
|
+ struct upisnd_instance *instance = cfg->instance;
|
|
+
|
|
+ if (!(instance->flags & UPISND_FLAG_ADC_CALIBRATION)) {
|
|
+ if (attr == &upisnd_adc_gain_attr.attr || attr == &upisnd_adc_offset_attr.attr)
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ return attr->mode;
|
|
+}
|
|
+
|
|
+static const struct attribute_group upisnd_root_group = {
|
|
+ .attrs = upisnd_root_attrs,
|
|
+ .is_visible = &upisnd_root_is_visible
|
|
+};
|
|
+
|
|
+static struct upisnd_config *upisnd_create_config(struct upisnd_instance *instance,
|
|
+ const char *name)
|
|
+{
|
|
+ struct upisnd_config *cfg = kzalloc(sizeof(*cfg), GFP_KERNEL);
|
|
+
|
|
+ if (!cfg) {
|
|
+ printe("Failed allocating upisnd_config!");
|
|
+ return NULL;
|
|
+ }
|
|
+
|
|
+ printd("cfg=%p", cfg);
|
|
+
|
|
+ cfg->instance = instance;
|
|
+
|
|
+ int err = kobject_set_name(&cfg->kset.kobj, name);
|
|
+
|
|
+ if (err < 0) {
|
|
+ printe("Failed setting config kset name! (%d)", err);
|
|
+ goto cleanup;
|
|
+ }
|
|
+
|
|
+ cfg->kset.kobj.ktype = &upisnd_config_type;
|
|
+ err = kset_register(&cfg->kset);
|
|
+
|
|
+ if (err < 0) {
|
|
+ printe("Failed registering config kset! (%d)", err);
|
|
+ goto cleanup;
|
|
+ }
|
|
+
|
|
+ cfg->kset.kobj.kset = &cfg->kset;
|
|
+
|
|
+ cfg->elements = kset_create_and_add("elements", NULL, &cfg->kset.kobj);
|
|
+ if (!cfg->elements) {
|
|
+ printe("Failed creating elements kset!");
|
|
+ goto cleanup;
|
|
+ }
|
|
+
|
|
+ err = sysfs_create_group(&cfg->kset.kobj, &upisnd_root_group);
|
|
+ if (err < 0) {
|
|
+ printe("Failed creating pisound-micro attributes! (%d)", err);
|
|
+ goto cleanup;
|
|
+ }
|
|
+
|
|
+ err = kobject_uevent(&cfg->kset.kobj, KOBJ_ADD);
|
|
+ printd("kobject_uevent(&cfg->kobj, KOBJ_ADD) = %d", err);
|
|
+
|
|
+ return cfg;
|
|
+
|
|
+cleanup:
|
|
+ if (cfg->elements) {
|
|
+ kset_unregister(cfg->elements);
|
|
+ cfg->elements = NULL;
|
|
+ }
|
|
+
|
|
+ kset_unregister(&cfg->kset);
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+static ssize_t upisnd_element_gpio_export(struct kobject *kobj,
|
|
+ struct kobj_attribute *attr,
|
|
+ const char *buf,
|
|
+ size_t len)
|
|
+{
|
|
+ if (!kobj || !kobj->parent || !kobj->parent->parent)
|
|
+ return -EINVAL;
|
|
+
|
|
+ struct upisnd_element *element = to_element(kobj);
|
|
+ struct upisnd_config *cfg = to_config(kobj->parent->parent);
|
|
+ struct upisnd_instance *instance = cfg->instance;
|
|
+
|
|
+ struct gpio_desc *desc = NULL;
|
|
+
|
|
+ int pin = element->gpio_pins[0];
|
|
+
|
|
+ if (pin == UPISND_PIN_INVALID) {
|
|
+ printe("Element %s is not set up yet!", kobj->name);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ down_write(&instance->rw_gpio_config_sem);
|
|
+ if (upisnd_setup_get_element_type(instance->gpio.pin_configs[pin].setup) !=
|
|
+ UPISND_ELEMENT_TYPE_GPIO) {
|
|
+ printe("Element %s is not set up as a GPIO!", kobj->name);
|
|
+ up_write(&instance->rw_gpio_config_sem);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ instance->gpio.pin_configs[pin].flags |= UPISND_PIN_FLAG_CONFIGURING_THROUGH_SYSFS;
|
|
+ up_write(&instance->rw_gpio_config_sem);
|
|
+
|
|
+ int err = 0;
|
|
+
|
|
+ printd("Requesting own %d", pin);
|
|
+ desc = gpiochip_request_own_desc(&instance->gpio.gpio_chip, pin, kobj->name, 0, 0);
|
|
+ if (IS_ERR(desc)) {
|
|
+ printe("Failed requesting own GPIO desc for pin %d! (%ld)",
|
|
+ upisnd_setup_get_pin_id(instance->gpio.pin_configs[pin].setup),
|
|
+ PTR_ERR(desc));
|
|
+ err = PTR_ERR(desc);
|
|
+ desc = NULL;
|
|
+ }
|
|
+
|
|
+ if (err >= 0) {
|
|
+ err = gpiod_export(desc, true);
|
|
+
|
|
+ if (err < 0) {
|
|
+ printe("Failed exporting GPIO via sysfs for pin %d! (%d)",
|
|
+ upisnd_setup_get_pin_id(instance->gpio.pin_configs[pin].setup), err);
|
|
+ gpiochip_free_own_desc(desc);
|
|
+ desc = NULL;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ printd("Result: %p %d", desc, err);
|
|
+
|
|
+ down_write(&instance->rw_gpio_config_sem);
|
|
+ instance->gpio.pin_configs[pin].flags = 0;
|
|
+ instance->gpio.pin_configs[pin].gpio_desc = desc;
|
|
+ up_write(&instance->rw_gpio_config_sem);
|
|
+
|
|
+ return err >= 0 ? len : err;
|
|
+}
|
|
+
|
|
+static ssize_t upisnd_element_gpio_unexport(struct kobject *kobj, struct kobj_attribute *attr,
|
|
+ const char *buf, size_t len)
|
|
+{
|
|
+ if (!kobj || !kobj->parent || !kobj->parent->parent)
|
|
+ return -EINVAL;
|
|
+
|
|
+ struct upisnd_element *element = to_element(kobj);
|
|
+ struct upisnd_config *cfg = to_config(kobj->parent->parent);
|
|
+ struct upisnd_instance *instance = cfg->instance;
|
|
+
|
|
+ int pin = element->gpio_pins[0];
|
|
+
|
|
+ if (pin == UPISND_PIN_INVALID) {
|
|
+ printe("Element %s is not set up yet!", kobj->name);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ down_write(&instance->rw_gpio_config_sem);
|
|
+ struct gpio_desc *desc = instance->gpio.pin_configs[pin].gpio_desc;
|
|
+
|
|
+ if (!desc) {
|
|
+ up_write(&instance->rw_gpio_config_sem);
|
|
+ printe("Element %s is not exported via gpio sysfs!", kobj->name);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ instance->gpio.pin_configs[pin].flags |= UPISND_PIN_FLAG_CONFIGURING_THROUGH_SYSFS;
|
|
+ up_write(&instance->rw_gpio_config_sem);
|
|
+
|
|
+ printd("Freeing own %d", pin);
|
|
+ gpiochip_free_own_desc(desc);
|
|
+
|
|
+ down_write(&instance->rw_gpio_config_sem);
|
|
+ instance->gpio.pin_configs[pin].flags &= ~UPISND_PIN_FLAG_CONFIGURING_THROUGH_SYSFS;
|
|
+ instance->gpio.pin_configs[pin].gpio_desc = NULL;
|
|
+ up_write(&instance->rw_gpio_config_sem);
|
|
+
|
|
+ return len;
|
|
+}
|
|
+
|
|
+static int upisnd_element_cleanup(struct kobject *kobj, struct upisnd_instance *instance)
|
|
+{
|
|
+ printd("(%p)", kobj);
|
|
+
|
|
+ struct upisnd_element *element = to_element(kobj);
|
|
+
|
|
+ if (element->gpio_pins[0] == UPISND_PIN_INVALID)
|
|
+ return 0;
|
|
+
|
|
+ upisnd_setup_t setup = instance->gpio.pin_configs[element->gpio_pins[0]].setup;
|
|
+
|
|
+ if (upisnd_setup_get_element_type(setup) == UPISND_ELEMENT_TYPE_NONE) {
|
|
+ printe("Pin %d is assigned, but the '%s' element is not set up",
|
|
+ element->gpio_pins[0], kobj->name);
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ if (instance->gpio.pin_configs[element->gpio_pins[0]].gpio_desc) {
|
|
+ printd("Freeing own desc");
|
|
+ struct gpio_desc *desc = instance->gpio.pin_configs[element->gpio_pins[0]]
|
|
+ .gpio_desc;
|
|
+
|
|
+ instance->gpio.pin_configs[element->gpio_pins[0]].flags |=
|
|
+ UPISND_PIN_FLAG_CONFIGURING_THROUGH_SYSFS;
|
|
+ up_write(&instance->rw_gpio_config_sem);
|
|
+ gpiochip_free_own_desc(desc);
|
|
+ down_write(&instance->rw_gpio_config_sem);
|
|
+ instance->gpio.pin_configs[element->gpio_pins[0]].flags &=
|
|
+ ~UPISND_PIN_FLAG_CONFIGURING_THROUGH_SYSFS;
|
|
+ printd("Freed");
|
|
+ }
|
|
+
|
|
+ upisnd_setup_set_element_type(&setup, UPISND_ELEMENT_TYPE_NONE);
|
|
+ int err = upisnd_gpio_setup(instance, setup);
|
|
+
|
|
+ int i;
|
|
+
|
|
+ for (i = 0; i < ARRAY_SIZE(element->gpio_pins); ++i) {
|
|
+ if (element->gpio_pins[i] == UPISND_PIN_INVALID)
|
|
+ break;
|
|
+
|
|
+ memset(&instance->gpio.pin_configs[element->gpio_pins[i]], 0,
|
|
+ sizeof(struct upisnd_pin_config_t));
|
|
+ instance->gpio.pin_subscription_refcounts[element->gpio_pins[i]] = 0;
|
|
+
|
|
+ element->gpio_pins[i] = UPISND_PIN_INVALID;
|
|
+ }
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static ssize_t upisnd_element_type_show(struct kobject *kobj, struct kobj_attribute *attr,
|
|
+ char *buf)
|
|
+{
|
|
+ const char *type = NULL;
|
|
+ struct upisnd_element *element = to_element(kobj);
|
|
+ struct upisnd_config *cfg = to_config(kobj->parent->parent);
|
|
+ struct upisnd_instance *instance = cfg->instance;
|
|
+
|
|
+ enum upisnd_element_type_e t = UPISND_ELEMENT_TYPE_NONE;
|
|
+
|
|
+ if (element->gpio_pins[0] != UPISND_PIN_INVALID) {
|
|
+ t = upisnd_setup_get_element_type(instance->gpio.pin_configs[element->gpio_pins[0]]
|
|
+ .setup);
|
|
+ }
|
|
+
|
|
+ switch (t) {
|
|
+ case UPISND_ELEMENT_TYPE_ENCODER:
|
|
+ type = "encoder";
|
|
+ break;
|
|
+ case UPISND_ELEMENT_TYPE_ANALOG_IN:
|
|
+ type = "analog_in";
|
|
+ break;
|
|
+ case UPISND_ELEMENT_TYPE_GPIO:
|
|
+ type = "gpio";
|
|
+ break;
|
|
+ case UPISND_ELEMENT_TYPE_ACTIVITY:
|
|
+ type = "activity";
|
|
+ break;
|
|
+ case UPISND_ELEMENT_TYPE_NONE:
|
|
+ type = "none";
|
|
+ break;
|
|
+ default:
|
|
+ type = "unknown";
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ return sprintf(buf, "%s\n", type);
|
|
+}
|
|
+
|
|
+static ssize_t upisnd_element_value_show(struct kobject *kobj,
|
|
+ struct kobj_attribute *attr,
|
|
+ char *buf)
|
|
+{
|
|
+ struct upisnd_element *element = to_element(kobj);
|
|
+ struct upisnd_config *cfg = to_config(kobj->parent->parent);
|
|
+ struct upisnd_instance *instance = cfg->instance;
|
|
+
|
|
+ enum upisnd_element_type_e t = UPISND_ELEMENT_TYPE_NONE;
|
|
+ int pin = UPISND_PIN_INVALID;
|
|
+
|
|
+ if (element->gpio_pins[0] != UPISND_PIN_INVALID) {
|
|
+ t = upisnd_setup_get_element_type(instance->gpio.pin_configs[element->gpio_pins[0]]
|
|
+ .setup);
|
|
+ pin = element->gpio_pins[0];
|
|
+ }
|
|
+
|
|
+ switch (t) {
|
|
+ case UPISND_ELEMENT_TYPE_ANALOG_IN:
|
|
+ case UPISND_ELEMENT_TYPE_ENCODER:
|
|
+ return sprintf(buf, "%d\n", element->value);
|
|
+ case UPISND_ELEMENT_TYPE_GPIO:
|
|
+ return sprintf(buf, "%d\n", upisnd_gpio_get(instance, pin));
|
|
+ case UPISND_ELEMENT_TYPE_ACTIVITY:
|
|
+ case UPISND_ELEMENT_TYPE_NONE:
|
|
+ default:
|
|
+ return 0;
|
|
+ }
|
|
+}
|
|
+
|
|
+static ssize_t upisnd_element_value_store(struct kobject *kobj, struct kobj_attribute *attr,
|
|
+ const char *buf, size_t len)
|
|
+{
|
|
+ if (!kobj || !kobj->parent || !kobj->parent->parent)
|
|
+ return -EINVAL;
|
|
+
|
|
+ struct upisnd_element *element = to_element(kobj);
|
|
+ struct upisnd_config *cfg = to_config(kobj->parent->parent);
|
|
+ struct upisnd_instance *instance = cfg->instance;
|
|
+
|
|
+ printd("%p %p", kobj, cfg);
|
|
+
|
|
+ enum upisnd_element_type_e t = UPISND_ELEMENT_TYPE_NONE;
|
|
+ int pin = UPISND_PIN_INVALID;
|
|
+
|
|
+ if (element->gpio_pins[0] != UPISND_PIN_INVALID) {
|
|
+ t = upisnd_setup_get_element_type(instance->gpio.pin_configs[element->gpio_pins[0]]
|
|
+ .setup);
|
|
+ pin = element->gpio_pins[0];
|
|
+ }
|
|
+
|
|
+ int value, err;
|
|
+ bool b;
|
|
+
|
|
+ bool changed = false;
|
|
+
|
|
+ switch (t) {
|
|
+ case UPISND_ELEMENT_TYPE_NONE:
|
|
+ return -EINVAL;
|
|
+ case UPISND_ELEMENT_TYPE_GPIO:
|
|
+ err = kstrtobool(buf, &b);
|
|
+ if (err < 0) {
|
|
+ printe("Failed parsing the provided boolean! (%d)", err);
|
|
+ return err;
|
|
+ }
|
|
+ changed = b != upisnd_gpio_get(instance, pin);
|
|
+ upisnd_gpio_set(instance, pin, b);
|
|
+ break;
|
|
+ case UPISND_ELEMENT_TYPE_ENCODER:
|
|
+ case UPISND_ELEMENT_TYPE_ANALOG_IN:
|
|
+ err = kstrtoint(buf, 0, &value);
|
|
+ if (err < 0) {
|
|
+ printe("Failed parsing the provided integer! (%d)", err);
|
|
+ return err;
|
|
+ }
|
|
+ down_write(&instance->rw_gpio_config_sem);
|
|
+ element->raw_value = upisnd_unmap_value_range(value, element->input_min,
|
|
+ element->input_max,
|
|
+ element->value_low,
|
|
+ element->value_high);
|
|
+ changed = upisnd_element_update_value(element);
|
|
+ up_write(&instance->rw_gpio_config_sem);
|
|
+ break;
|
|
+ case UPISND_ELEMENT_TYPE_ACTIVITY:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ if (changed)
|
|
+ sysfs_notify(kobj, NULL, "value");
|
|
+
|
|
+ return len;
|
|
+}
|
|
+
|
|
+static ssize_t upisnd_element_input_min_show(struct kobject *kobj, struct kobj_attribute *attr,
|
|
+ char *buf)
|
|
+{
|
|
+ struct upisnd_element *element = to_element(kobj);
|
|
+
|
|
+ return sprintf(buf, "%d\n", element->input_min);
|
|
+}
|
|
+
|
|
+static ssize_t upisnd_element_store_value_int(struct upisnd_element *element, int *value,
|
|
+ const char *buf, size_t len)
|
|
+{
|
|
+ int v;
|
|
+ int err = kstrtoint(buf, 0, &v);
|
|
+
|
|
+ if (err < 0) {
|
|
+ printe("Failed parsing the provided integer! (%d)", err);
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ struct upisnd_config *cfg = to_config(element->kobj.parent->parent);
|
|
+ struct upisnd_instance *instance = cfg->instance;
|
|
+
|
|
+ down_write(&instance->rw_gpio_config_sem);
|
|
+ *value = v;
|
|
+ bool changed = upisnd_element_update_value(element);
|
|
+
|
|
+ up_write(&instance->rw_gpio_config_sem);
|
|
+
|
|
+ if (changed)
|
|
+ sysfs_notify(&element->kobj, NULL, "value");
|
|
+
|
|
+ return len;
|
|
+}
|
|
+
|
|
+static ssize_t upisnd_element_input_min_store(struct kobject *kobj, struct kobj_attribute *attr,
|
|
+ const char *buf, size_t len)
|
|
+{
|
|
+ if (!kobj || !kobj->parent || !kobj->parent->parent)
|
|
+ return -EINVAL;
|
|
+
|
|
+ struct upisnd_element *element = to_element(kobj);
|
|
+ int result = upisnd_element_store_value_int(element, &element->input_min, buf, len);
|
|
+
|
|
+ if (result >= 0) {
|
|
+ if (element->input_min > element->input_max) {
|
|
+ int t = element->input_min;
|
|
+
|
|
+ element->input_min = element->input_max;
|
|
+ element->input_max = t;
|
|
+ }
|
|
+ }
|
|
+ return result;
|
|
+}
|
|
+
|
|
+static ssize_t upisnd_element_input_max_show(struct kobject *kobj, struct kobj_attribute *attr,
|
|
+ char *buf)
|
|
+{
|
|
+ if (!kobj || !kobj->parent || !kobj->parent->parent)
|
|
+ return -EINVAL;
|
|
+
|
|
+ struct upisnd_element *element = to_element(kobj);
|
|
+
|
|
+ return sprintf(buf, "%d\n", element->input_max);
|
|
+}
|
|
+
|
|
+static ssize_t upisnd_element_input_max_store(struct kobject *kobj, struct kobj_attribute *attr,
|
|
+ const char *buf, size_t len)
|
|
+{
|
|
+ if (!kobj || !kobj->parent || !kobj->parent->parent)
|
|
+ return -EINVAL;
|
|
+
|
|
+ struct upisnd_element *element = to_element(kobj);
|
|
+ int result = upisnd_element_store_value_int(element, &element->input_max, buf, len);
|
|
+
|
|
+ if (result >= 0) {
|
|
+ if (element->input_min > element->input_max) {
|
|
+ int t = element->input_min;
|
|
+
|
|
+ element->input_min = element->input_max;
|
|
+ element->input_max = t;
|
|
+ }
|
|
+ }
|
|
+ return result;
|
|
+}
|
|
+
|
|
+static ssize_t upisnd_element_value_low_show(struct kobject *kobj, struct kobj_attribute *attr,
|
|
+ char *buf)
|
|
+{
|
|
+ if (!kobj || !kobj->parent || !kobj->parent->parent)
|
|
+ return -EINVAL;
|
|
+
|
|
+ struct upisnd_element *element = to_element(kobj);
|
|
+
|
|
+ return sprintf(buf, "%d\n", element->value_low);
|
|
+}
|
|
+
|
|
+static ssize_t upisnd_element_value_low_store(struct kobject *kobj, struct kobj_attribute *attr,
|
|
+ const char *buf, size_t len)
|
|
+{
|
|
+ if (!kobj || !kobj->parent || !kobj->parent->parent)
|
|
+ return -EINVAL;
|
|
+
|
|
+ struct upisnd_element *element = to_element(kobj);
|
|
+
|
|
+ return upisnd_element_store_value_int(element, &element->value_low, buf, len);
|
|
+}
|
|
+
|
|
+static ssize_t upisnd_element_value_high_show(struct kobject *kobj, struct kobj_attribute *attr,
|
|
+ char *buf)
|
|
+{
|
|
+ if (!kobj || !kobj->parent || !kobj->parent->parent)
|
|
+ return -EINVAL;
|
|
+
|
|
+ struct upisnd_element *element = to_element(kobj);
|
|
+
|
|
+ return sprintf(buf, "%d\n", element->value_high);
|
|
+}
|
|
+
|
|
+static ssize_t upisnd_element_value_high_store(struct kobject *kobj, struct kobj_attribute *attr,
|
|
+ const char *buf, size_t len)
|
|
+{
|
|
+ if (!kobj || !kobj->parent || !kobj->parent->parent)
|
|
+ return -EINVAL;
|
|
+
|
|
+ struct upisnd_element *element = to_element(kobj);
|
|
+
|
|
+ return upisnd_element_store_value_int(element, &element->value_high, buf, len);
|
|
+}
|
|
+
|
|
+static ssize_t upisnd_element_value_mode_show(struct kobject *kobj, struct kobj_attribute *attr,
|
|
+ char *buf)
|
|
+{
|
|
+ if (!kobj || !kobj->parent || !kobj->parent->parent)
|
|
+ return -EINVAL;
|
|
+
|
|
+ struct upisnd_element *element = to_element(kobj);
|
|
+
|
|
+ const char *s;
|
|
+
|
|
+ switch (element->value_mode) {
|
|
+ case UPISND_VALUE_MODE_CLAMP:
|
|
+ s = "clamp";
|
|
+ break;
|
|
+ case UPISND_VALUE_MODE_WRAP:
|
|
+ s = "wrap";
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ return sprintf(buf, "%s\n", s);
|
|
+}
|
|
+
|
|
+static ssize_t upisnd_element_value_mode_store(struct kobject *kobj, struct kobj_attribute *attr,
|
|
+ const char *buf, size_t len)
|
|
+{
|
|
+ if (!kobj || !kobj->parent || !kobj->parent->parent)
|
|
+ return -EINVAL;
|
|
+
|
|
+ static const char *const SEP = "\n\t ";
|
|
+
|
|
+ char b[8];
|
|
+
|
|
+ strscpy(b, buf, min(len + 1, sizeof(b)));
|
|
+
|
|
+ char *s = b;
|
|
+ char *token = strsep(&s, SEP);
|
|
+
|
|
+ if (!token || *token == '\0')
|
|
+ return -EINVAL;
|
|
+
|
|
+ enum upisnd_value_mode mode;
|
|
+
|
|
+ if (strncasecmp(token, "clamp", 6u) == 0)
|
|
+ mode = UPISND_VALUE_MODE_CLAMP;
|
|
+ else if (strncasecmp(token, "wrap", 5u) == 0)
|
|
+ mode = UPISND_VALUE_MODE_WRAP;
|
|
+ else
|
|
+ return -EINVAL;
|
|
+
|
|
+ struct upisnd_element *element = to_element(kobj);
|
|
+ struct upisnd_config *cfg = to_config(element->kobj.parent->parent);
|
|
+ struct upisnd_instance *instance = cfg->instance;
|
|
+
|
|
+ down_write(&instance->rw_gpio_config_sem);
|
|
+ element->value_mode = mode;
|
|
+ bool changed = upisnd_element_update_value(element);
|
|
+
|
|
+ up_write(&instance->rw_gpio_config_sem);
|
|
+
|
|
+ if (changed)
|
|
+ sysfs_notify(&element->kobj, NULL, "value");
|
|
+
|
|
+ return len;
|
|
+}
|
|
+
|
|
+static ssize_t upisnd_element_direction_show(struct kobject *kobj, struct kobj_attribute *attr,
|
|
+ char *buf)
|
|
+{
|
|
+ struct upisnd_element *element = to_element(kobj);
|
|
+ struct upisnd_config *cfg = to_config(kobj->parent->parent);
|
|
+ struct upisnd_instance *instance = cfg->instance;
|
|
+
|
|
+ enum upisnd_element_type_e t = UPISND_ELEMENT_TYPE_NONE;
|
|
+ int pin = UPISND_PIN_INVALID;
|
|
+
|
|
+ if (element->gpio_pins[0] != UPISND_PIN_INVALID) {
|
|
+ t = upisnd_setup_get_element_type(instance->gpio.pin_configs[element->gpio_pins[0]]
|
|
+ .setup);
|
|
+ pin = element->gpio_pins[0];
|
|
+ }
|
|
+
|
|
+ switch (t) {
|
|
+ case UPISND_ELEMENT_TYPE_ENCODER:
|
|
+ return sprintf(buf, "in\n");
|
|
+ case UPISND_ELEMENT_TYPE_GPIO:
|
|
+ return sprintf(buf, upisnd_gpio_get_direction(instance, pin) == 0 ?
|
|
+ "out\n" : "in\n");
|
|
+ case UPISND_ELEMENT_TYPE_ACTIVITY:
|
|
+ return sprintf(buf, "out\n");
|
|
+ case UPISND_ELEMENT_TYPE_NONE:
|
|
+ default:
|
|
+ return 0;
|
|
+ }
|
|
+}
|
|
+
|
|
+static ssize_t upisnd_element_pin_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
|
|
+{
|
|
+ struct upisnd_element *element = to_element(kobj);
|
|
+
|
|
+ if (element->gpio_pins[0] != UPISND_PIN_INVALID)
|
|
+ return sprintf(buf, "%d\n", element->gpio_pins[0]);
|
|
+
|
|
+ return -EINVAL;
|
|
+}
|
|
+
|
|
+static ssize_t upisnd_element_pin_name_show(struct kobject *kobj, struct kobj_attribute *attr,
|
|
+ char *buf)
|
|
+{
|
|
+ struct upisnd_element *element = to_element(kobj);
|
|
+
|
|
+ if (element->gpio_pins[0] != UPISND_PIN_INVALID)
|
|
+ return sprintf(buf, "%s\n", upisnd_pin_name(element->gpio_pins[0]));
|
|
+
|
|
+ return -EINVAL;
|
|
+}
|
|
+
|
|
+static ssize_t upisnd_element_pin_b_show(struct kobject *kobj, struct kobj_attribute *attr,
|
|
+ char *buf)
|
|
+{
|
|
+ struct upisnd_element *element = to_element(kobj);
|
|
+
|
|
+ if (element->gpio_pins[1] != UPISND_PIN_INVALID)
|
|
+ return sprintf(buf, "%d\n", element->gpio_pins[1]);
|
|
+
|
|
+ return -EINVAL;
|
|
+}
|
|
+
|
|
+static ssize_t upisnd_element_pin_b_name_show(struct kobject *kobj, struct kobj_attribute *attr,
|
|
+ char *buf)
|
|
+{
|
|
+ struct upisnd_element *element = to_element(kobj);
|
|
+
|
|
+ if (element->gpio_pins[1] != UPISND_PIN_INVALID)
|
|
+ return sprintf(buf, "%s\n", upisnd_pin_name(element->gpio_pins[1]));
|
|
+
|
|
+ return -EINVAL;
|
|
+}
|
|
+
|
|
+static ssize_t upisnd_element_activity_type_show(struct kobject *kobj, struct kobj_attribute *attr,
|
|
+ char *buf)
|
|
+{
|
|
+ struct upisnd_element *element = to_element(kobj);
|
|
+
|
|
+ struct upisnd_config *cfg = to_config(kobj->parent->parent);
|
|
+ struct upisnd_instance *instance = cfg->instance;
|
|
+
|
|
+ enum upisnd_element_type_e t = UPISND_ELEMENT_TYPE_NONE;
|
|
+
|
|
+ if (element->gpio_pins[0] != UPISND_PIN_INVALID)
|
|
+ t = upisnd_setup_get_element_type(instance->gpio.pin_configs[element->gpio_pins[0]]
|
|
+ .setup);
|
|
+
|
|
+ if (t != UPISND_ELEMENT_TYPE_ACTIVITY)
|
|
+ return -EINVAL;
|
|
+
|
|
+ switch (upisnd_setup_get_activity_type(instance->gpio.pin_configs[element->gpio_pins[0]]
|
|
+ .setup)) {
|
|
+ case UPISND_ACTIVITY_TYPE_MIDI_IN:
|
|
+ return sprintf(buf, "midi_in\n");
|
|
+ case UPISND_ACTIVITY_TYPE_MIDI_OUT:
|
|
+ return sprintf(buf, "midi_out\n");
|
|
+ default:
|
|
+ return 0;
|
|
+ }
|
|
+}
|
|
+
|
|
+static void upisnd_sysfs_ctrl_event_handler(struct work_struct *work);
|
|
+
|
|
+int upisnd_sysfs_init(struct upisnd_instance *instance, const char *name)
|
|
+{
|
|
+ instance->config = upisnd_create_config(instance, name ? name : "pisound-micro");
|
|
+ if (!instance->config)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ INIT_KFIFO(instance->ctrl_event_fifo);
|
|
+ INIT_WORK(&instance->ctrl_event_handler, upisnd_sysfs_ctrl_event_handler);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+void upisnd_sysfs_uninit(struct upisnd_instance *instance)
|
|
+{
|
|
+ unsigned int i;
|
|
+
|
|
+ for (i = 0; i < ARRAY_SIZE(instance->gpio.pin_configs); ++i) {
|
|
+ if (instance->gpio.pin_configs[i].gpio_desc) {
|
|
+ gpiochip_free_own_desc(instance->gpio.pin_configs[i].gpio_desc);
|
|
+ instance->gpio.pin_configs[i].gpio_desc = NULL;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ kfifo_free(&instance->ctrl_event_fifo);
|
|
+
|
|
+ if (instance->config) {
|
|
+ struct kobject *k, *t;
|
|
+
|
|
+ if (instance->config->elements) {
|
|
+ list_for_each_entry_safe(k, t, &instance->config->elements->list, entry) {
|
|
+ printd("Putting element %p", k);
|
|
+ kobject_put(k);
|
|
+ }
|
|
+
|
|
+ printd("Unregistering elements set");
|
|
+ kset_unregister(instance->config->elements);
|
|
+ instance->config->elements = NULL;
|
|
+ }
|
|
+
|
|
+ instance->config->kset.kobj.kset = NULL;
|
|
+ kset_unregister(&instance->config->kset);
|
|
+ instance->config = NULL;
|
|
+ }
|
|
+}
|
|
+
|
|
+static void upisnd_sysfs_ctrl_event_handler(struct work_struct *work)
|
|
+{
|
|
+ printd("ctrl event handler");
|
|
+ struct upisnd_instance *instance = container_of(work, struct upisnd_instance,
|
|
+ ctrl_event_handler);
|
|
+ struct kobject *objs[UPISND_NUM_GPIOS];
|
|
+
|
|
+ memset(objs, 0, sizeof(objs));
|
|
+
|
|
+ unsigned int i, j = 0;
|
|
+ int n;
|
|
+ struct control_event_t events[32];
|
|
+
|
|
+ down_write(&instance->rw_gpio_config_sem);
|
|
+ while ((n = kfifo_out(&instance->ctrl_event_fifo, events, 32)) > 0) {
|
|
+ printd("Got %d events", n);
|
|
+ for (i = 0; i < n; ++i) {
|
|
+ upisnd_pin_t pin = events[i].pin;
|
|
+
|
|
+ if (!upisnd_is_pin_valid(pin)) {
|
|
+ printe("Received invalid pin (%d), ignoring", pin);
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ struct upisnd_element *element = instance->gpio.pin_configs[pin].element;
|
|
+
|
|
+ if (element) {
|
|
+ switch (upisnd_setup_get_element_type(instance->gpio.pin_configs
|
|
+ [pin].setup)) {
|
|
+ case UPISND_ELEMENT_TYPE_ENCODER:
|
|
+ element->raw_value += (int16_t)events[i].raw_value;
|
|
+ printd("Encoder %s, old raw: %d, new raw: %d",
|
|
+ element->kobj.name,
|
|
+ element->raw_value - (int16_t)events[i].raw_value,
|
|
+ element->raw_value);
|
|
+ break;
|
|
+ case UPISND_ELEMENT_TYPE_ANALOG_IN:
|
|
+ element->raw_value = (uint16_t)(events[i].raw_value
|
|
+ & 0x03ff);
|
|
+ printd("Analog in %s, raw: %d", element->kobj.name,
|
|
+ element->raw_value);
|
|
+ break;
|
|
+ case UPISND_ELEMENT_TYPE_GPIO:
|
|
+ if (upisnd_setup_get_gpio_dir(instance->gpio
|
|
+ .pin_configs[pin].setup) == UPISND_PIN_DIR_INPUT) {
|
|
+ element->raw_value = (uint16_t)events[i].raw_value;
|
|
+ printd("Button %s, raw: %d", element->kobj.name,
|
|
+ element->raw_value);
|
|
+ break;
|
|
+ }
|
|
+ break;
|
|
+ default:
|
|
+ printe
|
|
+ ("Got control event for non gpio, enc or analog element %s!"
|
|
+ , element->kobj.name);
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ objs[pin] = &element->kobj;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ for (i = 0; i < ARRAY_SIZE(objs); ++i) {
|
|
+ if (objs[i] && upisnd_element_update_value(to_element(objs[i])))
|
|
+ objs[j++] = kobject_get(objs[i]);
|
|
+ }
|
|
+ up_write(&instance->rw_gpio_config_sem);
|
|
+
|
|
+ for (i = 0; i < j; ++i) {
|
|
+ printd("Notify %d %p", i, objs[i]);
|
|
+ sysfs_notify(objs[i], NULL, "value");
|
|
+ kobject_put(objs[i]);
|
|
+ }
|
|
+
|
|
+ if (!kfifo_is_empty(&instance->ctrl_event_fifo) &&
|
|
+ !work_pending(&instance->ctrl_event_handler))
|
|
+ queue_work(instance->work_queue, &instance->ctrl_event_handler);
|
|
+}
|
|
+
|
|
+void upisnd_sysfs_handle_irq_event(struct upisnd_instance *instance,
|
|
+ const struct irq_event_t *events, unsigned int n)
|
|
+{
|
|
+ printd("Converting and pushing %u events", n);
|
|
+ struct control_event_t cev;
|
|
+ unsigned int i;
|
|
+
|
|
+ for (i = 0; i < n; ++i) {
|
|
+ cev.pin = events[i].num;
|
|
+ cev.raw_value = events[i].high ? 1 : 0;
|
|
+ kfifo_put(&instance->ctrl_event_fifo, cev);
|
|
+ }
|
|
+ if (!work_pending(&instance->ctrl_event_handler))
|
|
+ queue_work(instance->work_queue, &instance->ctrl_event_handler);
|
|
+}
|
|
+
|
|
+void upisnd_sysfs_handle_control_event(struct upisnd_instance *instance,
|
|
+ const struct control_event_t *events, unsigned int n)
|
|
+{
|
|
+ printd("Pushing %u events", n);
|
|
+ kfifo_in(&instance->ctrl_event_fifo, events, n);
|
|
+ if (!work_pending(&instance->ctrl_event_handler))
|
|
+ queue_work(instance->work_queue, &instance->ctrl_event_handler);
|
|
+}
|
|
+
|
|
+/* vim: set ts=8 sw=8 noexpandtab: */
|
|
--- /dev/null
|
|
+++ b/sound/drivers/upisnd/upisnd_sysfs.h
|
|
@@ -0,0 +1,32 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0-only */
|
|
+/*
|
|
+ * Pisound Micro Linux kernel module.
|
|
+ * Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/
|
|
+ *
|
|
+ * 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.
|
|
+ */
|
|
+
|
|
+#ifndef UPISOUND_SYSFS_H
|
|
+#define UPISOUND_SYSFS_H
|
|
+
|
|
+int upisnd_sysfs_init(struct upisnd_instance *instance, const char *name);
|
|
+void upisnd_sysfs_uninit(struct upisnd_instance *instance);
|
|
+
|
|
+void upisnd_sysfs_handle_irq_event(struct upisnd_instance *instance,
|
|
+ const struct irq_event_t *events,
|
|
+ unsigned int n);
|
|
+void upisnd_sysfs_handle_control_event(struct upisnd_instance *instance,
|
|
+ const struct control_event_t *events,
|
|
+ unsigned int n);
|
|
+
|
|
+#endif // UPISOUND_SYSFS_H
|
|
+
|
|
+/* vim: set ts=8 sw=8 noexpandtab: */
|
|
--- /dev/null
|
|
+++ b/sound/drivers/upisnd/upisnd_utils.c
|
|
@@ -0,0 +1,33 @@
|
|
+// SPDX-License-Identifier: GPL-2.0-only
|
|
+/*
|
|
+ * Pisound Micro Linux kernel module.
|
|
+ * Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/
|
|
+ *
|
|
+ * 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.
|
|
+ */
|
|
+
|
|
+#include "upisnd_common.h"
|
|
+
|
|
+int upisnd_map_value_range(int v, int input_min, int input_max, int output_min, int output_max)
|
|
+{
|
|
+ int x = (v - input_min) * (output_max - output_min);
|
|
+
|
|
+ return x / (input_max - input_min) + output_min;
|
|
+}
|
|
+
|
|
+int upisnd_unmap_value_range(int v, int input_min, int input_max, int output_min, int output_max)
|
|
+{
|
|
+ int x = (v - output_min) * (input_max - input_min);
|
|
+
|
|
+ return x / (output_max - output_min) + input_min;
|
|
+}
|
|
+
|
|
+/* vim: set ts=8 sw=8 noexpandtab: */
|
|
--- /dev/null
|
|
+++ b/sound/drivers/upisnd/upisnd_utils.h
|
|
@@ -0,0 +1,25 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0-only */
|
|
+/*
|
|
+ * Pisound Micro Linux kernel module.
|
|
+ * Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/
|
|
+ *
|
|
+ * 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.
|
|
+ */
|
|
+
|
|
+#ifndef UPISOUND_UTILS_H
|
|
+#define UPISOUND_UTILS_H
|
|
+
|
|
+int upisnd_map_value_range(int v, int input_min, int input_max, int output_min, int output_max);
|
|
+int upisnd_unmap_value_range(int v, int input_min, int input_max, int output_min, int output_max);
|
|
+
|
|
+#endif // UPISOUND_UTILS_H
|
|
+
|
|
+/* vim: set ts=8 sw=8 noexpandtab: */
|