mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH 0/4] ALSA: caiaq: Kore controller fixes and LCD support
@ 2026-09-27  0:35 Niko Huuskonen
  2026-09-27  0:35 ` [PATCH 1/4] ALSA: caiaq: Serialize access to the EP1 command buffer Niko Huuskonen
                   ` (4 more replies)
  0 siblings, 5 replies; 6+ messages in thread
From: Niko Huuskonen @ 2026-09-27  0:35 UTC (permalink / raw)
  To: Takashi Iwai, Jaroslav Kysela, Daniel Mack
  Cc: linux-sound, linux-kernel, Niko Huuskonen

The Native Instruments Kore controllers (USB 17cc:4711 and 17cc:4712)
work with snd-usb-caiaq, but a few things are wrong or missing:

- The EP1 command buffer is shared by all senders without locking.
  Patch 1 adds a mutex. It is needed by patch 4, which adds a sender.

- Parts of the Kore key map do not match the hardware: softkeys 5-8
  come in reverse order, and on the first Kore the knob touch sensors
  are scrambled. Patch 2 fixes the map.

- The 128x64 LCD is not supported, and userspace cannot reach it while
  the driver is bound. Patch 3 adds a hwdep interface ID, and patch 4
  exposes the LCD as a hwdep device that takes whole frames, plus an
  "LCD Contrast" control. The display protocol comes from USB captures
  of the vendor software that the OpenKoreBridge project published.

Patch 2 changes the key codes userspace sees for softkeys 5-8 on both
controllers, and for the touch sensors on the first Kore. Userspace
that compensates for the old map, such as the OpenKoreBridge controller
script, would have to follow. Userspace can read the map with
EVIOCGKEYCODE to support kernels with and without the change. If
changing the codes is a concern, patch 2 can be dropped; the rest of
the series does not depend on it.

Testing: the series was tested on a Kore controller (17cc:4711), applied
to v7.2.6 and built out of tree, with a userspace MIDI bridge that uses
the LCD, the LEDs and all controls. Every patch builds with W=1, and
checkpatch.pl --strict only asks whether MAINTAINERS needs updating for
the new file, which the existing sound/usb/caiaq/ entry already covers.
The locking problem in patch 1 was found by code review and has not
been reproduced. A Kore 2 was not available for testing.

Tool use: the patches were written with an AI coding assistant (Claude,
an LLM), as the Assisted-by tags say. It worked in an interactive
session in which I asked it to make the Kore controller usable as a MIDI
controller on Linux, to find out how its LCD is driven, to fix the
button mapping I reported from testing the hardware, and to prepare the
kernel changes for submission. It found the missing locking by code
review, derived the key map from my tests on the controller, checked the
LCD protocol on the device over raw USB, and tested each change on my
controller.

Niko Huuskonen (4):
  ALSA: caiaq: Serialize access to the EP1 command buffer
  ALSA: caiaq: Fix the Kore controller key map
  ALSA: uapi: Add hwdep interface ID for caiaq devices
  ALSA: caiaq: Add LCD support for the Kore controllers

 include/uapi/sound/asound.h |   3 +-
 sound/usb/caiaq/Makefile    |   2 +-
 sound/usb/caiaq/device.c    |  12 ++
 sound/usb/caiaq/device.h    |  13 ++
 sound/usb/caiaq/input.c     |  29 +++-
 sound/usb/caiaq/lcd.c       | 258 ++++++++++++++++++++++++++++++++++++
 sound/usb/caiaq/lcd.h       |   7 +
 7 files changed, 318 insertions(+), 6 deletions(-)
 create mode 100644 sound/usb/caiaq/lcd.c
 create mode 100644 sound/usb/caiaq/lcd.h


base-commit: 4d4bc656580bcc448f8e6d6b3b3a903120eb29bb
-- 
2.55.0


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH 1/4] ALSA: caiaq: Serialize access to the EP1 command buffer
  2026-09-27  0:35 [PATCH 0/4] ALSA: caiaq: Kore controller fixes and LCD support Niko Huuskonen
@ 2026-09-27  0:35 ` Niko Huuskonen
  2026-09-27  0:35 ` [PATCH 2/4] ALSA: caiaq: Fix the Kore controller key map Niko Huuskonen
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: Niko Huuskonen @ 2026-09-27  0:35 UTC (permalink / raw)
  To: Takashi Iwai, Jaroslav Kysela, Daniel Mack
  Cc: linux-sound, linux-kernel, Niko Huuskonen

