/* * Pisound Linux kernel module. * Copyright (C) 2016-2020 Vilniaus Blokas UAB, https://blokas.io/pisound * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; version 2 of the * License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ #include #include #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_flush(void); static void pisnd_spi_start(void); static uint8_t pisnd_spi_recv(uint8_t *buffer, uint8_t length); typedef void (*pisnd_spi_recv_cb)(void *data); static void pisnd_spi_set_callback(pisnd_spi_recv_cb cb, void *data); static const char *pisnd_spi_get_serial(void); static const char *pisnd_spi_get_id(void); static const char *pisnd_spi_get_fw_version(void); static const char *pisnd_spi_get_hw_version(void); static int pisnd_midi_init(struct snd_card *card); static void pisnd_midi_uninit(void); enum task_e { TASK_PROCESS = 0, }; static void pisnd_schedule_process(enum task_e task); #define PISOUND_LOG_PREFIX "pisound: " #ifdef PISOUND_DEBUG # define printd(...) pr_alert(PISOUND_LOG_PREFIX __VA_ARGS__) #else # define printd(...) do {} while (0) #endif #define printe(...) pr_err(PISOUND_LOG_PREFIX __VA_ARGS__) #define printi(...) pr_info(PISOUND_LOG_PREFIX __VA_ARGS__) static struct snd_rawmidi *g_rmidi; static struct snd_rawmidi_substream *g_midi_output_substream; static int pisnd_output_open(struct snd_rawmidi_substream *substream) { g_midi_output_substream = substream; return 0; } static int pisnd_output_close(struct snd_rawmidi_substream *substream) { g_midi_output_substream = NULL; return 0; } static void pisnd_output_trigger( struct snd_rawmidi_substream *substream, int up ) { if (substream != g_midi_output_substream) { printe("MIDI output trigger called for an unexpected stream!"); return; } if (!up) return; pisnd_spi_start(); } static void pisnd_output_drain(struct snd_rawmidi_substream *substream) { pisnd_spi_flush(); } static int pisnd_input_open(struct snd_rawmidi_substream *substream) { return 0; } static int pisnd_input_close(struct snd_rawmidi_substream *substream) { return 0; } static void pisnd_midi_recv_callback(void *substream) { uint8_t data[128]; uint8_t n = 0; while ((n = pisnd_spi_recv(data, sizeof(data)))) { int res = snd_rawmidi_receive(substream, data, n); (void)res; printd("midi recv %u bytes, res = %d\n", n, res); } } static void pisnd_input_trigger(struct snd_rawmidi_substream *substream, int up) { if (up) { pisnd_spi_set_callback(pisnd_midi_recv_callback, substream); pisnd_schedule_process(TASK_PROCESS); } else { pisnd_spi_set_callback(NULL, NULL); } } static struct snd_rawmidi_ops pisnd_output_ops = { .open = pisnd_output_open, .close = pisnd_output_close, .trigger = pisnd_output_trigger, .drain = pisnd_output_drain, }; static struct snd_rawmidi_ops pisnd_input_ops = { .open = pisnd_input_open, .close = pisnd_input_close, .trigger = pisnd_input_trigger, }; static void pisnd_get_port_info( struct snd_rawmidi *rmidi, int number, struct snd_seq_port_info *seq_port_info ) { seq_port_info->type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | SNDRV_SEQ_PORT_TYPE_HARDWARE | SNDRV_SEQ_PORT_TYPE_PORT; seq_port_info->midi_voices = 0; } static struct snd_rawmidi_global_ops pisnd_global_ops = { .get_port_info = pisnd_get_port_info, }; static int pisnd_midi_init(struct snd_card *card) { int err; g_midi_output_substream = NULL; err = snd_rawmidi_new(card, "pisound MIDI", 0, 1, 1, &g_rmidi); if (err < 0) { printe("snd_rawmidi_new failed: %d\n", err); return err; } strcpy(g_rmidi->name, "pisound MIDI "); strcat(g_rmidi->name, pisnd_spi_get_serial()); g_rmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT | SNDRV_RAWMIDI_INFO_DUPLEX; g_rmidi->ops = &pisnd_global_ops; g_rmidi->private_data = (void *)0; snd_rawmidi_set_ops( g_rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &pisnd_output_ops ); snd_rawmidi_set_ops( g_rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &pisnd_input_ops ); return 0; } static void pisnd_midi_uninit(void) { } static void *g_recvData; static pisnd_spi_recv_cb g_recvCallback; #define FIFO_SIZE 4096 static char g_serial_num[11]; static char g_id[25]; enum { MAX_VERSION_STR_LEN = 6 }; static char g_fw_version[MAX_VERSION_STR_LEN]; static char g_hw_version[MAX_VERSION_STR_LEN]; static uint8_t g_ledFlashDuration; static bool g_ledFlashDurationChanged; DEFINE_KFIFO(spi_fifo_in, uint8_t, FIFO_SIZE); DEFINE_KFIFO(spi_fifo_out, uint8_t, FIFO_SIZE); static struct gpio_desc *data_available; static struct gpio_desc *spi_reset; static struct spi_device *pisnd_spi_device; static struct workqueue_struct *pisnd_workqueue; static struct work_struct pisnd_work_process; static void pisnd_work_handler(struct work_struct *work); static void spi_transfer(const uint8_t *txbuf, uint8_t *rxbuf, int len); static uint16_t spi_transfer16(uint16_t val); static int pisnd_init_workqueues(void) { pisnd_workqueue = create_singlethread_workqueue("pisnd_workqueue"); INIT_WORK(&pisnd_work_process, pisnd_work_handler); return 0; } static void pisnd_uninit_workqueues(void) { flush_workqueue(pisnd_workqueue); destroy_workqueue(pisnd_workqueue); pisnd_workqueue = NULL; } static bool pisnd_spi_has_more(void) { return gpiod_get_value(data_available); } static void pisnd_schedule_process(enum task_e task) { if (pisnd_spi_device != NULL && pisnd_workqueue != NULL && !work_pending(&pisnd_work_process) ) { printd("schedule: has more = %d\n", pisnd_spi_has_more()); if (task == TASK_PROCESS) queue_work(pisnd_workqueue, &pisnd_work_process); } } static irqreturn_t data_available_interrupt_handler(int irq, void *dev_id) { if (irq == gpiod_to_irq(data_available) && pisnd_spi_has_more()) { printd("schedule from irq\n"); pisnd_schedule_process(TASK_PROCESS); } return IRQ_HANDLED; } static uint16_t spi_transfer16(uint16_t val) { uint8_t txbuf[2]; uint8_t rxbuf[2]; if (!pisnd_spi_device) { printe("pisnd_spi_device null, returning\n"); return 0; } txbuf[0] = val >> 8; txbuf[1] = val & 0xff; spi_transfer(txbuf, rxbuf, sizeof(txbuf)); printd("received: %02x%02x\n", rxbuf[0], rxbuf[1]); return (rxbuf[0] << 8) | rxbuf[1]; } static void spi_transfer(const uint8_t *txbuf, uint8_t *rxbuf, int len) { int err; struct spi_transfer transfer; struct spi_message msg; memset(rxbuf, 0, len); if (!pisnd_spi_device) { printe("pisnd_spi_device null, returning\n"); return; } spi_message_init(&msg); memset(&transfer, 0, sizeof(transfer)); transfer.tx_buf = txbuf; transfer.rx_buf = rxbuf; transfer.len = len; transfer.speed_hz = 150000; transfer.delay.value = 10; transfer.delay.unit = SPI_DELAY_UNIT_USECS; spi_message_add_tail(&transfer, &msg); err = spi_sync(pisnd_spi_device, &msg); if (err < 0) { printe("spi_sync error %d\n", err); return; } printd("hasMore %d\n", pisnd_spi_has_more()); } static int spi_read_bytes(char *dst, size_t length, uint8_t *bytesRead) { uint16_t rx; uint8_t size; uint8_t i; memset(dst, 0, length); *bytesRead = 0; rx = spi_transfer16(0); if (!(rx >> 8)) return -EINVAL; size = rx & 0xff; if (size > length) return -EINVAL; for (i = 0; i < size; ++i) { rx = spi_transfer16(0); if (!(rx >> 8)) return -EINVAL; dst[i] = rx & 0xff; } *bytesRead = i; return 0; } static int spi_device_match(struct device *dev, const void *data) { struct spi_device *spi = container_of(dev, struct spi_device, dev); printd(" %s %s %dkHz %d bits mode=0x%02X\n", spi->modalias, dev_name(dev), spi->max_speed_hz/1000, spi->bits_per_word, spi->mode); if (strcmp("pisound-spi", spi->modalias) == 0) { printi("\tFound!\n"); return 1; } printe("\tNot found!\n"); return 0; } static struct spi_device *pisnd_spi_find_device(void) { struct device *dev; printi("Searching for spi device...\n"); dev = bus_find_device(&spi_bus_type, NULL, NULL, spi_device_match); if (dev != NULL) return container_of(dev, struct spi_device, dev); else return NULL; } static void pisnd_work_handler(struct work_struct *work) { enum { TRANSFER_SIZE = 4 }; enum { PISOUND_OUTPUT_BUFFER_SIZE_MILLIBYTES = 127 * 1000 }; enum { MIDI_MILLIBYTES_PER_JIFFIE = (3125 * 1000) / HZ }; int out_buffer_used_millibytes = 0; unsigned long now; uint8_t val; uint8_t txbuf[TRANSFER_SIZE]; uint8_t rxbuf[TRANSFER_SIZE]; uint8_t midibuf[TRANSFER_SIZE]; int i, n; bool had_data; unsigned long last_transfer_at = jiffies; if (work == &pisnd_work_process) { if (pisnd_spi_device == NULL) return; do { if (g_midi_output_substream && kfifo_avail(&spi_fifo_out) >= sizeof(midibuf)) { n = snd_rawmidi_transmit_peek( g_midi_output_substream, midibuf, sizeof(midibuf) ); if (n > 0) { for (i = 0; i < n; ++i) kfifo_put( &spi_fifo_out, midibuf[i] ); snd_rawmidi_transmit_ack( g_midi_output_substream, i ); } } had_data = false; memset(txbuf, 0, sizeof(txbuf)); for (i = 0; i < sizeof(txbuf) && ((out_buffer_used_millibytes+1000 < PISOUND_OUTPUT_BUFFER_SIZE_MILLIBYTES) || g_ledFlashDurationChanged); i += 2) { val = 0; if (g_ledFlashDurationChanged) { txbuf[i+0] = 0xf0; txbuf[i+1] = g_ledFlashDuration; g_ledFlashDuration = 0; g_ledFlashDurationChanged = false; } else if (kfifo_get(&spi_fifo_out, &val)) { txbuf[i+0] = 0x0f; txbuf[i+1] = val; out_buffer_used_millibytes += 1000; } } spi_transfer(txbuf, rxbuf, sizeof(txbuf)); /* Estimate the Pisound's MIDI output buffer usage, so * that we don't overflow it. Space in the buffer should * be becoming available at the UART MIDI byte transfer * rate. */ now = jiffies; if (now != last_transfer_at) { out_buffer_used_millibytes -= (now - last_transfer_at) * MIDI_MILLIBYTES_PER_JIFFIE; if (out_buffer_used_millibytes < 0) out_buffer_used_millibytes = 0; last_transfer_at = now; } for (i = 0; i < sizeof(rxbuf); i += 2) { if (rxbuf[i]) { kfifo_put(&spi_fifo_in, rxbuf[i+1]); if (kfifo_len(&spi_fifo_in) > 16 && g_recvCallback) g_recvCallback(g_recvData); had_data = true; } } } while (had_data || !kfifo_is_empty(&spi_fifo_out) || pisnd_spi_has_more() || g_ledFlashDurationChanged || out_buffer_used_millibytes != 0 ); if (!kfifo_is_empty(&spi_fifo_in) && g_recvCallback) g_recvCallback(g_recvData); } } static int pisnd_spi_gpio_init(struct device *dev) { spi_reset = gpiod_get_index(dev, "reset", 1, GPIOD_ASIS); data_available = gpiod_get_index(dev, "data_available", 0, GPIOD_ASIS); gpiod_direction_output(spi_reset, 1); gpiod_direction_input(data_available); /* Reset the slave. */ gpiod_set_value(spi_reset, false); mdelay(1); gpiod_set_value(spi_reset, true); /* Give time for spi slave to start. */ mdelay(64); return 0; } static void pisnd_spi_gpio_uninit(void) { gpiod_set_value(spi_reset, false); gpiod_put(spi_reset); spi_reset = NULL; gpiod_put(data_available); data_available = NULL; } static int pisnd_spi_gpio_irq_init(struct device *dev) { return request_threaded_irq( gpiod_to_irq(data_available), NULL, data_available_interrupt_handler, IRQF_TIMER | IRQF_TRIGGER_RISING | IRQF_ONESHOT, "data_available_int", NULL ); } static void pisnd_spi_gpio_irq_uninit(void) { free_irq(gpiod_to_irq(data_available), NULL); } static int spi_read_info(void) { uint16_t tmp; uint8_t count; uint8_t n; uint8_t i; uint8_t j; char buffer[257]; int ret; char *p; memset(g_serial_num, 0, sizeof(g_serial_num)); memset(g_fw_version, 0, sizeof(g_fw_version)); strcpy(g_hw_version, "1.0"); // Assume 1.0 hw 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_fw_version, MAX_VERSION_STR_LEN, "%x.%02x", buffer[0], buffer[1] ); g_fw_version[MAX_VERSION_STR_LEN-1] = '\0'; break; case 3: if (n != 2) return -EINVAL; snprintf( g_hw_version, MAX_VERSION_STR_LEN, "%x.%x", buffer[0], buffer[1] ); g_hw_version[MAX_VERSION_STR_LEN-1] = '\0'; 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*2 >= sizeof(g_id)) return -EINVAL; p = g_id; for (j = 0; j < n; ++j) p += sprintf(p, "%02x", buffer[j]); *p = '\0'; } 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_fw_version, 0, sizeof(g_fw_version)); memset(g_hw_version, 0, sizeof(g_hw_version)); spi = pisnd_spi_find_device(); if (spi != NULL) { printd("initializing spi!\n"); pisnd_spi_device = spi; ret = spi_setup(pisnd_spi_device); } else { printe("SPI device not found, deferring!\n"); return -EPROBE_DEFER; } ret = pisnd_spi_gpio_init(dev); if (ret < 0) { printe("SPI GPIO init failed: %d\n", ret); spi_dev_put(pisnd_spi_device); pisnd_spi_device = NULL; pisnd_spi_gpio_uninit(); return ret; } ret = spi_read_info(); if (ret < 0) { printe("Reading card info failed: %d\n", ret); spi_dev_put(pisnd_spi_device); pisnd_spi_device = NULL; pisnd_spi_gpio_uninit(); return ret; } /* Flash the LEDs. */ spi_transfer16(0xf008); ret = pisnd_spi_gpio_irq_init(dev); if (ret < 0) { printe("SPI irq request failed: %d\n", ret); spi_dev_put(pisnd_spi_device); pisnd_spi_device = NULL; pisnd_spi_gpio_irq_uninit(); pisnd_spi_gpio_uninit(); } ret = pisnd_init_workqueues(); if (ret != 0) { printe("Workqueue initialization failed: %d\n", ret); spi_dev_put(pisnd_spi_device); pisnd_spi_device = NULL; pisnd_spi_gpio_irq_uninit(); pisnd_spi_gpio_uninit(); pisnd_uninit_workqueues(); return ret; } if (pisnd_spi_has_more()) { printd("data is available, scheduling from init\n"); pisnd_schedule_process(TASK_PROCESS); } return 0; } static void pisnd_spi_uninit(void) { pisnd_uninit_workqueues(); spi_dev_put(pisnd_spi_device); pisnd_spi_device = NULL; pisnd_spi_gpio_irq_uninit(); pisnd_spi_gpio_uninit(); } static void pisnd_spi_flash_leds(uint8_t duration) { g_ledFlashDuration = duration; g_ledFlashDurationChanged = true; printd("schedule from spi_flash_leds\n"); pisnd_schedule_process(TASK_PROCESS); } static void pisnd_spi_flush(void) { while (!kfifo_is_empty(&spi_fifo_out)) { pisnd_spi_start(); flush_workqueue(pisnd_workqueue); } } static void pisnd_spi_start(void) { printd("schedule from spi_start\n"); pisnd_schedule_process(TASK_PROCESS); } static uint8_t pisnd_spi_recv(uint8_t *buffer, uint8_t length) { return kfifo_out(&spi_fifo_in, buffer, length); } static void pisnd_spi_set_callback(pisnd_spi_recv_cb cb, void *data) { g_recvData = data; g_recvCallback = cb; } static const char *pisnd_spi_get_serial(void) { return g_serial_num; } static const char *pisnd_spi_get_id(void) { return g_id; } static const char *pisnd_spi_get_fw_version(void) { return g_fw_version; } static const char *pisnd_spi_get_hw_version(void) { return g_hw_version; } static const struct of_device_id pisound_of_match[] = { { .compatible = "blokaslabs,pisound", }, { .compatible = "blokaslabs,pisound-spi", }, {}, }; enum { SWITCH = 0, VOLUME = 1, }; static int pisnd_ctl_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { if (kcontrol->private_value == SWITCH) { uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; uinfo->count = 1; uinfo->value.integer.min = 0; uinfo->value.integer.max = 1; return 0; } else if (kcontrol->private_value == VOLUME) { uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; uinfo->count = 1; uinfo->value.integer.min = 0; uinfo->value.integer.max = 100; return 0; } return -EINVAL; } static int pisnd_ctl_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { if (kcontrol->private_value == SWITCH) { ucontrol->value.integer.value[0] = 1; return 0; } else if (kcontrol->private_value == VOLUME) { ucontrol->value.integer.value[0] = 100; return 0; } return -EINVAL; } static struct snd_kcontrol_new pisnd_ctl[] = { { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "PCM Playback Switch", .index = 0, .private_value = SWITCH, .access = SNDRV_CTL_ELEM_ACCESS_READ, .info = pisnd_ctl_info, .get = pisnd_ctl_get, }, { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "PCM Playback Volume", .index = 0, .private_value = VOLUME, .access = SNDRV_CTL_ELEM_ACCESS_READ, .info = pisnd_ctl_info, .get = pisnd_ctl_get, }, }; static int pisnd_ctl_init(struct snd_card *card) { int err, i; for (i = 0; i < ARRAY_SIZE(pisnd_ctl); ++i) { err = snd_ctl_add(card, snd_ctl_new1(&pisnd_ctl[i], NULL)); if (err < 0) return err; } return 0; } static int pisnd_ctl_uninit(void) { return 0; } static struct gpio_desc *osr0, *osr1, *osr2; static struct gpio_desc *reset; static struct gpio_desc *button; static int pisnd_hw_params( struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params ) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); /* Pisound runs on fixed 32 clock counts per channel, * as generated by the master ADC. */ snd_soc_dai_set_bclk_ratio(cpu_dai, 32*2); printd("rate = %d\n", params_rate(params)); printd("ch = %d\n", params_channels(params)); printd("bits = %u\n", snd_pcm_format_physical_width(params_format(params))); printd("format = %d\n", params_format(params)); gpiod_set_value(reset, false); switch (params_rate(params)) { case 48000: gpiod_set_value(osr0, true); gpiod_set_value(osr1, false); gpiod_set_value(osr2, false); break; case 96000: gpiod_set_value(osr0, true); gpiod_set_value(osr1, false); gpiod_set_value(osr2, true); break; case 192000: gpiod_set_value(osr0, true); gpiod_set_value(osr1, true); gpiod_set_value(osr2, true); break; default: printe("Unsupported rate %u!\n", params_rate(params)); return -EINVAL; } gpiod_set_value(reset, true); return 0; } static unsigned int rates[3] = { 48000, 96000, 192000 }; static struct snd_pcm_hw_constraint_list constraints_rates = { .count = ARRAY_SIZE(rates), .list = rates, .mask = 0, }; static int pisnd_startup(struct snd_pcm_substream *substream) { int err = snd_pcm_hw_constraint_list( substream->runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &constraints_rates ); if (err < 0) return err; err = snd_pcm_hw_constraint_single( substream->runtime, SNDRV_PCM_HW_PARAM_CHANNELS, 2 ); if (err < 0) return err; err = snd_pcm_hw_constraint_mask64( substream->runtime, SNDRV_PCM_HW_PARAM_FORMAT, SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE ); if (err < 0) return err; return 0; } static struct snd_soc_ops pisnd_ops = { .startup = pisnd_startup, .hw_params = pisnd_hw_params, }; SND_SOC_DAILINK_DEFS(pisnd, DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), DAILINK_COMP_ARRAY(COMP_DUMMY()), DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0"))); static struct snd_soc_dai_link pisnd_dai[] = { { .name = "pisound", .stream_name = "pisound", .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM, .ops = &pisnd_ops, SND_SOC_DAILINK_REG(pisnd), }, }; static int pisnd_card_probe(struct snd_soc_card *card) { int err = pisnd_midi_init(card->snd_card); if (err < 0) { printe("pisnd_midi_init failed: %d\n", err); return err; } err = pisnd_ctl_init(card->snd_card); if (err < 0) { printe("pisnd_ctl_init failed: %d\n", err); return err; } return 0; } static int pisnd_card_remove(struct snd_soc_card *card) { pisnd_ctl_uninit(); pisnd_midi_uninit(); return 0; } static struct snd_soc_card pisnd_card = { .name = "pisound", .owner = THIS_MODULE, .dai_link = pisnd_dai, .num_links = ARRAY_SIZE(pisnd_dai), .probe = pisnd_card_probe, .remove = pisnd_card_remove, }; static int pisnd_init_gpio(struct device *dev) { osr0 = gpiod_get_index(dev, "osr", 0, GPIOD_ASIS); osr1 = gpiod_get_index(dev, "osr", 1, GPIOD_ASIS); osr2 = gpiod_get_index(dev, "osr", 2, GPIOD_ASIS); reset = gpiod_get_index(dev, "reset", 0, GPIOD_ASIS); button = gpiod_get_index(dev, "button", 0, GPIOD_ASIS); gpiod_direction_output(osr0, 1); gpiod_direction_output(osr1, 1); gpiod_direction_output(osr2, 1); gpiod_direction_output(reset, 1); gpiod_set_value(reset, false); gpiod_set_value(osr0, true); gpiod_set_value(osr1, false); gpiod_set_value(osr2, false); gpiod_set_value(reset, true); gpiod_export(button, false); return 0; } static int pisnd_uninit_gpio(void) { int i; struct gpio_desc **gpios[] = { &osr0, &osr1, &osr2, &reset, &button, }; gpiod_unexport(button); for (i = 0; i < ARRAY_SIZE(gpios); ++i) { if (*gpios[i] == NULL) { printd("weird, GPIO[%d] is NULL already\n", i); continue; } gpiod_put(*gpios[i]); *gpios[i] = NULL; } return 0; } static struct kobject *pisnd_kobj; static ssize_t pisnd_serial_show( struct kobject *kobj, struct kobj_attribute *attr, char *buf ) { return sprintf(buf, "%s\n", pisnd_spi_get_serial()); } static ssize_t pisnd_id_show( struct kobject *kobj, struct kobj_attribute *attr, char *buf ) { return sprintf(buf, "%s\n", pisnd_spi_get_id()); } static ssize_t pisnd_fw_version_show( struct kobject *kobj, struct kobj_attribute *attr, char *buf ) { return sprintf(buf, "%s\n", pisnd_spi_get_fw_version()); } static ssize_t pisnd_hw_version_show( struct kobject *kobj, struct kobj_attribute *attr, char *buf ) { return sprintf(buf, "%s\n", pisnd_spi_get_hw_version()); } static ssize_t pisnd_led_store( struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t length ) { uint32_t timeout; int err; err = kstrtou32(buf, 10, &timeout); if (err == 0 && timeout <= 255) pisnd_spi_flash_leds(timeout); return length; } static struct kobj_attribute pisnd_serial_attribute = __ATTR(serial, 0444, pisnd_serial_show, NULL); static struct kobj_attribute pisnd_id_attribute = __ATTR(id, 0444, pisnd_id_show, NULL); static struct kobj_attribute pisnd_fw_version_attribute = __ATTR(version, 0444, pisnd_fw_version_show, NULL); static struct kobj_attribute pisnd_hw_version_attribute = __ATTR(hw_version, 0444, pisnd_hw_version_show, NULL); static struct kobj_attribute pisnd_led_attribute = __ATTR(led, 0644, NULL, pisnd_led_store); static struct attribute *attrs[] = { &pisnd_serial_attribute.attr, &pisnd_id_attribute.attr, &pisnd_fw_version_attribute.attr, &pisnd_hw_version_attribute.attr, &pisnd_led_attribute.attr, NULL }; static struct attribute_group attr_group = { .attrs = attrs }; static int pisnd_probe(struct platform_device *pdev) { int ret = 0; int i; ret = pisnd_spi_init(&pdev->dev); if (ret < 0) { printe("pisnd_spi_init failed: %d\n", ret); return ret; } printi("Detected Pisound card:\n"); printi("\tSerial: %s\n", pisnd_spi_get_serial()); printi("\tFirmware Version: %s\n", pisnd_spi_get_fw_version()); printi("\tHardware Version: %s\n", pisnd_spi_get_hw_version()); printi("\tId: %s\n", pisnd_spi_get_id()); pisnd_kobj = kobject_create_and_add("pisound", kernel_kobj); if (!pisnd_kobj) { pisnd_spi_uninit(); return -ENOMEM; } ret = sysfs_create_group(pisnd_kobj, &attr_group); if (ret < 0) { pisnd_spi_uninit(); kobject_put(pisnd_kobj); return -ENOMEM; } pisnd_init_gpio(&pdev->dev); pisnd_card.dev = &pdev->dev; if (pdev->dev.of_node) { struct device_node *i2s_node; i2s_node = of_parse_phandle( pdev->dev.of_node, "i2s-controller", 0 ); for (i = 0; i < pisnd_card.num_links; ++i) { struct snd_soc_dai_link *dai = &pisnd_dai[i]; if (i2s_node) { dai->cpus->dai_name = NULL; dai->cpus->of_node = i2s_node; dai->platforms->name = NULL; dai->platforms->of_node = i2s_node; dai->stream_name = pisnd_spi_get_serial(); } } } ret = snd_soc_register_card(&pisnd_card); if (ret < 0) { if (ret != -EPROBE_DEFER) printe("snd_soc_register_card() failed: %d\n", ret); pisnd_uninit_gpio(); kobject_put(pisnd_kobj); pisnd_spi_uninit(); } return ret; } static int pisnd_remove(struct platform_device *pdev) { printi("Unloading.\n"); if (pisnd_kobj) { kobject_put(pisnd_kobj); pisnd_kobj = NULL; } pisnd_spi_uninit(); /* Turn off */ gpiod_set_value(reset, false); pisnd_uninit_gpio(); return snd_soc_unregister_card(&pisnd_card); } MODULE_DEVICE_TABLE(of, pisound_of_match); static struct platform_driver pisnd_driver = { .driver = { .name = "snd-rpi-pisound", .owner = THIS_MODULE, .of_match_table = pisound_of_match, }, .probe = pisnd_probe, .remove = pisnd_remove, }; module_platform_driver(pisnd_driver); MODULE_AUTHOR("Giedrius Trainavicius "); MODULE_DESCRIPTION("ASoC Driver for Pisound, https://blokas.io/pisound"); MODULE_LICENSE("GPL v2");