From 7d8efd6f3198fd3dafe5a74003616a2aea5dc6c5 Mon Sep 17 00:00:00 2001 From: gtrainavicius Date: Sun, 23 Oct 2016 12:06:53 +0300 Subject: [PATCH] Support for Blokas Labs pisound board Pisound dynamic overlay (#1760) Restructuring pisound-overlay.dts, so it can be loaded and unloaded dynamically using dtoverlay. Print a logline when the kernel module is removed. --- .../devicetree/bindings/vendor-prefixes.txt | 1 + arch/arm/boot/dts/overlays/pisound-overlay.dts | 94 +- sound/soc/bcm/Kconfig | 6 + sound/soc/bcm/Makefile | 2 + sound/soc/bcm/pisound.c | 989 +++++++++++++++++++++ 5 files changed, 1048 insertions(+), 44 deletions(-) create mode 100644 sound/soc/bcm/pisound.c --- a/Documentation/devicetree/bindings/vendor-prefixes.txt +++ b/Documentation/devicetree/bindings/vendor-prefixes.txt @@ -41,6 +41,7 @@ avago Avago Technologies avia avia semiconductor avic Shanghai AVIC Optoelectronics Co., Ltd. axis Axis Communications AB +blokaslabs Vilniaus Blokas UAB boe BOE Technology Group Co., Ltd. bosch Bosch Sensortec GmbH boundary Boundary Devices Inc. --- a/arch/arm/boot/dts/overlays/pisound-overlay.dts +++ b/arch/arm/boot/dts/overlays/pisound-overlay.dts @@ -26,6 +26,54 @@ compatible = "brcm,bcm2708"; fragment@0 { + target = <&spi0>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@2 { + target = <&spidev1>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@3 { + target = <&spi0>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + + pisound_spi: pisound_spi@0{ + compatible = "blokaslabs,pisound-spi"; + reg = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&spi0_pins>; + spi-max-frequency = <1000000>; + }; + }; + }; + + fragment@4 { + target-path = "/"; + __overlay__ { + pcm5102a-codec { + #sound-dai-cells = <0>; + compatible = "ti,pcm5102a"; + status = "okay"; + }; + }; + }; + + fragment@5 { target = <&sound>; __overlay__ { compatible = "blokaslabs,pisound"; @@ -49,7 +97,7 @@ }; }; - fragment@1 { + fragment@6 { target = <&gpio>; __overlay__ { pinctrl-names = "default"; @@ -63,52 +111,10 @@ }; }; - fragment@2 { + fragment@7 { target = <&i2s>; __overlay__ { status = "okay"; }; }; - - fragment@3 { - target-path = "/"; - __overlay__ { - pcm5102a-codec { - #sound-dai-cells = <0>; - compatible = "ti,pcm5102a"; - status = "okay"; - }; - }; - }; - - fragment@4 { - target = <&spi0>; - __overlay__ { - status = "okay"; - - spidev@0{ - status = "disabled"; - }; - - spidev@1{ - status = "okay"; - }; - }; - }; - - fragment@5 { - target = <&spi0>; - __overlay__ { - #address-cells = <1>; - #size-cells = <0>; - - pisound_spi: pisound_spi@0{ - compatible = "blokaslabs,pisound-spi"; - reg = <0>; - pinctrl-names = "default"; - pinctrl-0 = <&spi0_pins>; - spi-max-frequency = <1000000>; - }; - }; - }; }; --- a/sound/soc/bcm/Kconfig +++ b/sound/soc/bcm/Kconfig @@ -130,3 +130,9 @@ config SND_BCM2708_SOC_ALLO_PIANO_DAC select SND_SOC_PCM512x_I2C help Say Y or M if you want to add support for Allo Piano DAC. + +config SND_PISOUND + tristate "Support for Blokas Labs pisound" + depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S + help + Say Y or M if you want to add support for Blokas Labs pisound. --- a/sound/soc/bcm/Makefile +++ b/sound/soc/bcm/Makefile @@ -25,6 +25,7 @@ snd-soc-audioinjector-pi-soundcard-objs snd-soc-digidac1-soundcard-objs := digidac1-soundcard.o snd-soc-dionaudio-loco-objs := dionaudio_loco.o snd-soc-allo-piano-dac-objs := allo-piano-dac.o +snd-soc-pisound-objs := pisound.o obj-$(CONFIG_SND_BCM2708_SOC_ADAU1977_ADC) += snd-soc-adau1977-adc.o obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_AMP) += snd-soc-hifiberry-amp.o @@ -42,3 +43,4 @@ obj-$(CONFIG_SND_AUDIOINJECTOR_PI_SOUNDC obj-$(CONFIG_SND_DIGIDAC1_SOUNDCARD) += snd-soc-digidac1-soundcard.o obj-$(CONFIG_SND_BCM2708_SOC_DIONAUDIO_LOCO) += snd-soc-dionaudio-loco.o obj-$(CONFIG_SND_BCM2708_SOC_ALLO_PIANO_DAC) += snd-soc-allo-piano-dac.o +obj-$(CONFIG_SND_PISOUND) += snd-soc-pisound.o --- /dev/null +++ b/sound/soc/bcm/pisound.c @@ -0,0 +1,989 @@ +/* + * pisound Linux kernel module. + * Copyright (C) 2016 Vilniaus Blokas UAB, http://blokas.io/pisound + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 of the + * License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +static int pisnd_spi_init(struct device *dev); +static void pisnd_spi_uninit(void); + +static void pisnd_spi_send(uint8_t val); +static uint8_t pisnd_spi_recv(uint8_t *buffer, uint8_t length); + +typedef void (*pisnd_spi_recv_cb)(void *data); +static void pisnd_spi_set_callback(pisnd_spi_recv_cb cb, void *data); + +static const char *pisnd_spi_get_serial(void); +static const char *pisnd_spi_get_id(void); +static const char *pisnd_spi_get_version(void); + +static int pisnd_midi_init(struct snd_card *card); +static void pisnd_midi_uninit(void); + +#define PISOUND_LOG_PREFIX "pisound: " + +#ifdef DEBUG +# define printd(...) pr_alert(PISOUND_LOG_PREFIX __VA_ARGS__) +#else +# define printd(...) do {} while (0) +#endif + +#define printe(...) pr_err(PISOUND_LOG_PREFIX __VA_ARGS__) +#define printi(...) pr_info(PISOUND_LOG_PREFIX __VA_ARGS__) + +static int pisnd_output_open(struct snd_rawmidi_substream *substream) +{ + return 0; +} + +static int pisnd_output_close(struct snd_rawmidi_substream *substream) +{ + return 0; +} + +static void pisnd_output_trigger( + struct snd_rawmidi_substream *substream, + int up + ) +{ + uint8_t data; + + if (!up) + return; + + while (snd_rawmidi_transmit_peek(substream, &data, 1)) { + pisnd_spi_send(data); + snd_rawmidi_transmit_ack(substream, 1); + } +} + +static void pisnd_output_drain(struct snd_rawmidi_substream *substream) +{ + uint8_t data; + + while (snd_rawmidi_transmit_peek(substream, &data, 1)) { + pisnd_spi_send(data); + + snd_rawmidi_transmit_ack(substream, 1); + } +} + +static int pisnd_input_open(struct snd_rawmidi_substream *substream) +{ + return 0; +} + +static int pisnd_input_close(struct snd_rawmidi_substream *substream) +{ + return 0; +} + +static void pisnd_midi_recv_callback(void *substream) +{ + uint8_t data[128]; + uint8_t n = 0; + + while ((n = pisnd_spi_recv(data, sizeof(data)))) { + int res = snd_rawmidi_receive(substream, data, n); + (void)res; + printd("midi recv 0x%02x, res = %d\n", data, res); + } +} + +static void pisnd_input_trigger(struct snd_rawmidi_substream *substream, int up) +{ + if (up) { + pisnd_spi_set_callback(pisnd_midi_recv_callback, substream); + pisnd_midi_recv_callback(substream); + } else { + pisnd_spi_set_callback(NULL, NULL); + } +} + +static struct snd_rawmidi *g_rmidi; + +static struct snd_rawmidi_ops pisnd_output_ops = { + .open = pisnd_output_open, + .close = pisnd_output_close, + .trigger = pisnd_output_trigger, + .drain = pisnd_output_drain, +}; + +static struct snd_rawmidi_ops pisnd_input_ops = { + .open = pisnd_input_open, + .close = pisnd_input_close, + .trigger = pisnd_input_trigger, +}; + +static void pisnd_get_port_info( + struct snd_rawmidi *rmidi, + int number, + struct snd_seq_port_info *seq_port_info + ) +{ + seq_port_info->type = + SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | + SNDRV_SEQ_PORT_TYPE_HARDWARE | + SNDRV_SEQ_PORT_TYPE_PORT; + seq_port_info->midi_voices = 0; +} + +static struct snd_rawmidi_global_ops pisnd_global_ops = { + .get_port_info = pisnd_get_port_info, +}; + +static int pisnd_midi_init(struct snd_card *card) +{ + int err = snd_rawmidi_new(card, "pisound MIDI", 0, 1, 1, &g_rmidi); + + if (err < 0) { + printe("snd_rawmidi_new failed: %d\n", err); + return err; + } + + strcpy(g_rmidi->name, "pisound MIDI "); + strcat(g_rmidi->name, pisnd_spi_get_serial()); + + g_rmidi->info_flags = + SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + + g_rmidi->ops = &pisnd_global_ops; + + g_rmidi->private_data = (void *)0; + + snd_rawmidi_set_ops( + g_rmidi, + SNDRV_RAWMIDI_STREAM_OUTPUT, + &pisnd_output_ops + ); + + snd_rawmidi_set_ops( + g_rmidi, + SNDRV_RAWMIDI_STREAM_INPUT, + &pisnd_input_ops + ); + + return 0; +} + +static void pisnd_midi_uninit(void) +{ +} + +static void *g_recvData; +static pisnd_spi_recv_cb g_recvCallback; + +#define FIFO_SIZE 512 + +static char g_serial_num[11]; +static char g_id[25]; +static char g_version[5]; + +DEFINE_KFIFO(spi_fifo_in, uint8_t, FIFO_SIZE); +DEFINE_KFIFO(spi_fifo_out, uint8_t, FIFO_SIZE); + +static struct gpio_desc *data_available; +static struct gpio_desc *spi_reset; + +static struct spi_device *pisnd_spi_device; + +static struct workqueue_struct *pisnd_workqueue; +static struct work_struct pisnd_work_process; + +static void pisnd_work_handler(struct work_struct *work); + +static uint16_t spi_transfer16(uint16_t val); + +static int pisnd_init_workqueues(void) +{ + pisnd_workqueue = create_singlethread_workqueue("pisnd_workqueue"); + INIT_WORK(&pisnd_work_process, pisnd_work_handler); + + return 0; +} + +static void pisnd_uninit_workqueues(void) +{ + flush_workqueue(pisnd_workqueue); + destroy_workqueue(pisnd_workqueue); + + pisnd_workqueue = NULL; +} + +static bool pisnd_spi_has_more(void) +{ + return gpiod_get_value(data_available); +} + +enum task_e { + TASK_PROCESS = 0, +}; + +static void pisnd_schedule_process(enum task_e task) +{ + if (pisnd_spi_device != NULL && + pisnd_workqueue != NULL && + !work_pending(&pisnd_work_process) + ) { + printd("schedule: has more = %d\n", pisnd_spi_has_more()); + if (task == TASK_PROCESS) + queue_work(pisnd_workqueue, &pisnd_work_process); + } +} + +static irqreturn_t data_available_interrupt_handler(int irq, void *dev_id) +{ + if (irq == gpiod_to_irq(data_available) && pisnd_spi_has_more()) { + printd("schedule from irq\n"); + pisnd_schedule_process(TASK_PROCESS); + } + + return IRQ_HANDLED; +} + +static DEFINE_SPINLOCK(spilock); +static unsigned long spilockflags; + +static uint16_t spi_transfer16(uint16_t val) +{ + int err; + struct spi_transfer transfer; + struct spi_message msg; + uint8_t txbuf[2]; + uint8_t rxbuf[2]; + + if (!pisnd_spi_device) { + printe("pisnd_spi_device null, returning\n"); + return 0; + } + + spi_message_init(&msg); + + memset(&transfer, 0, sizeof(transfer)); + memset(&rxbuf, 0, sizeof(rxbuf)); + + txbuf[0] = val >> 8; + txbuf[1] = val & 0xff; + + transfer.tx_buf = &txbuf; + transfer.rx_buf = &rxbuf; + transfer.len = sizeof(txbuf); + transfer.speed_hz = 125000; + transfer.delay_usecs = 100; + spi_message_add_tail(&transfer, &msg); + + spin_lock_irqsave(&spilock, spilockflags); + err = spi_sync(pisnd_spi_device, &msg); + spin_unlock_irqrestore(&spilock, spilockflags); + + if (err < 0) { + printe("spi_sync error %d\n", err); + return 0; + } + + printd("received: %02x%02x\n", rxbuf[0], rxbuf[1]); + printd("hasMore %d\n", pisnd_spi_has_more()); + + return (rxbuf[0] << 8) | rxbuf[1]; +} + +static int spi_read_bytes(char *dst, size_t length, uint8_t *bytesRead) +{ + uint16_t rx; + uint8_t size; + uint8_t i; + + memset(dst, 0, length); + *bytesRead = 0; + + rx = spi_transfer16(0); + if (!(rx >> 8)) + return -EINVAL; + + size = rx & 0xff; + + if (size > length) + return -EINVAL; + + for (i = 0; i < size; ++i) { + rx = spi_transfer16(0); + if (!(rx >> 8)) + return -EINVAL; + + dst[i] = rx & 0xff; + } + + *bytesRead = i; + + return 0; +} + +static int spi_device_match(struct device *dev, void *data) +{ + struct spi_device *spi = container_of(dev, struct spi_device, dev); + + printd(" %s %s %dkHz %d bits mode=0x%02X\n", + spi->modalias, dev_name(dev), spi->max_speed_hz/1000, + spi->bits_per_word, spi->mode); + + if (strcmp("pisound-spi", spi->modalias) == 0) { + printi("\tFound!\n"); + return 1; + } + + printe("\tNot found!\n"); + return 0; +} + +static struct spi_device *pisnd_spi_find_device(void) +{ + struct device *dev; + + printi("Searching for spi device...\n"); + dev = bus_find_device(&spi_bus_type, NULL, NULL, spi_device_match); + if (dev != NULL) + return container_of(dev, struct spi_device, dev); + else + return NULL; +} + +static void pisnd_work_handler(struct work_struct *work) +{ + uint16_t rx; + uint16_t tx; + uint8_t val; + + if (work == &pisnd_work_process) { + if (pisnd_spi_device == NULL) + return; + + do { + val = 0; + tx = 0; + + if (kfifo_get(&spi_fifo_out, &val)) + tx = 0x0f00 | val; + + rx = spi_transfer16(tx); + + if (rx & 0xff00) { + kfifo_put(&spi_fifo_in, rx & 0xff); + if (kfifo_len(&spi_fifo_in) > 16 + && g_recvCallback) + g_recvCallback(g_recvData); + } + } while (rx != 0 + || !kfifo_is_empty(&spi_fifo_out) + || pisnd_spi_has_more() + ); + + if (!kfifo_is_empty(&spi_fifo_in) && g_recvCallback) + g_recvCallback(g_recvData); + } +} + +static int pisnd_spi_gpio_init(struct device *dev) +{ + spi_reset = gpiod_get_index(dev, "reset", 1, GPIOD_ASIS); + data_available = gpiod_get_index(dev, "data_available", 0, GPIOD_ASIS); + + gpiod_direction_output(spi_reset, 1); + gpiod_direction_input(data_available); + + /* Reset the slave. */ + gpiod_set_value(spi_reset, false); + mdelay(1); + gpiod_set_value(spi_reset, true); + + /* Give time for spi slave to start. */ + mdelay(64); + + return 0; +} + +static void pisnd_spi_gpio_uninit(void) +{ + gpiod_set_value(spi_reset, false); + gpiod_put(spi_reset); + spi_reset = NULL; + + gpiod_put(data_available); + data_available = NULL; +} + +static int pisnd_spi_gpio_irq_init(struct device *dev) +{ + return request_irq( + gpiod_to_irq(data_available), + data_available_interrupt_handler, + IRQF_TIMER | IRQF_TRIGGER_RISING, + "data_available_int", + NULL + ); +} + +static void pisnd_spi_gpio_irq_uninit(void) +{ + free_irq(gpiod_to_irq(data_available), NULL); +} + +static int spi_read_info(void) +{ + uint16_t tmp; + uint8_t count; + uint8_t n; + uint8_t i; + uint8_t j; + char buffer[257]; + int ret; + char *p; + + memset(g_serial_num, 0, sizeof(g_serial_num)); + memset(g_version, 0, sizeof(g_version)); + memset(g_id, 0, sizeof(g_id)); + + tmp = spi_transfer16(0); + + if (!(tmp >> 8)) + return -EINVAL; + + count = tmp & 0xff; + + for (i = 0; i < count; ++i) { + memset(buffer, 0, sizeof(buffer)); + ret = spi_read_bytes(buffer, sizeof(buffer)-1, &n); + + if (ret < 0) + return ret; + + switch (i) { + case 0: + if (n != 2) + return -EINVAL; + + snprintf( + g_version, + sizeof(g_version), + "%x.%02x", + buffer[0], + buffer[1] + ); + break; + case 1: + if (n >= sizeof(g_serial_num)) + return -EINVAL; + + memcpy(g_serial_num, buffer, sizeof(g_serial_num)); + break; + case 2: + { + if (n >= sizeof(g_id)) + return -EINVAL; + + p = g_id; + for (j = 0; j < n; ++j) + p += sprintf(p, "%02x", buffer[j]); + } + break; + default: + break; + } + } + + return 0; +} + +static int pisnd_spi_init(struct device *dev) +{ + int ret; + struct spi_device *spi; + + memset(g_serial_num, 0, sizeof(g_serial_num)); + memset(g_id, 0, sizeof(g_id)); + memset(g_version, 0, sizeof(g_version)); + + spi = pisnd_spi_find_device(); + + if (spi != NULL) { + printd("initializing spi!\n"); + pisnd_spi_device = spi; + ret = spi_setup(pisnd_spi_device); + } else { + printe("SPI device not found, deferring!\n"); + return -EPROBE_DEFER; + } + + ret = pisnd_spi_gpio_init(dev); + + if (ret < 0) { + printe("SPI GPIO init failed: %d\n", ret); + spi_dev_put(pisnd_spi_device); + pisnd_spi_device = NULL; + pisnd_spi_gpio_uninit(); + return ret; + } + + ret = spi_read_info(); + + if (ret < 0) { + printe("Reading card info failed: %d\n", ret); + spi_dev_put(pisnd_spi_device); + pisnd_spi_device = NULL; + pisnd_spi_gpio_uninit(); + return ret; + } + + /* Flash the LEDs. */ + spi_transfer16(0xf000); + + ret = pisnd_spi_gpio_irq_init(dev); + if (ret < 0) { + printe("SPI irq request failed: %d\n", ret); + spi_dev_put(pisnd_spi_device); + pisnd_spi_device = NULL; + pisnd_spi_gpio_irq_uninit(); + pisnd_spi_gpio_uninit(); + } + + ret = pisnd_init_workqueues(); + if (ret != 0) { + printe("Workqueue initialization failed: %d\n", ret); + spi_dev_put(pisnd_spi_device); + pisnd_spi_device = NULL; + pisnd_spi_gpio_irq_uninit(); + pisnd_spi_gpio_uninit(); + pisnd_uninit_workqueues(); + return ret; + } + + if (pisnd_spi_has_more()) { + printd("data is available, scheduling from init\n"); + pisnd_schedule_process(TASK_PROCESS); + } + + return 0; +} + +static void pisnd_spi_uninit(void) +{ + pisnd_uninit_workqueues(); + + spi_dev_put(pisnd_spi_device); + pisnd_spi_device = NULL; + + pisnd_spi_gpio_irq_uninit(); + pisnd_spi_gpio_uninit(); +} + +static void pisnd_spi_send(uint8_t val) +{ + kfifo_put(&spi_fifo_out, val); + printd("schedule from spi_send\n"); + pisnd_schedule_process(TASK_PROCESS); +} + +static uint8_t pisnd_spi_recv(uint8_t *buffer, uint8_t length) +{ + return kfifo_out(&spi_fifo_in, buffer, length); +} + +static void pisnd_spi_set_callback(pisnd_spi_recv_cb cb, void *data) +{ + g_recvData = data; + g_recvCallback = cb; +} + +static const char *pisnd_spi_get_serial(void) +{ + if (strlen(g_serial_num)) + return g_serial_num; + + return ""; +} + +static const char *pisnd_spi_get_id(void) +{ + if (strlen(g_id)) + return g_id; + + return ""; +} + +static const char *pisnd_spi_get_version(void) +{ + if (strlen(g_version)) + return g_version; + + return ""; +} + +static const struct of_device_id pisound_of_match[] = { + { .compatible = "blokaslabs,pisound", }, + { .compatible = "blokaslabs,pisound-spi", }, + {}, +}; + +static struct gpio_desc *osr0, *osr1, *osr2; +static struct gpio_desc *reset; +static struct gpio_desc *button; + +static int pisnd_hw_params( + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params + ) +{ + printd("rate = %d\n", params_rate(params)); + printd("ch = %d\n", params_channels(params)); + printd("bits = %u\n", + snd_pcm_format_physical_width(params_format(params))); + printd("format = %d\n", params_format(params)); + + gpiod_set_value(reset, false); + + switch (params_rate(params)) { + case 48000: + gpiod_set_value(osr0, true); + gpiod_set_value(osr1, false); + gpiod_set_value(osr2, false); + break; + case 96000: + gpiod_set_value(osr0, true); + gpiod_set_value(osr1, true); + gpiod_set_value(osr2, false); + break; + case 192000: + gpiod_set_value(osr0, true); + gpiod_set_value(osr1, true); + gpiod_set_value(osr2, true); + break; + default: + printe("Unsupported rate %u!\n", params_rate(params)); + return -EINVAL; + } + + gpiod_set_value(reset, true); + + return 0; +} + +static unsigned int rates[3] = { + 48000, 96000, 192000 +}; + +static struct snd_pcm_hw_constraint_list constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, +}; + +static unsigned int sample_bits[] = { + 24, 32 +}; + +static struct snd_pcm_hw_constraint_list constraints_sample_bits = { + .count = ARRAY_SIZE(sample_bits), + .list = sample_bits, + .mask = 0, +}; + +static int pisnd_startup(struct snd_pcm_substream *substream) +{ + int err = snd_pcm_hw_constraint_list( + substream->runtime, + 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_rates + ); + + if (err < 0) + return err; + + err = snd_pcm_hw_constraint_list( + substream->runtime, + 0, + SNDRV_PCM_HW_PARAM_SAMPLE_BITS, + &constraints_sample_bits + ); + + if (err < 0) + return err; + + return 0; +} + +static struct snd_soc_ops pisnd_ops = { + .startup = pisnd_startup, + .hw_params = pisnd_hw_params, +}; + +static struct snd_soc_dai_link pisnd_dai[] = { + { + .name = "pisound", + .stream_name = "pisound", + .cpu_dai_name = "bcm2708-i2s.0", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "bcm2708-i2s.0", + .codec_name = "snd-soc-dummy", + .dai_fmt = + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM, + .ops = &pisnd_ops, + }, +}; + +static int pisnd_card_probe(struct snd_soc_card *card) +{ + int err = pisnd_midi_init(card->snd_card); + + if (err < 0) + printe("pisnd_midi_init failed: %d\n", err); + + return err; +} + +static int pisnd_card_remove(struct snd_soc_card *card) +{ + pisnd_midi_uninit(); + return 0; +} + +static struct snd_soc_card pisnd_card = { + .name = "pisound", + .owner = THIS_MODULE, + .dai_link = pisnd_dai, + .num_links = ARRAY_SIZE(pisnd_dai), + .probe = pisnd_card_probe, + .remove = pisnd_card_remove, +}; + +static int pisnd_init_gpio(struct device *dev) +{ + osr0 = gpiod_get_index(dev, "osr", 0, GPIOD_ASIS); + osr1 = gpiod_get_index(dev, "osr", 1, GPIOD_ASIS); + osr2 = gpiod_get_index(dev, "osr", 2, GPIOD_ASIS); + + reset = gpiod_get_index(dev, "reset", 0, GPIOD_ASIS); + + button = gpiod_get_index(dev, "button", 0, GPIOD_ASIS); + + gpiod_direction_output(osr0, 1); + gpiod_direction_output(osr1, 1); + gpiod_direction_output(osr2, 1); + gpiod_direction_output(reset, 1); + + gpiod_set_value(reset, false); + gpiod_set_value(osr0, true); + gpiod_set_value(osr1, false); + gpiod_set_value(osr2, false); + gpiod_set_value(reset, true); + + gpiod_export(button, false); + + return 0; +} + +static int pisnd_uninit_gpio(void) +{ + int i; + + struct gpio_desc **gpios[] = { + &osr0, &osr1, &osr2, &reset, &button, + }; + + gpiod_unexport(button); + + for (i = 0; i < ARRAY_SIZE(gpios); ++i) { + if (*gpios[i] == NULL) { + printd("weird, GPIO[%d] is NULL already\n", i); + continue; + } + + gpiod_put(*gpios[i]); + *gpios[i] = NULL; + } + + return 0; +} + +static struct kobject *pisnd_kobj; + +static ssize_t pisnd_serial_show( + struct kobject *kobj, + struct kobj_attribute *attr, + char *buf + ) +{ + return sprintf(buf, "%s\n", pisnd_spi_get_serial()); +} + +static ssize_t pisnd_id_show( + struct kobject *kobj, + struct kobj_attribute *attr, + char *buf + ) +{ + return sprintf(buf, "%s\n", pisnd_spi_get_id()); +} + +static ssize_t pisnd_version_show( + struct kobject *kobj, + struct kobj_attribute *attr, + char *buf + ) +{ + return sprintf(buf, "%s\n", pisnd_spi_get_version()); +} + +static struct kobj_attribute pisnd_serial_attribute = + __ATTR(serial, 0644, pisnd_serial_show, NULL); +static struct kobj_attribute pisnd_id_attribute = + __ATTR(id, 0644, pisnd_id_show, NULL); +static struct kobj_attribute pisnd_version_attribute = + __ATTR(version, 0644, pisnd_version_show, NULL); + +static struct attribute *attrs[] = { + &pisnd_serial_attribute.attr, + &pisnd_id_attribute.attr, + &pisnd_version_attribute.attr, + NULL +}; + +static struct attribute_group attr_group = { .attrs = attrs }; + +static int pisnd_probe(struct platform_device *pdev) +{ + int ret = 0; + int i; + + ret = pisnd_spi_init(&pdev->dev); + if (ret < 0) { + printe("pisnd_spi_init failed: %d\n", ret); + return ret; + } + + printi("Detected pisound card:\n"); + printi("\tSerial: %s\n", pisnd_spi_get_serial()); + printi("\tVersion: %s\n", pisnd_spi_get_version()); + printi("\tId: %s\n", pisnd_spi_get_id()); + + pisnd_kobj = kobject_create_and_add("pisound", kernel_kobj); + if (!pisnd_kobj) { + pisnd_spi_uninit(); + return -ENOMEM; + } + + ret = sysfs_create_group(pisnd_kobj, &attr_group); + if (ret < 0) { + pisnd_spi_uninit(); + kobject_put(pisnd_kobj); + return -ENOMEM; + } + + pisnd_init_gpio(&pdev->dev); + pisnd_card.dev = &pdev->dev; + + if (pdev->dev.of_node) { + struct device_node *i2s_node; + + i2s_node = of_parse_phandle( + pdev->dev.of_node, + "i2s-controller", + 0 + ); + + for (i = 0; i < pisnd_card.num_links; ++i) { + struct snd_soc_dai_link *dai = &pisnd_dai[i]; + + if (i2s_node) { + dai->cpu_dai_name = NULL; + dai->cpu_of_node = i2s_node; + dai->platform_name = NULL; + dai->platform_of_node = i2s_node; + dai->stream_name = pisnd_spi_get_serial(); + } + } + } + + ret = snd_soc_register_card(&pisnd_card); + + if (ret < 0) { + printe("snd_soc_register_card() failed: %d\n", ret); + pisnd_uninit_gpio(); + kobject_put(pisnd_kobj); + pisnd_spi_uninit(); + } + + return ret; +} + +static int pisnd_remove(struct platform_device *pdev) +{ + printi("Unloading.\n"); + + if (pisnd_kobj) { + kobject_put(pisnd_kobj); + pisnd_kobj = NULL; + } + + pisnd_spi_uninit(); + + /* Turn off */ + gpiod_set_value(reset, false); + pisnd_uninit_gpio(); + + return snd_soc_unregister_card(&pisnd_card); +} + +MODULE_DEVICE_TABLE(of, pisound_of_match); + +static struct platform_driver pisnd_driver = { + .driver = { + .name = "snd-rpi-pisound", + .owner = THIS_MODULE, + .of_match_table = pisound_of_match, + }, + .probe = pisnd_probe, + .remove = pisnd_remove, +}; + +module_platform_driver(pisnd_driver); + +MODULE_AUTHOR("Giedrius Trainavicius "); +MODULE_DESCRIPTION("ASoC Driver for pisound, http://blokas.io/pisound"); +MODULE_LICENSE("GPL v2");