snd_usb_caiaq_send_command() and snd_usb_caiaq_send_command_bank() copy
the command into cdev->ep1_out_buf and send it with a synchronous bulk
transfer. Nothing serializes their callers. An ALSA control write, which
sets the LEDs on the Kore controllers and several other devices, can run
at the same time as a PCM prepare, which sends the audio parameters
through the same buffer. One caller can then overwrite the buffer while
the transfer of the other is still in flight, and the device receives a
mix of both commands.

Protect the buffer with a mutex. All callers run in process context and
already sleep in usb_bulk_msg().

The problem was found by code review while adding another user of the
buffer, the Kore LCD support later in this series. It has not been
observed or reproduced.

Fixes: 8e3cd08ed8e5 ("[ALSA] caiaq - add control API and more input features")
Assisted-by: LLM
Signed-off-by: Niko Huuskonen <niko.huuskonen.00@gmail.com>
---
 sound/usb/caiaq/device.c | 5 +++++
 sound/usb/caiaq/device.h | 3 +++
 2 files changed, 8 insertions(+)

diff --git a/sound/usb/caiaq/device.c b/sound/usb/caiaq/device.c
index a16e59248480..3e63eecebe00 100644
--- a/sound/usb/caiaq/device.c
+++ b/sound/usb/caiaq/device.c
@@ -212,6 +212,8 @@ int snd_usb_caiaq_send_command(struct snd_usb_caiaqdev *cdev,
 	if (len > EP1_BUFSIZE - 1)
 		len = EP1_BUFSIZE - 1;
 
+	guard(mutex)(&cdev->ep1_out_mutex);
+
 	if (buffer && len > 0)
 		memcpy(cdev->ep1_out_buf+1, buffer, len);
 
@@ -235,6 +237,8 @@ int snd_usb_caiaq_send_command_bank(struct snd_usb_caiaqdev *cdev,
 	if (len > EP1_BUFSIZE - 2)
 		len = EP1_BUFSIZE - 2;
 
+	guard(mutex)(&cdev->ep1_out_mutex);
+
 	if (buffer && len > 0)
 		memcpy(cdev->ep1_out_buf+2, buffer, len);
 
@@ -439,6 +443,7 @@ static int create_card(struct usb_device *usb_dev,
 	cdev->chip.usb_id = USB_ID(le16_to_cpu(usb_dev->descriptor.idVendor),
 				  le16_to_cpu(usb_dev->descriptor.idProduct));
 	spin_lock_init(&cdev->spinlock);
+	mutex_init(&cdev->ep1_out_mutex);
 
 	*cardp = card;
 	return 0;
diff --git a/sound/usb/caiaq/device.h b/sound/usb/caiaq/device.h
index 743eb0387b5f..0354e348e919 100644
--- a/sound/usb/caiaq/device.h
+++ b/sound/usb/caiaq/device.h
@@ -2,6 +2,8 @@
 #ifndef CAIAQ_DEVICE_H
 #define CAIAQ_DEVICE_H
 
+#include <linux/mutex.h>
+
 #include "../usbaudio.h"
 
 #define USB_VID_NATIVEINSTRUMENTS 0x17cc
@@ -68,6 +70,7 @@ struct snd_usb_caiaqdev {
 
 	unsigned char ep1_in_buf[EP1_BUFSIZE];
 	unsigned char ep1_out_buf[EP1_BUFSIZE];
+	struct mutex ep1_out_mutex;	/* protects ep1_out_buf */
 	unsigned char midi_out_buf[EP1_BUFSIZE];
 
 	struct caiaq_device_spec spec;
-- 
2.55.0


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH 2/4] ALSA: caiaq: Fix the Kore controller key map
  2026-09-27  0:35 [PATCH 0/4] ALSA: caiaq: Kore controller fixes and LCD support Niko Huuskonen
  2026-09-27  0:35 ` [PATCH 1/4] ALSA: caiaq: Serialize access to the EP1 command buffer Niko Huuskonen
@ 2026-09-27  0:35 ` Niko Huuskonen
  2026-09-27  0:35 ` [PATCH 3/4] ALSA: uapi: Add hwdep interface ID for caiaq devices Niko Huuskonen
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: Niko Huuskonen @ 2026-09-27  0:35 UTC (permalink / raw)
  To: Takashi Iwai, Jaroslav Kysela, Daniel Mack
  Cc: linux-sound, linux-kernel, Niko Huuskonen

keycode_kore does not match the hardware in two places:

- Softkeys 5 to 8 are listed in reverse order, so pressing the fifth
  softkey reports BTN_8, the sixth BTN_7 and so on. Softkeys 1 to 4 are
  correct. The OpenKoreBridge project, which drives a Kore 2 through
  this driver, works around the same reversal in userspace.

- On the first Kore controller the touch sensors of the eight knobs are
  scrambled: touching knob 1 reports KEY_BRL_DOT6, knob 2 KEY_BRL_DOT8,
  knob 3 KEY_BRL_DOT2, knob 6 KEY_BRL_DOT7, knob 7 KEY_BRL_DOT1 and
  knob 8 KEY_BRL_DOT3. Only knobs 4 and 5 are right.

Put the softkeys in order for both controllers, and give the first Kore
controller its own touch sensor order, so that BTN_n and KEY_BRL_DOTn
belong to the n-th knob. The touch sensor order of the Kore 2 is left
alone, as it could not be checked.

Userspace that compensates for the old order needs to follow. It can
read the key map with EVIOCGKEYCODE, which also makes it possible to
support kernels with and without this change.

Tested on a Kore controller (USB ID 17cc:4711).

Link: https://github.com/OpenKoreBridge/OpenKoreBridge
Fixes: 8e3cd08ed8e5 ("[ALSA] caiaq - add control API and more input features")
Assisted-by: LLM
Signed-off-by: Niko Huuskonen <niko.huuskonen.00@gmail.com>
---
 sound/usb/caiaq/input.c | 29 +++++++++++++++++++++++++----
 1 file changed, 25 insertions(+), 4 deletions(-)

diff --git a/sound/usb/caiaq/input.c b/sound/usb/caiaq/input.c
index 8d924330c54c..856235c3398e 100644
--- a/sound/usb/caiaq/input.c
+++ b/sound/usb/caiaq/input.c
@@ -41,11 +41,11 @@ static const unsigned short keycode_kore[] = {
 	BTN_3,
 	BTN_2,
 	BTN_1,
-	BTN_8,
-	BTN_7,
-	BTN_6,
 	BTN_5,
-	KEY_BRL_DOT4,   /* touch sensitive knobs */
+	BTN_6,
+	BTN_7,
+	BTN_8,
+	KEY_BRL_DOT4,   /* touch sensitive knobs (Kore 2 order) */
 	KEY_BRL_DOT3,
 	KEY_BRL_DOT2,
 	KEY_BRL_DOT1,
@@ -55,6 +55,21 @@ static const unsigned short keycode_kore[] = {
 	KEY_BRL_DOT5
 };
 
+/* index of the first touch sensor in keycode_kore */
+#define KORE_TOUCH_KEYS	24
+
+/* the first Kore controller reports its touch sensors in this order */
+static const unsigned short keycode_kore1_touch[] = {
+	KEY_BRL_DOT4,
+	KEY_BRL_DOT8,
+	KEY_BRL_DOT3,
+	KEY_BRL_DOT7,
+	KEY_BRL_DOT2,
+	KEY_BRL_DOT6,
+	KEY_BRL_DOT1,
+	KEY_BRL_DOT5
+};
+
 #define MASCHINE_BUTTONS   (42)
 #define MASCHINE_BUTTON(X) ((X) + BTN_MISC)
 #define MASCHINE_PADS      (16)
@@ -666,6 +681,12 @@ int snd_usb_caiaq_input_init(struct snd_usb_caiaqdev *cdev)
 		input->absbit[BIT_WORD(ABS_MISC)] |= BIT_MASK(ABS_MISC);
 		BUILD_BUG_ON(sizeof(cdev->keycode) < sizeof(keycode_kore));
 		memcpy(cdev->keycode, keycode_kore, sizeof(keycode_kore));
+		BUILD_BUG_ON(KORE_TOUCH_KEYS + ARRAY_SIZE(keycode_kore1_touch) >
+			     ARRAY_SIZE(keycode_kore));
+		if (cdev->chip.usb_id ==
+		    USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER))
+			memcpy(cdev->keycode + KORE_TOUCH_KEYS, keycode_kore1_touch,
+			       sizeof(keycode_kore1_touch));
 		input->keycodemax = ARRAY_SIZE(keycode_kore);
 		input_set_abs_params(input, ABS_HAT0X, 0, 999, 0, 10);
 		input_set_abs_params(input, ABS_HAT0Y, 0, 999, 0, 10);
-- 
2.55.0


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH 3/4] ALSA: uapi: Add hwdep interface ID for caiaq devices
  2026-09-27  0:35 [PATCH 0/4] ALSA: caiaq: Kore controller fixes and LCD support Niko Huuskonen
  2026-09-27  0:35 ` [PATCH 1/4] ALSA: caiaq: Serialize access to the EP1 command buffer Niko Huuskonen
  2026-09-27  0:35 ` [PATCH 2/4] ALSA: caiaq: Fix the Kore controller key map Niko Huuskonen
@ 2026-09-27  0:35 ` Niko Huuskonen
  2026-09-27  0:35 ` [PATCH 4/4] ALSA: caiaq: Add LCD support for the Kore controllers Niko Huuskonen
  2026-09-28 16:12 ` [PATCH 0/4] ALSA: caiaq: Kore controller fixes and LCD support Takashi Iwai
  4 siblings, 0 replies; 6+ messages in thread
From: Niko Huuskonen @ 2026-09-27  0:35 UTC (permalink / raw)
  To: Takashi Iwai, Jaroslav Kysela, Daniel Mack
  Cc: linux-sound, linux-kernel, Niko Huuskonen

Add SNDRV_HWDEP_IFACE_CAIAQ for the hwdep device that snd-usb-caiaq
creates for the LCD of the Native Instruments Kore controllers, added
by the next patch.

Assisted-by: LLM
Signed-off-by: Niko Huuskonen <niko.huuskonen.00@gmail.com>
---
 include/uapi/sound/asound.h | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h
index c11da9656e38..767c96273245 100644
--- a/include/uapi/sound/asound.h
+++ b/include/uapi/sound/asound.h
@@ -99,9 +99,10 @@ enum {
 	SNDRV_HWDEP_IFACE_LINE6,	/* Line6 USB processors */
 	SNDRV_HWDEP_IFACE_FW_MOTU,	/* MOTU FireWire series */
 	SNDRV_HWDEP_IFACE_FW_FIREFACE,	/* RME Fireface series */
+	SNDRV_HWDEP_IFACE_CAIAQ,	/* Native Instruments caiaq USB devices */
 
 	/* Don't forget to change the following: */
-	SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_FIREFACE
+	SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_CAIAQ
 };
 
 struct snd_hwdep_info {
-- 
2.55.0


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH 4/4] ALSA: caiaq: Add LCD support for the Kore controllers
  2026-09-27  0:35 [PATCH 0/4] ALSA: caiaq: Kore controller fixes and LCD support Niko Huuskonen
                   ` (2 preceding siblings ...)
  2026-09-27  0:35 ` [PATCH 3/4] ALSA: uapi: Add hwdep interface ID for caiaq devices Niko Huuskonen
@ 2026-09-27  0:35 ` Niko Huuskonen
  2026-09-28 16:12 ` [PATCH 0/4] ALSA: caiaq: Kore controller fixes and LCD support Takashi Iwai
  4 siblings, 0 replies; 6+ messages in thread
From: Niko Huuskonen @ 2026-09-27  0:35 UTC (permalink / raw)
  To: Takashi Iwai, Jaroslav Kysela, Daniel Mack
  Cc: linux-sound, linux-kernel, Niko Huuskonen

Both Kore controllers have a 128x64 pixel monochrome LCD. The driver
does not support it, and userspace cannot reach it while the driver is
bound, so the display stays blank on Linux.

The firmware passes EP1 packets with the command byte 0x08 on to an
ST7565-style display controller: "08 00 <n> <commands>" carries
controller commands, "08 01 <n> <data>" display RAM data. The vendor
software sets the controller up, then writes each 128 byte page in
blocks of 32 bytes, each preceded by page and column address commands.
The OpenKoreBridge project documented this from USB captures of the
vendor software with a Kore 2.

Add a hwdep device, "Kore LCD", for both controllers. A write carries
one frame of 1024 bytes: 8 pages of 128 columns, with bit 0 as the top
pixel of each page. The driver sets the controller up on the first
write and afterwards only sends the pages that changed. The device is
exclusive, so frames from different writers cannot interleave. Add an
"LCD Contrast" control (0-63); the backlight is already the "LED lcd"
control.

Tested on a Kore controller (USB ID 17cc:4711): full frames, frames that
change single pages and contrast changes show up as expected, while
audio, MIDI, input and the LEDs keep working. A Kore 2 was not available
for testing; it gets the protocol that OpenKoreBridge uses with it.

Link: https://github.com/OpenKoreBridge/OpenKoreBridge
Assisted-by: LLM
Signed-off-by: Niko Huuskonen <niko.huuskonen.00@gmail.com>
---
 sound/usb/caiaq/Makefile |   2 +-
 sound/usb/caiaq/device.c |   7 ++
 sound/usb/caiaq/device.h |  10 ++
 sound/usb/caiaq/lcd.c    | 258 +++++++++++++++++++++++++++++++++++++++
 sound/usb/caiaq/lcd.h    |   7 ++
 5 files changed, 283 insertions(+), 1 deletion(-)
 create mode 100644 sound/usb/caiaq/lcd.c
 create mode 100644 sound/usb/caiaq/lcd.h

diff --git a/sound/usb/caiaq/Makefile b/sound/usb/caiaq/Makefile
index 9a99c17a2c1b..ffa5a7edf95c 100644
--- a/sound/usb/caiaq/Makefile
+++ b/sound/usb/caiaq/Makefile
@@ -1,5 +1,5 @@
 # SPDX-License-Identifier: GPL-2.0-only
-snd-usb-caiaq-y := device.o audio.o midi.o control.o
+snd-usb-caiaq-y := device.o audio.o midi.o control.o lcd.o
 snd-usb-caiaq-$(CONFIG_SND_USB_CAIAQ_INPUT) += input.o
 
 obj-$(CONFIG_SND_USB_CAIAQ) += snd-usb-caiaq.o
diff --git a/sound/usb/caiaq/device.c b/sound/usb/caiaq/device.c
index 3e63eecebe00..dfbfd30b418d 100644
--- a/sound/usb/caiaq/device.c
+++ b/sound/usb/caiaq/device.c
@@ -22,6 +22,7 @@
 #include "midi.h"
 #include "control.h"
 #include "input.h"
+#include "lcd.h"
 
 MODULE_AUTHOR("Daniel Mack <daniel@caiaq.de>");
 MODULE_DESCRIPTION("caiaq USB audio");
@@ -387,6 +388,12 @@ static int setup_card(struct snd_usb_caiaqdev *cdev)
 	}
 #endif
 
+	ret = snd_usb_caiaq_lcd_init(cdev);
+	if (ret < 0) {
+		dev_err(dev, "Unable to set up LCD (ret=%d)\n", ret);
+		return ret;
+	}
+
 	/* finally, register the card and all its sub-instances */
 	ret = snd_card_register(cdev->chip.card);
 	if (ret < 0) {
diff --git a/sound/usb/caiaq/device.h b/sound/usb/caiaq/device.h
index 0354e348e919..e888a53562bf 100644
--- a/sound/usb/caiaq/device.h
+++ b/sound/usb/caiaq/device.h
@@ -30,6 +30,8 @@
 
 #define MODNAME "snd-usb-caiaq"
 
+#define CAIAQ_LCD_FRAME_SIZE	1024
+
 #define EP1_CMD_GET_DEVICE_INFO	0x1
 #define EP1_CMD_READ_ERP	0x2
 #define EP1_CMD_READ_ANALOG	0x3
@@ -37,6 +39,7 @@
 #define EP1_CMD_WRITE_IO	0x5
 #define EP1_CMD_MIDI_READ	0x6
 #define EP1_CMD_MIDI_WRITE	0x7
+#define EP1_CMD_LCD		0x8
 #define EP1_CMD_AUDIO_PARAMS	0x9
 #define EP1_CMD_AUTO_MSG	0xb
 #define EP1_CMD_DIMM_LEDS       0xc
@@ -101,6 +104,13 @@ struct snd_usb_caiaqdev {
 	unsigned char control_state[256];
 	unsigned char ep8_out_buf[2];
 
+	/* Kore LCD */
+	struct mutex lcd_mutex;		/* protects the lcd_* fields */
+	unsigned char lcd_frame[CAIAQ_LCD_FRAME_SIZE];	/* shown frame */
+	unsigned char lcd_contrast;
+	bool lcd_ready;			/* controller set up */
+	bool lcd_frame_valid;		/* lcd_frame matches the display */
+
 	/* Linux input */
 #ifdef CONFIG_SND_USB_CAIAQ_INPUT
 	struct input_dev *input_dev;
diff --git a/sound/usb/caiaq/lcd.c b/sound/usb/caiaq/lcd.c
new file mode 100644
index 000000000000..dd2a3b949a43
--- /dev/null
+++ b/sound/usb/caiaq/lcd.c
@@ -0,0 +1,258 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * LCD support for the Native Instruments Kore controllers
+ *
+ * Copyright (c) 2026 Niko Huuskonen <niko.huuskonen.00@gmail.com>
+ *
+ * Both Kore controllers have a 128x64 pixel monochrome LCD behind an
+ * ST7565-style controller. The firmware passes EP1 packets with the
+ * command byte EP1_CMD_LCD on to that controller:
+ *
+ *   EP1_CMD_LCD 0x00 <n> <n controller command bytes>
+ *   EP1_CMD_LCD 0x01 <n> <n display RAM bytes>
+ *
+ * The display is exposed as an exclusive hwdep device. Each write carries
+ * a whole frame of CAIAQ_LCD_FRAME_SIZE bytes: 8 pages of 128 columns,
+ * one byte per column and page, with bit 0 as the top pixel of the page.
+ * Only the pages that changed since the previous frame are sent to the
+ * device. The contrast is an ALSA control; the backlight is the existing
+ * "LED lcd" control.
+ *
+ * The controller setup and the packet layout follow the USB traffic of
+ * the vendor software, as documented by the OpenKoreBridge project.
+ */
+
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/usb.h>
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/hwdep.h>
+#include <sound/pcm.h>
+
+#include "device.h"
+#include "lcd.h"
+
+#define LCD_WIDTH		128
+#define LCD_PAGES		(CAIAQ_LCD_FRAME_SIZE / LCD_WIDTH)
+#define LCD_COLUMN_OFFSET	4	/* first visible controller column */
+#define LCD_CHUNK		32	/* display RAM bytes per packet */
+#define LCD_CONTRAST_MAX	0x3f
+#define LCD_CONTRAST_DEFAULT	0x1c
+
+#define LCD_KIND_COMMAND	0x00
+#define LCD_KIND_DATA		0x01
+
+static int lcd_send(struct snd_usb_caiaqdev *cdev, u8 kind,
+		    const u8 *bytes, unsigned int len)
+{
+	u8 buf[2 + LCD_CHUNK];
+
+	if (WARN_ON(len > LCD_CHUNK))
+		return -EINVAL;
+
+	buf[0] = kind;
+	buf[1] = len;
+	memcpy(buf + 2, bytes, len);
+	return snd_usb_caiaq_send_command(cdev, EP1_CMD_LCD, buf, len + 2);
+}
+
+static int lcd_command(struct snd_usb_caiaqdev *cdev, u8 cmd)
+{
+	return lcd_send(cdev, LCD_KIND_COMMAND, &cmd, 1);
+}
+
+static int lcd_set_contrast(struct snd_usb_caiaqdev *cdev)
+{
+	const u8 cmd[] = { 0x81, cdev->lcd_contrast };
+
+	return lcd_send(cdev, LCD_KIND_COMMAND, cmd, sizeof(cmd));
+}
+
+/* controller setup, in the order used by the vendor software */
+static int lcd_setup(struct snd_usb_caiaqdev *cdev)
+{
+	static const u8 head[] = {
+		0xe2,			/* reset */
+		0xa1,			/* reverse column direction */
+		0xc8,			/* reverse row direction */
+		0xa2,			/* 1/9 bias */
+		0x2c, 0x2e, 0x2f,	/* power up in three steps */
+		0x27,			/* regulator resistor ratio */
+	};
+	static const u8 tail[] = {
+		0xa6,			/* normal, non-inverted display */
+		0x88, 0xef,		/* sent by the vendor software */
+		0xaf,			/* display on */
+	};
+	int i, ret;
+
+	for (i = 0; i < ARRAY_SIZE(head); i++) {
+		ret = lcd_command(cdev, head[i]);
+		if (ret)
+			return ret;
+	}
+
+	ret = lcd_set_contrast(cdev);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < ARRAY_SIZE(tail); i++) {
+		ret = lcd_command(cdev, tail[i]);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int lcd_write_page(struct snd_usb_caiaqdev *cdev, unsigned int page,
+			  const u8 *data)
+{
+	unsigned int col;
+	int ret;
+
+	for (col = 0; col < LCD_WIDTH; col += LCD_CHUNK) {
+		unsigned int addr = LCD_COLUMN_OFFSET + col;
+		const u8 column[] = { 0x10 | (addr >> 4), addr & 0x0f };
+
+		ret = lcd_command(cdev, 0xb0 | page);
+		if (!ret)
+			ret = lcd_send(cdev, LCD_KIND_COMMAND,
+				       column, sizeof(column));
+		if (!ret)
+			ret = lcd_send(cdev, LCD_KIND_DATA,
+				       data + col, LCD_CHUNK);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static long lcd_hwdep_write(struct snd_hwdep *hw, const char __user *buf,
+			    long count, loff_t *offset)
+{
+	struct snd_usb_caiaqdev *cdev = hw->private_data;
+	unsigned int page;
+	int ret;
+
+	if (count != CAIAQ_LCD_FRAME_SIZE)
+		return -EINVAL;
+
+	u8 *frame __free(kfree) = memdup_user(buf, count);
+	if (IS_ERR(frame))
+		return PTR_ERR(frame);
+
+	guard(mutex)(&cdev->lcd_mutex);
+
+	if (!cdev->lcd_ready) {
+		ret = lcd_setup(cdev);
+		if (ret)
+			return ret;
+		cdev->lcd_ready = true;
+		cdev->lcd_frame_valid = false;
+	}
+
+	for (page = 0; page < LCD_PAGES; page++) {
+		u8 *shown = cdev->lcd_frame + page * LCD_WIDTH;
+		const u8 *next = frame + page * LCD_WIDTH;
+
+		if (cdev->lcd_frame_valid && !memcmp(shown, next, LCD_WIDTH))
+			continue;
+
+		ret = lcd_write_page(cdev, page, next);
+		if (ret) {
+			cdev->lcd_frame_valid = false;
+			return ret;
+		}
+		memcpy(shown, next, LCD_WIDTH);
+	}
+	cdev->lcd_frame_valid = true;
+
+	return count;
+}
+
+static int lcd_contrast_info(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = LCD_CONTRAST_MAX;
+	return 0;
+}
+
+static int lcd_contrast_get(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_usb_caiaqdev *cdev = snd_kcontrol_chip(kcontrol);
+
+	guard(mutex)(&cdev->lcd_mutex);
+	ucontrol->value.integer.value[0] = cdev->lcd_contrast;
+	return 0;
+}
+
+static int lcd_contrast_put(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_usb_caiaqdev *cdev = snd_kcontrol_chip(kcontrol);
+	long val = ucontrol->value.integer.value[0];
+	int ret;
+
+	if (val < 0 || val > LCD_CONTRAST_MAX)
+		return -EINVAL;
+
+	guard(mutex)(&cdev->lcd_mutex);
+	if (val == cdev->lcd_contrast)
+		return 0;
+
+	cdev->lcd_contrast = val;
+	if (cdev->lcd_ready) {
+		ret = lcd_set_contrast(cdev);
+		if (ret)
+			return ret;
+	}
+
+	return 1;
+}
+
+static const struct snd_kcontrol_new lcd_contrast_control = {
+	.iface = SNDRV_CTL_ELEM_IFACE_HWDEP,
+	.name = "LCD Contrast",
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.info = lcd_contrast_info,
+	.get = lcd_contrast_get,
+	.put = lcd_contrast_put,
+};
+
+int snd_usb_caiaq_lcd_init(struct snd_usb_caiaqdev *cdev)
+{
+	struct snd_card *card = cdev->chip.card;
+	struct snd_hwdep *hw;
+	int ret;
+
+	switch (cdev->chip.usb_id) {
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER):
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2):
+		break;
+	default:
+		return 0;
+	}
+
+	mutex_init(&cdev->lcd_mutex);
+	cdev->lcd_contrast = LCD_CONTRAST_DEFAULT;
+
+	ret = snd_hwdep_new(card, "Kore LCD", 0, &hw);
+	if (ret < 0)
+		return ret;
+
+	strscpy(hw->name, "Kore LCD", sizeof(hw->name));
+	hw->iface = SNDRV_HWDEP_IFACE_CAIAQ;
+	hw->private_data = cdev;
+	hw->exclusive = 1;
+	hw->ops.write = lcd_hwdep_write;
+
+	return snd_ctl_add(card, snd_ctl_new1(&lcd_contrast_control, cdev));
+}
diff --git a/sound/usb/caiaq/lcd.h b/sound/usb/caiaq/lcd.h
new file mode 100644
index 000000000000..423464750278
--- /dev/null
+++ b/sound/usb/caiaq/lcd.h
@@ -0,0 +1,7 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef CAIAQ_LCD_H
+#define CAIAQ_LCD_H
+
+int snd_usb_caiaq_lcd_init(struct snd_usb_caiaqdev *cdev);
+
+#endif /* CAIAQ_LCD_H */
-- 
2.55.0


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH 0/4] ALSA: caiaq: Kore controller fixes and LCD support
  2026-09-27  0:35 [PATCH 0/4] ALSA: caiaq: Kore controller fixes and LCD support Niko Huuskonen
                   ` (3 preceding siblings ...)
  2026-09-27  0:35 ` [PATCH 4/4] ALSA: caiaq: Add LCD support for the Kore controllers Niko Huuskonen
@ 2026-09-28 16:12 ` Takashi Iwai
  4 siblings, 0 replies; 6+ messages in thread
From: Takashi Iwai @ 2026-09-28 16:12 UTC (permalink / raw)
  To: Niko Huuskonen
  Cc: Takashi Iwai, Jaroslav Kysela, Daniel Mack, linux-sound, linux-kernel

On Sun, 27 Sep 2026 02:35:28 +0200,
Niko Huuskonen wrote:
> 
> The Native Instruments Kore controllers (USB 17cc:4711 and 17cc:4712)
> work with snd-usb-caiaq, but a few things are wrong or missing:
> 
> - The EP1 command buffer is shared by all senders without locking.
>   Patch 1 adds a mutex. It is needed by patch 4, which adds a sender.
> 
> - Parts of the Kore key map do not match the hardware: softkeys 5-8
>   come in reverse order, and on the first Kore the knob touch sensors
>   are scrambled. Patch 2 fixes the map.
> 
> - The 128x64 LCD is not supported, and userspace cannot reach it while
>   the driver is bound. Patch 3 adds a hwdep interface ID, and patch 4
>   exposes the LCD as a hwdep device that takes whole frames, plus an
>   "LCD Contrast" control. The display protocol comes from USB captures
>   of the vendor software that the OpenKoreBridge project published.
> 
> Patch 2 changes the key codes userspace sees for softkeys 5-8 on both
> controllers, and for the touch sensors on the first Kore. Userspace
> that compensates for the old map, such as the OpenKoreBridge controller
> script, would have to follow. Userspace can read the map with
> EVIOCGKEYCODE to support kernels with and without the change. If
> changing the codes is a concern, patch 2 can be dropped; the rest of
> the series does not depend on it.
> 
> Testing: the series was tested on a Kore controller (17cc:4711), applied
> to v7.2.6 and built out of tree, with a userspace MIDI bridge that uses
> the LCD, the LEDs and all controls. Every patch builds with W=1, and
> checkpatch.pl --strict only asks whether MAINTAINERS needs updating for
> the new file, which the existing sound/usb/caiaq/ entry already covers.
> The locking problem in patch 1 was found by code review and has not
> been reproduced. A Kore 2 was not available for testing.
> 
> Tool use: the patches were written with an AI coding assistant (Claude,
> an LLM), as the Assisted-by tags say. It worked in an interactive
> session in which I asked it to make the Kore controller usable as a MIDI
> controller on Linux, to find out how its LCD is driven, to fix the
> button mapping I reported from testing the hardware, and to prepare the
> kernel changes for submission. It found the missing locking by code
> review, derived the key map from my tests on the controller, checked the
> LCD protocol on the device over raw USB, and tested each change on my
> controller.
> 
> Niko Huuskonen (4):
>   ALSA: caiaq: Serialize access to the EP1 command buffer
>   ALSA: caiaq: Fix the Kore controller key map
>   ALSA: uapi: Add hwdep interface ID for caiaq devices
>   ALSA: caiaq: Add LCD support for the Kore controllers

Applied all four patches to for-next branch.  Thanks.


Takashi

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2026-09-28 16:12 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-27  0:35 [PATCH 0/4] ALSA: caiaq: Kore controller fixes and LCD support Niko Huuskonen
2026-09-27  0:35 ` [PATCH 1/4] ALSA: caiaq: Serialize access to the EP1 command buffer Niko Huuskonen
2026-09-27  0:35 ` [PATCH 2/4] ALSA: caiaq: Fix the Kore controller key map Niko Huuskonen
2026-09-27  0:35 ` [PATCH 3/4] ALSA: uapi: Add hwdep interface ID for caiaq devices Niko Huuskonen
2026-09-27  0:35 ` [PATCH 4/4] ALSA: caiaq: Add LCD support for the Kore controllers Niko Huuskonen
2026-09-28 16:12 ` [PATCH 0/4] ALSA: caiaq: Kore controller fixes and LCD support Takashi Iwai

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®