// SPDX-License-Identifier: GPL-2.0-only
/*
 * Pimidi Linux kernel module.
 * Copyright (C) 2017-2024  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/completion.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/irq.h>
#include <linux/irqdesc.h>
#include <linux/bitops.h>
#include <linux/of_irq.h>
#include <linux/kfifo.h>
#include <linux/list.h>
#include <linux/workqueue.h>
#include <linux/gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
#include <linux/mutex.h>
#include <linux/refcount.h>
#include <linux/crc8.h>
#include <linux/delay.h>

#include <sound/core.h>
#include <sound/initval.h>
#include <sound/rawmidi.h>
#include <sound/asequencer.h>
#include <sound/info.h>

#define PIMIDI_LOG_IMPL(instance, log_func, msg, ...) log_func("pimidi(%s)[%c]: " msg "\n", \
	__func__, (instance) ? (instance)->d + '0' : 'G', ## __VA_ARGS__)

#ifdef PIMIDI_DEBUG
#	define printd(instance, ...) PIMIDI_LOG_IMPL(instance, pr_alert, __VA_ARGS__)
#	define printd_rl(instance, ...) PIMIDI_LOG_IMPL(instance, pr_alert_ratelimited, __VA_ARGS__)
#	define printd_g(...) printd((struct pimidi_instance *)NULL, __VA_ARGS__)
#else
#	define printd(instance, ...) do {} while (0)
#	define printd_rl(instance, ...) do {} while (0)
#	define printd_g(...) do {} while (0)
#endif

#define printe(instance, ...) PIMIDI_LOG_IMPL(instance, pr_err, __VA_ARGS__)
#define printe_rl(instance, ...) PIMIDI_LOG_IMPL(instance, pr_err_ratelimited, __VA_ARGS__)
#define printi(instance, ...) PIMIDI_LOG_IMPL(instance, pr_info, __VA_ARGS__)
#define printw(instance, ...) PIMIDI_LOG_IMPL(instance, pr_warn, __VA_ARGS__)
#define printw_rl(instance, ...) PIMIDI_LOG_IMPL(instance, pr_warn_ratelimited, __VA_ARGS__)

#define printe_g(...) printe((struct pimidi_instance *)NULL, __VA_ARGS__)
#define printi_g(...) printi((struct pimidi_instance *)NULL, __VA_ARGS__)

DECLARE_CRC8_TABLE(pimidi_crc8_table);
enum { PIMIDI_CRC8_POLYNOMIAL = 0x83 };
enum { PIMIDI_MAX_DEVICES = 4 };
enum { PIMIDI_MAX_PACKET_SIZE = 17 };
enum { PIMIDI_PORTS = 2 };

struct pimidi_shared {
	// lock protects the shared reset_gpio and devices list.
	struct mutex            lock;
	struct gpio_desc        *reset_gpio;
	struct workqueue_struct *work_queue;
	struct list_head        devices;
};

static struct pimidi_shared pimidi_global = {
	.devices = LIST_HEAD_INIT(pimidi_global.devices),
};

struct pimidi_version_t {
	u8 hwrev;
	u8 major;
	u8 minor;
	u8 build;
};

enum { PIMIDI_IN_FIFO_SIZE = 4096 };

struct pimidi_midi_port {
	// in_lock protects the input substream.
	struct mutex                      in_lock;
	// out_lock protects the output substream.
	struct mutex                      out_lock;
	DECLARE_KFIFO(in_fifo, uint8_t, PIMIDI_IN_FIFO_SIZE);
	unsigned int                      last_output_at;
	unsigned int                      output_buffer_used_in_millibytes;
	struct work_struct                in_handler;
	struct delayed_work               out_handler;
	unsigned long                     enabled_streams;
	unsigned int                      tx_cnt;
	unsigned int                      rx_cnt;
};

struct pimidi_instance {
	struct list_head                  list;
	struct i2c_client                 *i2c_client;
	struct pimidi_version_t           version;
	char                              serial[11];
	char                              d;
	struct gpio_desc                  *data_ready_gpio;

	struct work_struct                drdy_handler;

	// comm_lock serializes I2C communication.
	struct mutex                      comm_lock;
	char                              *rx_buf;
	size_t                            rx_len;
	int                               rx_status;
	struct completion                 *rx_completion;

	struct snd_rawmidi                *rawmidi;
	struct pimidi_midi_port           midi_port[PIMIDI_PORTS];
	bool                              stopping;
};

static struct snd_rawmidi_substream *pimidi_find_substream(struct snd_rawmidi *rawmidi,
							   int stream,
							   int number
	)
{
	struct snd_rawmidi_substream *substream;

	list_for_each_entry(substream, &rawmidi->streams[stream].substreams, list) {
		if (substream->number == number)
			return substream;
	}
	return NULL;
}

static void pimidi_midi_in_handler(struct pimidi_instance *instance, int port)
{
	int i, n, err;

	printd(instance, "(%d)", port);

	struct pimidi_midi_port *midi_port = &instance->midi_port[port];

	if (!test_bit(SNDRV_RAWMIDI_STREAM_INPUT, &midi_port->enabled_streams)) {
		printd(instance, "Input not enabled for %d", port);
		return;
	}

	u8 data[512];

	n = kfifo_out_peek(&midi_port->in_fifo, data, sizeof(data));
	printd(instance, "Peeked %d MIDI bytes", n);

	mutex_lock(&midi_port->in_lock);
	struct snd_rawmidi_substream *substream =
		pimidi_find_substream(instance->rawmidi,
				      SNDRV_RAWMIDI_STREAM_INPUT,
				      port);

	err = snd_rawmidi_receive(substream, data, n);
	if (err > 0)
		midi_port->rx_cnt += err;
	mutex_unlock(&midi_port->in_lock);

	for (i = 0; i < err; ++i)
		kfifo_skip(&midi_port->in_fifo);

	if (n != err)
		printw_rl(instance,
			  "Not all MIDI data consumed for port %d: %d / %d", port, err, n);

	if (!kfifo_is_empty(&midi_port->in_fifo) && !instance->stopping)
		queue_work(pimidi_global.work_queue, &midi_port->in_handler);

	printd(instance, "Done");
}

static void pimidi_midi_in_handler_0(struct work_struct *work)
{
	pimidi_midi_in_handler(container_of(work, struct pimidi_instance, midi_port[0].in_handler),
			       0);
}

static void pimidi_midi_in_handler_1(struct work_struct *work)
{
	pimidi_midi_in_handler(container_of(work, struct pimidi_instance, midi_port[1].in_handler),
			       1);
}

static void pimidi_midi_out_handler(struct pimidi_instance *instance, int port)
{
	printd(instance, "(%d)", port);
	if (!test_bit(SNDRV_RAWMIDI_STREAM_OUTPUT, &instance->midi_port[port].enabled_streams)) {
		printd(instance, "Output not enabled for %d", port);
		return;
	}

	struct pimidi_midi_port *midi_port = &instance->midi_port[port];

	struct snd_rawmidi_substream *substream =
		pimidi_find_substream(instance->rawmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, port);

	mutex_lock(&midi_port->out_lock);

	enum { MIDI_MILLI_BYTES_PER_JIFFY = 3125000 / HZ };
	enum { MIDI_MAX_OUTPUT_BUFFER_SIZE_IN_MILLIBYTES =
		(512 - PIMIDI_MAX_PACKET_SIZE - 1) * 1000 };

	unsigned int now = jiffies;
	unsigned int millibytes_became_available =
		(MIDI_MILLI_BYTES_PER_JIFFY) * (now - midi_port->last_output_at);

	midi_port->output_buffer_used_in_millibytes =
		midi_port->output_buffer_used_in_millibytes <=
		millibytes_became_available ? 0 : midi_port->output_buffer_used_in_millibytes -
		millibytes_became_available;

	unsigned int output_buffer_available =
		(MIDI_MAX_OUTPUT_BUFFER_SIZE_IN_MILLIBYTES
		- midi_port->output_buffer_used_in_millibytes)
		/ 1000;

	u8 buffer[PIMIDI_MAX_PACKET_SIZE];
	int n, batch, err;

	for (batch = 0; batch < 3; ++batch) {
		if (output_buffer_available == 0)
			printd(instance, "Buffer full");

		printd(instance, "Buffer available: %u (%u +%u, %u -> %u, dt %u) (%u) @ %u",
		       output_buffer_available, midi_port->output_buffer_used_in_millibytes,
		       millibytes_became_available, midi_port->last_output_at, now,
		       now - midi_port->last_output_at, midi_port->tx_cnt, HZ);
		midi_port->last_output_at = now;

		n = output_buffer_available
			? snd_rawmidi_transmit_peek(substream, buffer + 1,
						    min(output_buffer_available,
							sizeof(buffer) - 2))
			: 0;
		if (n > 0) {
			printd(instance, "Peeked: %d", n);
			snd_rawmidi_transmit_ack(substream, n);

			buffer[0] = (port << 4) | n;
			buffer[n + 1] = ~crc8(pimidi_crc8_table, buffer, n + 1, CRC8_INIT_VALUE);

#ifdef PIMIDI_DEBUG
			pr_debug("%s[%d]: Sending %d bytes:", __func__, instance->d, n + 2);
			int i;

			for (i = 0; i < n + 2; ++i)
				pr_cont(" %02x", buffer[i]);

			pr_cont("\n");
#endif
			mutex_lock(&instance->comm_lock);
			err = i2c_master_send(instance->i2c_client, buffer, n + 2);
			mutex_unlock(&instance->comm_lock);

			if (err < 0) {
				printe(instance,
				       "Error occurred when sending MIDI data over I2C! (%d)",
				       err);
				goto cleanup;
			}

			midi_port->tx_cnt += n;
			midi_port->output_buffer_used_in_millibytes += n * 1000;
			output_buffer_available -= n;
		} else if (n < 0) {
			err = n;
			printe(instance, "snd_rawmidi_transmit_peek returned error %d!", err);
			goto cleanup;
		} else {
			break;
		}
	}

	printd(instance, "Checking if empty %p", substream);
	if (!snd_rawmidi_transmit_empty(substream) && !instance->stopping) {
		unsigned int delay = 1;

		if (output_buffer_available == 0)
			delay = 125000 / MIDI_MILLI_BYTES_PER_JIFFY;
		printd(instance, "Queue more work after %u jiffies", delay);
		mod_delayed_work(pimidi_global.work_queue, &midi_port->out_handler, delay);
	}

cleanup:
	mutex_unlock(&midi_port->out_lock);
	printd(instance, "Done");
}

static void pimidi_midi_out_handler_0(struct work_struct *work)
{
	pimidi_midi_out_handler(container_of(work, struct pimidi_instance,
					     midi_port[0].out_handler.work), 0);
}

static void pimidi_midi_out_handler_1(struct work_struct *work)
{
	pimidi_midi_out_handler(container_of(work, struct pimidi_instance,
					     midi_port[1].out_handler.work), 1);
}

static void pimidi_midi_output_trigger(struct snd_rawmidi_substream *substream, int up)
{
	struct pimidi_instance *instance = substream->rmidi->private_data;

	printd(instance, "(%d, %d, %d)", substream->stream, substream->number, up);

	if (up == 0) {
		clear_bit(substream->stream,
			  &instance->midi_port[substream->number].enabled_streams);
	} else {
		set_bit(substream->stream,
			&instance->midi_port[substream->number].enabled_streams);
		if (!delayed_work_pending(&instance->midi_port[substream->number].out_handler)) {
			printd(instance, "Queueing work");
			queue_delayed_work(pimidi_global.work_queue,
					   &instance->midi_port[substream->number].out_handler, 0);
		}
	}
}

static void pimidi_midi_output_drain(struct snd_rawmidi_substream *substream)
{
	struct pimidi_instance *instance = substream->rmidi->private_data;

	printd(instance, "(%d, %d)", substream->stream, substream->number);

	printd(instance, "Begin draining!");

	queue_delayed_work(pimidi_global.work_queue,
			   &instance->midi_port[substream->number].out_handler, 0);

	unsigned long deadline = jiffies + 5 * HZ;

	do {
		printd(instance, "Before flush");
		while (delayed_work_pending(&instance->midi_port[substream->number].out_handler))
			flush_delayed_work(&instance->midi_port[substream->number].out_handler);
		printd(instance, "Flushed");
	} while (!snd_rawmidi_transmit_empty(substream) && time_before(jiffies, deadline));

	printd(instance, "Done!");
}

static int pimidi_midi_output_close(struct snd_rawmidi_substream *substream)
{
	struct pimidi_instance *instance = substream->rmidi->private_data;
	struct pimidi_midi_port *midi_port = &instance->midi_port[substream->number];

	mutex_lock(&midi_port->out_lock);
	clear_bit(substream->stream, &midi_port->enabled_streams);
	mutex_unlock(&midi_port->out_lock);
	return 0;
}

static int pimidi_midi_input_close(struct snd_rawmidi_substream *substream)
{
	struct pimidi_instance *instance = substream->rmidi->private_data;
	struct pimidi_midi_port *midi_port = &instance->midi_port[substream->number];

	mutex_lock(&midi_port->in_lock);
	clear_bit(substream->stream, &midi_port->enabled_streams);
	mutex_unlock(&midi_port->in_lock);
	return 0;
}

static void pimidi_midi_input_trigger(struct snd_rawmidi_substream *substream, int up)
{
	struct pimidi_instance *instance = substream->rmidi->private_data;

	printd(instance, "(%d, %d, %d)", substream->stream, substream->number, up);

	if (up == 0) {
		clear_bit(substream->stream,
			  &instance->midi_port[substream->number].enabled_streams);
		cancel_work_sync(&instance->midi_port[substream->number].in_handler);
	} else {
		set_bit(substream->stream,
			&instance->midi_port[substream->number].enabled_streams);
		if (!instance->stopping)
			queue_work(pimidi_global.work_queue,
				   &instance->midi_port[substream->number].in_handler);
	}
}

static void pimidi_get_port_info(struct snd_rawmidi *rmidi, int number,
				 struct snd_seq_port_info *seq_port_info)
{
	printd_g("%p, %d, %p", rmidi, number, seq_port_info);
	seq_port_info->type =
		SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC |
		SNDRV_SEQ_PORT_TYPE_HARDWARE |
		SNDRV_SEQ_PORT_TYPE_PORT;
	strscpy(seq_port_info->name, number == 0 ? "a" : "b",
		sizeof(seq_port_info->name));
	seq_port_info->midi_voices = 0;
}

static const struct snd_rawmidi_global_ops pimidi_midi_ops = {
	.get_port_info = pimidi_get_port_info,
};

static int pimidi_midi_open(struct snd_rawmidi_substream *substream)
{
	printd_g("(%p) stream=%d number=%d", substream, substream->stream, substream->number);
	return 0;
}

static const struct snd_rawmidi_ops pimidi_midi_output_ops = {
	.open = pimidi_midi_open,
	.close = pimidi_midi_output_close,
	.trigger = pimidi_midi_output_trigger,
	.drain = pimidi_midi_output_drain,
};

static const struct snd_rawmidi_ops pimidi_midi_input_ops = {
	.open = pimidi_midi_open,
	.close = pimidi_midi_input_close,
	.trigger = pimidi_midi_input_trigger,
};

static int pimidi_register(struct pimidi_instance *instance)
{
	int err = 0;

	mutex_lock(&pimidi_global.lock);
	printd(instance, "Registering...");
	if (!pimidi_global.reset_gpio) {
		printd_g("Getting reset pin.");
		pimidi_global.reset_gpio = gpiod_get(&instance->i2c_client->dev, "reset",
						     GPIOD_OUT_LOW);
		if (IS_ERR(pimidi_global.reset_gpio)) {
			err = PTR_ERR(pimidi_global.reset_gpio);
			printe_g("gpiod_get failed: %d", err);
			pimidi_global.reset_gpio = NULL;
			mutex_unlock(&pimidi_global.lock);
			return err;
		}
	}
	list_add_tail(&instance->list, &pimidi_global.devices);
	mutex_unlock(&pimidi_global.lock);
	return err;
}

static void pimidi_unregister(struct pimidi_instance *instance)
{
	mutex_lock(&pimidi_global.lock);
	printd(instance, "Unregistering...");
	list_del(&instance->list);
	if (list_empty(&pimidi_global.devices)) {
		printd_g("Releasing reset pin");
		gpiod_put(pimidi_global.reset_gpio);
		pimidi_global.reset_gpio = NULL;
	}
	mutex_unlock(&pimidi_global.lock);
}

static void pimidi_perform_reset(void)
{
	mutex_lock(&pimidi_global.lock);

	printd_g("Performing reset.");

	struct list_head *p;

	list_for_each(p, &pimidi_global.devices) {
		struct pimidi_instance *instance = list_entry(p, struct pimidi_instance, list);

		printd(instance, "Pausing...");
		instance->stopping = true;
		disable_irq(instance->i2c_client->irq);
		cancel_work(&instance->drdy_handler);

		int i;

		for (i = 0; i < PIMIDI_PORTS; ++i) {
			cancel_work(&instance->midi_port[i].in_handler);
			cancel_delayed_work(&instance->midi_port[i].out_handler);
		}

		drain_workqueue(pimidi_global.work_queue);
	}

	printd_g("Reset = low");
	gpiod_set_value(pimidi_global.reset_gpio, 1);

	list_for_each(p, &pimidi_global.devices) {
		struct pimidi_instance *instance = list_entry(p, struct pimidi_instance, list);

		if (gpiod_is_active_low(instance->data_ready_gpio))
			gpiod_toggle_active_low(instance->data_ready_gpio);
		gpiod_direction_output(instance->data_ready_gpio, 1);
		printd(instance, "DRDY high");
	}

	usleep_range(1000, 5000);
	printd_g("Reset = high");
	gpiod_set_value(pimidi_global.reset_gpio, 0);
	msleep(30);

	int i;

	for (i = 0; i < PIMIDI_MAX_DEVICES; ++i) {
		usleep_range(1000, 3000);
		list_for_each(p, &pimidi_global.devices) {
			struct pimidi_instance *instance = list_entry(p, struct pimidi_instance,
								      list);

			if (instance->d < i)
				continue;
			printd(instance, "DRDY -> %d", !gpiod_get_value(instance->data_ready_gpio));
			gpiod_set_value(instance->data_ready_gpio,
					!gpiod_get_value(instance->data_ready_gpio));
		}
	}
	usleep_range(16000, 20000);

	list_for_each(p, &pimidi_global.devices) {
		struct pimidi_instance *instance = list_entry(p, struct pimidi_instance, list);

		if (!gpiod_is_active_low(instance->data_ready_gpio))
			gpiod_toggle_active_low(instance->data_ready_gpio);

		printd(instance, "DRDY input");
		gpiod_direction_input(instance->data_ready_gpio);

		printd(instance, "Resume...");
		instance->stopping = false;
		enable_irq(instance->i2c_client->irq);
	}

	printd_g("Reset done.");
	usleep_range(16000, 20000);

	mutex_unlock(&pimidi_global.lock);
}

static int pimidi_read_version(struct pimidi_version_t *version, struct pimidi_instance *instance)
{
	memset(version, 0, sizeof(*version));

	const char cmd[4] = { 0xb2, 0x01, 0x01, 0x95 };

	char result[9];

	memset(result, 0, sizeof(result));

	DECLARE_COMPLETION_ONSTACK(done);

	mutex_lock(&instance->comm_lock);
	int err = i2c_master_send(instance->i2c_client, cmd, sizeof(cmd));

	if (err < 0) {
		mutex_unlock(&instance->comm_lock);
		return err;
	}
	instance->rx_buf = result;
	instance->rx_len = sizeof(result);
	instance->rx_completion = &done;
	mutex_unlock(&instance->comm_lock);

	printd(instance, "Waiting for drdy");
	wait_for_completion_io_timeout(&done, msecs_to_jiffies(1000u));
	printd(instance, "Done waiting");

	if (!completion_done(&done)) {
		mutex_lock(&instance->comm_lock);
		instance->rx_buf = NULL;
		instance->rx_len = 0;
		instance->rx_status = -ETIMEDOUT;
		instance->rx_completion = NULL;
		mutex_unlock(&instance->comm_lock);
		return -ETIMEDOUT;
	}

	if (CRC8_GOOD_VALUE(pimidi_crc8_table) != crc8(pimidi_crc8_table, result, sizeof(result),
						       CRC8_INIT_VALUE))
		return -EIO;

	const char expected[4] = { 0xb7, 0x81, 0x01, 0x00 };

	if (memcmp(result, expected, sizeof(expected)) != 0)
		return -EPROTO;

	u32 v = ntohl(*(uint32_t *)(result + 4));

	version->hwrev = v >> 24;
	version->major = (v & 0x00ff0000) >> 16;
	version->minor = (v & 0x0000ff00) >> 8;
	version->build = v & 0x000000ff;

	return 0;
}

static int pimidi_read_serial(char serial[11], struct pimidi_instance *instance)
{
	memset(serial, 0, sizeof(char[11]));

	const char cmd[4] = { 0xb2, 0x03, 0x04, 0x97 };

	char result[PIMIDI_MAX_PACKET_SIZE];

	memset(result, 0, sizeof(result));

	DECLARE_COMPLETION_ONSTACK(done);

	mutex_lock(&instance->comm_lock);
	int err = i2c_master_send(instance->i2c_client, cmd, sizeof(cmd));

	if (err < 0) {
		mutex_unlock(&instance->comm_lock);
		return err;
	}
	instance->rx_buf = result;
	instance->rx_len = sizeof(result);
	instance->rx_completion = &done;
	mutex_unlock(&instance->comm_lock);

	printd(instance, "Waiting for drdy");
	wait_for_completion_io_timeout(&done, msecs_to_jiffies(1000u));
	printd(instance, "Done waiting");

	if (!completion_done(&done)) {
		mutex_lock(&instance->comm_lock);
		instance->rx_buf = NULL;
		instance->rx_len = 0;
		instance->rx_status = -ETIMEDOUT;
		instance->rx_completion = NULL;
		mutex_unlock(&instance->comm_lock);
		printe(instance, "Timed out");
		return -ETIMEDOUT;
	}

	if (CRC8_GOOD_VALUE(pimidi_crc8_table) != crc8(pimidi_crc8_table, result,
						       (result[0] & 0x0f) + 2, CRC8_INIT_VALUE))
		return -EIO;

	const char expected[4] = { 0xbd, 0x83, 0x04, 0x0a };

	if (memcmp(result, expected, sizeof(expected)) != 0) {
		printe(instance, "Unexpected response: %02x %02x %02x %02x", result[0], result[1],
		       result[2], result[3]);
		return -EPROTO;
	}

	memcpy(serial, result + 4, 10);

	if (strspn(serial, "\xff") == 10)
		strscpy(serial, "(unset)", 8);

	return 0;
}

static void pimidi_handle_midi_data(struct pimidi_instance *instance, int port, const uint8_t *data,
				    unsigned int n)
{
	printd(instance, "Handling MIDI data for port %d (%u bytes)", port, n);
	if (n == 0)
		return;

	struct pimidi_midi_port *midi_port = &instance->midi_port[port];

	kfifo_in(&midi_port->in_fifo, data, n);

	if (!instance->stopping)
		queue_work(pimidi_global.work_queue, &midi_port->in_handler);

	printd(instance, "Done");
}

static void pimidi_drdy_continue(struct pimidi_instance *instance)
{
	if (instance->stopping) {
		printd(instance, "Refusing to queue work / enable IRQ due to stopping.");
		return;
	}

	if (gpiod_get_value(instance->data_ready_gpio)) {
		printd_rl(instance, "Queue work due to DRDY line still low");
		queue_work(pimidi_global.work_queue, &instance->drdy_handler);
	} else {
		printd_rl(instance, "Enabling irq for more data");
		enable_irq(gpiod_to_irq(instance->data_ready_gpio));
	}
}

static void pimidi_drdy_handler(struct work_struct *work)
{
	struct pimidi_instance *instance = container_of(work, struct pimidi_instance, drdy_handler);

	printd(instance, "(%p)", work);

	mutex_lock(&instance->comm_lock);
	if (!instance->rx_completion) {
		u8 data[PIMIDI_MAX_PACKET_SIZE];
		int n = i2c_master_recv(instance->i2c_client, data, 3);

		if (n < 0) {
			printe(instance, "Error reading from device: %d", n);
			mutex_unlock(&instance->comm_lock);
			pimidi_drdy_continue(instance);
			return;
		}

		if (data[0] == 0xfe) {
			printe_rl(instance, "Invalid packet 0x%02x 0x%02x 0x%02x", data[0], data[1],
				  data[2]);
			mutex_unlock(&instance->comm_lock);
			pimidi_drdy_continue(instance);
			return;
		}

		int len = (data[0] & 0x0f) + 2;

		if (len > n) {
			printd(instance, "Need %d more bytes", len - n);
			int err = i2c_master_recv(instance->i2c_client, data + n, len - n);

			if (err < 0) {
				printe(instance, "Error reading remainder from device: %d", err);
				mutex_unlock(&instance->comm_lock);
				pimidi_drdy_continue(instance);
				return;
#ifdef PIMIDI_DEBUG
			} else {
				pr_debug("Recv_2:");
				int i;

				for (i = n; i < len; ++i)
					pr_cont(" %02x", data[i]);
				pr_cont("\n");
#endif
			}
		}

		if (CRC8_GOOD_VALUE(pimidi_crc8_table) == crc8(pimidi_crc8_table, data, len,
							       CRC8_INIT_VALUE)) {
			switch (data[0] & 0xf0) {
			case 0x00:
				pimidi_handle_midi_data(instance, 0, data + 1, len - 2);
				break;
			case 0x10:
				pimidi_handle_midi_data(instance, 1, data + 1, len - 2);
				break;
			default:
				printd(instance, "Unhandled command %02x", data[0]);
				break;
			}
		} else {
			printe(instance, "I2C rx corruption detected.");
			pr_info("Packet [%d]:", len);
			int i;

			for (i = 0; i < len; ++i)
				pr_cont(" %02x", data[i]);
			pr_cont("\n");
		}

		mutex_unlock(&instance->comm_lock);
	} else {
		printd(instance, "Completing drdy");
		instance->rx_status = i2c_master_recv(instance->i2c_client, instance->rx_buf, 3);
		printd(instance, "Recv_1 %02x %02x %02x", instance->rx_buf[0], instance->rx_buf[1],
		       instance->rx_buf[2]);
		if (instance->rx_len > 3 && instance->rx_status == 3) {
			instance->rx_status = i2c_master_recv(instance->i2c_client,
							      instance->rx_buf + 3,
							      instance->rx_len - 3);
			if (instance->rx_status >= 0)
				instance->rx_status += 3;
#ifdef PIMIDI_DEBUG
			pr_debug("Recv_2:");
			int i;

			for (i = 3; i < instance->rx_len; ++i)
				pr_cont(" %02x", instance->rx_buf[i]);
			pr_cont("\n");
#endif
		}
		struct completion *done = instance->rx_completion;

		instance->rx_buf = NULL;
		instance->rx_len = 0;
		instance->rx_completion = NULL;
		complete_all(done);
		mutex_unlock(&instance->comm_lock);
	}

	pimidi_drdy_continue(instance);
}

static irqreturn_t pimidi_drdy_interrupt_handler(int irq, void *dev_id)
{
	struct pimidi_instance *instance = (struct pimidi_instance *)dev_id;

	if (instance->stopping) {
		printd(instance, "DRDY interrupt, but stopping, ignoring...");
		return IRQ_HANDLED;
	}

	printd(instance, "DRDY interrupt, masking");
	disable_irq_nosync(irq);

	printd(instance, "Queue work due to DRDY interrupt");
	queue_work(pimidi_global.work_queue, &instance->drdy_handler);

	return IRQ_HANDLED;
}

static void pimidi_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);
}

static void pimidi_proc_serial_show(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
{
	struct pimidi_instance *instance = entry->private_data;

	snd_iprintf(buffer, "%s\n", instance->serial);
}

static void pimidi_proc_version_show(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
{
	struct pimidi_instance *instance = entry->private_data;

	snd_iprintf(buffer, "%u.%u.%u\n", instance->version.major, instance->version.minor,
		    instance->version.build);
}

static void pimidi_proc_hwrev_show(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
{
	struct pimidi_instance *instance = entry->private_data;

	snd_iprintf(buffer, "%u\n", instance->version.hwrev);
}

static int pimidi_i2c_probe(struct i2c_client *client)
{
	struct snd_card *card = NULL;
	int err, d, i;

	d = client->addr - 0x20;

	if (d < 0 || d >= 8) {
		printe_g("Unexpected device address: %d", client->addr);
		err = -EINVAL;
		goto finalize;
	}

	err = snd_card_new(&client->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, THIS_MODULE,
			   sizeof(struct pimidi_instance), &card);

	if (err) {
		printe_g("snd_card_new failed: %d", err);
		return err;
	}

	struct pimidi_instance *instance = (struct pimidi_instance *)card->private_data;

	instance->i2c_client = client;
	instance->d = d;

	struct snd_rawmidi *rawmidi;

	err = snd_rawmidi_new(card, card->shortname, 0, 2, 2, &rawmidi);
	if (err < 0) {
		printe(instance, "snd_rawmidi_new failed: %d", err);
		goto finalize;
	}

	instance->rawmidi = rawmidi;
	strscpy(rawmidi->name, "pimidi", sizeof(rawmidi->name));

	rawmidi->info_flags =
		SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT | SNDRV_RAWMIDI_INFO_DUPLEX;
	rawmidi->private_data = instance;
	rawmidi->ops = &pimidi_midi_ops;

	snd_rawmidi_set_ops(rawmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &pimidi_midi_output_ops);
	snd_rawmidi_set_ops(rawmidi, SNDRV_RAWMIDI_STREAM_INPUT, &pimidi_midi_input_ops);

	instance->data_ready_gpio = devm_gpiod_get(&client->dev, "data-ready", GPIOD_OUT_HIGH);
	if (IS_ERR(instance->data_ready_gpio)) {
		err = PTR_ERR(instance->data_ready_gpio);
		printe(instance, "devm_gpiod_get failed: %d", err);
		goto finalize;
	}

	err = pimidi_register(instance);
	if (err < 0) {
		printe(instance, "pimidi_register failed: %d", err);
		goto finalize;
	}

	pimidi_perform_reset();

	INIT_WORK(&instance->drdy_handler, pimidi_drdy_handler);
	mutex_init(&instance->comm_lock);

	err = devm_request_irq(&client->dev, client->irq, pimidi_drdy_interrupt_handler,
			       IRQF_SHARED | IRQF_TRIGGER_LOW, "data_ready_int", instance);

	if (err != 0) {
		printe(instance, "data_available IRQ request failed! %d", err);
		goto finalize;
	}

	err = pimidi_read_version(&instance->version, instance);
	if (err < 0) {
		printe(instance, "pimidi_read_version failed: %d", err);
		goto finalize;
	}

	err = pimidi_read_serial(instance->serial, instance);
	if (err < 0) {
		printe(instance, "pimidi_read_serial failed: %d", err);
		goto finalize;
	} else if (instance->serial[0] != 'P' || instance->serial[1] != 'M' ||
		   strlen(instance->serial) != 10) {
		printe(instance, "Unexpected serial number: %s", instance->serial);
		err = -EIO;
		goto finalize;
	}

	printi(instance, "pimidi%d hw:%d version %u.%u.%u-%u, serial %s",
	       d,
	       card->number,
	       instance->version.major,
	       instance->version.minor,
	       instance->version.build,
	       instance->version.hwrev,
	       instance->serial
	       );

	strscpy(card->driver, "snd-pimidi", sizeof(card->driver));
	snprintf(card->shortname, sizeof(card->shortname), "pimidi%d", d);
	snprintf(card->longname, sizeof(card->longname), "pimidi%d %s", d, instance->serial);
	snprintf(card->id, sizeof(card->id), "pimidi%d", d);

	snprintf(pimidi_find_substream(rawmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, 0)->name,
		 10u, "pimidi%d-a", d);
	snprintf(pimidi_find_substream(rawmidi, SNDRV_RAWMIDI_STREAM_INPUT,  0)->name,
		 10u, "pimidi%d-a", d);
	snprintf(pimidi_find_substream(rawmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, 1)->name,
		 10u, "pimidi%d-b", d);
	snprintf(pimidi_find_substream(rawmidi, SNDRV_RAWMIDI_STREAM_INPUT,  1)->name,
		 10u, "pimidi%d-b", d);

	err = snd_card_ro_proc_new(card, "a-tx", &instance->midi_port[0].tx_cnt,
				   pimidi_proc_stat_show);
	err = snd_card_ro_proc_new(card, "a-rx", &instance->midi_port[0].rx_cnt,
				   pimidi_proc_stat_show);
	err = snd_card_ro_proc_new(card, "b-tx", &instance->midi_port[1].tx_cnt,
				   pimidi_proc_stat_show);
	err = snd_card_ro_proc_new(card, "b-rx", &instance->midi_port[1].rx_cnt,
				   pimidi_proc_stat_show);
	err = snd_card_ro_proc_new(card, "serial", instance, pimidi_proc_serial_show);
	err = snd_card_ro_proc_new(card, "version", instance, pimidi_proc_version_show);
	err = snd_card_ro_proc_new(card, "hwrev", instance, pimidi_proc_hwrev_show);
	if (err < 0) {
		printe(instance, "snd_card_ro_proc_new failed: %d", err);
		goto finalize;
	}

	err = snd_card_register(card);
	if (err < 0) {
		printe(instance, "snd_card_register failed: %d", err);
		goto finalize;
	}

finalize:
	if (err) {
		instance->stopping = true;
		cancel_work_sync(&instance->drdy_handler);
		mutex_destroy(&instance->comm_lock);
		pimidi_unregister(instance);
		snd_card_free(card);
		return err;
	}

	for (i = 0; i < PIMIDI_PORTS; ++i) {
		struct pimidi_midi_port *port = &instance->midi_port[i];

		mutex_init(&port->in_lock);
		mutex_init(&port->out_lock);
		INIT_WORK(&port->in_handler,
			  i == 0 ? pimidi_midi_in_handler_0 : pimidi_midi_in_handler_1);
		INIT_DELAYED_WORK(&port->out_handler,
				  i == 0 ? pimidi_midi_out_handler_0 : pimidi_midi_out_handler_1);
		INIT_KFIFO(port->in_fifo);
		port->last_output_at = jiffies;
	}

	i2c_set_clientdata(client, card);
	return 0;
}

static void pimidi_i2c_remove(struct i2c_client *client)
{
	printd_g("(%p)", client);

	int i;
	struct snd_card *card = i2c_get_clientdata(client);

	if (card) {
		printi_g("Unloading hw:%d %s", card->number, card->longname);
		struct pimidi_instance *instance = (struct pimidi_instance *)card->private_data;

		instance->stopping = true;
		i2c_set_clientdata(client, NULL);
		devm_free_irq(&client->dev, client->irq, instance);
		cancel_work_sync(&instance->drdy_handler);

		for (i = 0; i < PIMIDI_PORTS; ++i) {
			cancel_work_sync(&instance->midi_port[i].in_handler);
			cancel_delayed_work_sync(&instance->midi_port[i].out_handler);
			mutex_destroy(&instance->midi_port[i].out_lock);
			mutex_destroy(&instance->midi_port[i].in_lock);
			kfifo_free(&instance->midi_port[i].in_fifo);
		}

		mutex_destroy(&instance->comm_lock);
		pimidi_unregister(instance);
		snd_card_free(card);
	}
}

static const struct i2c_device_id pimidi_i2c_ids[] = {
	{ "pimidi", 0 },
	{}
};
MODULE_DEVICE_TABLE(i2c, pimidi_i2c_ids);

static const struct of_device_id pimidi_i2c_dt_ids[] = {
	{ .compatible = "blokaslabs,pimidi", },
	{}
};
MODULE_DEVICE_TABLE(of, pimidi_i2c_dt_ids);

static struct i2c_driver pimidi_i2c_driver = {
	.driver = {
		.name = "pimidi",
		.owner = THIS_MODULE,
		.of_match_table = of_match_ptr(pimidi_i2c_dt_ids),
	},
	.probe = pimidi_i2c_probe,
	.remove = pimidi_i2c_remove,
	.id_table = pimidi_i2c_ids,
};

static int pimidi_module_init(void)
{
	int err = 0;

	mutex_init(&pimidi_global.lock);

	INIT_LIST_HEAD(&pimidi_global.devices);

	pimidi_global.work_queue = create_singlethread_workqueue("pimidi");
	if (!pimidi_global.work_queue) {
		err = -ENOMEM;
		goto cleanup;
	}

	err = i2c_add_driver(&pimidi_i2c_driver);
	if (err < 0)
		goto cleanup;

	crc8_populate_msb(pimidi_crc8_table, PIMIDI_CRC8_POLYNOMIAL);

	return 0;

cleanup:
	mutex_destroy(&pimidi_global.lock);
	return err;
}

static void pimidi_module_exit(void)
{
	i2c_del_driver(&pimidi_i2c_driver);
	mutex_lock(&pimidi_global.lock);
	if (pimidi_global.reset_gpio) {
		gpiod_put(pimidi_global.reset_gpio);
		pimidi_global.reset_gpio = NULL;
	}
	mutex_unlock(&pimidi_global.lock);

	destroy_workqueue(pimidi_global.work_queue);
	pimidi_global.work_queue = NULL;

	mutex_destroy(&pimidi_global.lock);
}

module_init(pimidi_module_init);
module_exit(pimidi_module_exit);

MODULE_AUTHOR("Giedrius Trainavi\xc4\x8dius <giedrius@blokas.io>");
MODULE_DESCRIPTION("MIDI driver for Blokas Pimidi, https://blokas.io/");
MODULE_LICENSE("GPL");

/* vim: set ts=8 sw=8 noexpandtab: */
