Skip to content

Commit 8846739

Browse files
morbidrsabroonie
authored andcommitted
spi: add ch341a usb2spi driver
Add a driver for the QiHeng Electronics ch341a USB-to-SPI adapter. This driver is loosely based on the ch341a module from the flashrom project. Signed-off-by: Johannes Thumshirn <[email protected]> Link: https://patch.msgid.link/[email protected] Signed-off-by: Mark Brown <[email protected]>
1 parent 0f17a12 commit 8846739

File tree

3 files changed

+248
-0
lines changed

3 files changed

+248
-0
lines changed

drivers/spi/Kconfig

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,12 @@ config SPI_CADENCE_XSPI
277277
device with a Cadence XSPI controller and want to access the
278278
Flash as an MTD device.
279279

280+
config SPI_CH341
281+
tristate "CH341 USB2SPI adapter"
282+
depends on SPI_MASTER && USB
283+
help
284+
Enables the SPI controller on the CH341a USB to serial chip
285+
280286
config SPI_CLPS711X
281287
tristate "CLPS711X host SPI controller"
282288
depends on ARCH_CLPS711X || COMPILE_TEST

drivers/spi/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ obj-$(CONFIG_SPI_BUTTERFLY) += spi-butterfly.o
3939
obj-$(CONFIG_SPI_CADENCE) += spi-cadence.o
4040
obj-$(CONFIG_SPI_CADENCE_QUADSPI) += spi-cadence-quadspi.o
4141
obj-$(CONFIG_SPI_CADENCE_XSPI) += spi-cadence-xspi.o
42+
obj-$(CONFIG_SPI_CH341) += spi-ch341.o
4243
obj-$(CONFIG_SPI_CLPS711X) += spi-clps711x.o
4344
obj-$(CONFIG_SPI_COLDFIRE_QSPI) += spi-coldfire-qspi.o
4445
obj-$(CONFIG_SPI_CS42L43) += spi-cs42l43.o

drivers/spi/spi-ch341.c

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
//
3+
// QiHeng Electronics ch341a USB-to-SPI adapter driver
4+
//
5+
// Copyright (C) 2024 Johannes Thumshirn <[email protected]>
6+
//
7+
// Based on ch341a_spi.c from the flashrom project.
8+
9+
#include <linux/module.h>
10+
#include <linux/usb.h>
11+
#include <linux/spi/spi.h>
12+
13+
#define CH341_PACKET_LENGTH 32
14+
#define CH341_DEFAULT_TIMEOUT 1000
15+
16+
#define CH341A_CMD_UIO_STREAM 0xab
17+
18+
#define CH341A_CMD_UIO_STM_END 0x20
19+
#define CH341A_CMD_UIO_STM_DIR 0x40
20+
#define CH341A_CMD_UIO_STM_OUT 0x80
21+
22+
#define CH341A_CMD_I2C_STREAM 0xaa
23+
#define CH341A_CMD_I2C_STM_SET 0x60
24+
#define CH341A_CMD_I2C_STM_END 0x00
25+
26+
#define CH341A_CMD_SPI_STREAM 0xa8
27+
28+
#define CH341A_STM_I2C_100K 0x01
29+
30+
struct ch341_spi_dev {
31+
struct spi_controller *ctrl;
32+
struct usb_device *udev;
33+
unsigned int write_pipe;
34+
unsigned int read_pipe;
35+
int rx_len;
36+
void *rx_buf;
37+
u8 *tx_buf;
38+
struct urb *rx_urb;
39+
struct spi_device *spidev;
40+
};
41+
42+
static void ch341_set_cs(struct spi_device *spi, bool is_high)
43+
{
44+
struct ch341_spi_dev *ch341 =
45+
spi_controller_get_devdata(spi->controller);
46+
int err;
47+
48+
memset(ch341->tx_buf, 0, CH341_PACKET_LENGTH);
49+
ch341->tx_buf[0] = CH341A_CMD_UIO_STREAM;
50+
ch341->tx_buf[1] = CH341A_CMD_UIO_STM_OUT | (is_high ? 0x36 : 0x37);
51+
52+
if (is_high) {
53+
ch341->tx_buf[2] = CH341A_CMD_UIO_STM_DIR | 0x3f;
54+
ch341->tx_buf[3] = CH341A_CMD_UIO_STM_END;
55+
} else {
56+
ch341->tx_buf[2] = CH341A_CMD_UIO_STM_END;
57+
}
58+
59+
err = usb_bulk_msg(ch341->udev, ch341->write_pipe, ch341->tx_buf,
60+
(is_high ? 4 : 3), NULL, CH341_DEFAULT_TIMEOUT);
61+
if (err)
62+
dev_err(&spi->dev,
63+
"error sending USB message for setting CS (%d)\n", err);
64+
}
65+
66+
static int ch341_transfer_one(struct spi_controller *host,
67+
struct spi_device *spi,
68+
struct spi_transfer *trans)
69+
{
70+
struct ch341_spi_dev *ch341 =
71+
spi_controller_get_devdata(spi->controller);
72+
int len;
73+
int ret;
74+
75+
len = min(CH341_PACKET_LENGTH, trans->len + 1);
76+
77+
memset(ch341->tx_buf, 0, CH341_PACKET_LENGTH);
78+
79+
ch341->tx_buf[0] = CH341A_CMD_SPI_STREAM;
80+
81+
memcpy(ch341->tx_buf + 1, trans->tx_buf, len);
82+
83+
ret = usb_bulk_msg(ch341->udev, ch341->write_pipe, ch341->tx_buf, len,
84+
NULL, CH341_DEFAULT_TIMEOUT);
85+
if (ret)
86+
return ret;
87+
88+
return usb_bulk_msg(ch341->udev, ch341->read_pipe, trans->rx_buf,
89+
len - 1, NULL, CH341_DEFAULT_TIMEOUT);
90+
}
91+
92+
static void ch341_recv(struct urb *urb)
93+
{
94+
struct ch341_spi_dev *ch341 = urb->context;
95+
struct usb_device *udev = ch341->udev;
96+
97+
switch (urb->status) {
98+
case 0:
99+
/* success */
100+
break;
101+
case -ENOENT:
102+
case -ECONNRESET:
103+
case -EPIPE:
104+
case -ESHUTDOWN:
105+
dev_dbg(&udev->dev, "rx urb terminated with status: %d\n",
106+
urb->status);
107+
return;
108+
default:
109+
dev_dbg(&udev->dev, "rx urb error: %d\n", urb->status);
110+
break;
111+
}
112+
}
113+
114+
static int ch341_config_stream(struct ch341_spi_dev *ch341)
115+
{
116+
memset(ch341->tx_buf, 0, CH341_PACKET_LENGTH);
117+
ch341->tx_buf[0] = CH341A_CMD_I2C_STREAM;
118+
ch341->tx_buf[1] = CH341A_CMD_I2C_STM_SET | CH341A_STM_I2C_100K;
119+
ch341->tx_buf[2] = CH341A_CMD_I2C_STM_END;
120+
121+
return usb_bulk_msg(ch341->udev, ch341->write_pipe, ch341->tx_buf, 3,
122+
NULL, CH341_DEFAULT_TIMEOUT);
123+
}
124+
125+
static int ch341_enable_pins(struct ch341_spi_dev *ch341, bool enable)
126+
{
127+
memset(ch341->tx_buf, 0, CH341_PACKET_LENGTH);
128+
ch341->tx_buf[0] = CH341A_CMD_UIO_STREAM;
129+
ch341->tx_buf[1] = CH341A_CMD_UIO_STM_OUT | 0x37;
130+
ch341->tx_buf[2] = CH341A_CMD_UIO_STM_DIR | (enable ? 0x3f : 0x00);
131+
ch341->tx_buf[3] = CH341A_CMD_UIO_STM_END;
132+
133+
return usb_bulk_msg(ch341->udev, ch341->write_pipe, ch341->tx_buf, 4,
134+
NULL, CH341_DEFAULT_TIMEOUT);
135+
}
136+
137+
static struct spi_board_info chip = {
138+
.modalias = "spi-ch341a",
139+
};
140+
141+
static int ch341_probe(struct usb_interface *intf,
142+
const struct usb_device_id *id)
143+
{
144+
struct usb_device *udev = interface_to_usbdev(intf);
145+
struct usb_endpoint_descriptor *in, *out;
146+
struct ch341_spi_dev *ch341;
147+
struct spi_controller *ctrl;
148+
int ret;
149+
150+
ret = usb_find_common_endpoints(intf->cur_altsetting, &in, &out, NULL,
151+
NULL);
152+
if (ret)
153+
return ret;
154+
155+
ctrl = devm_spi_alloc_master(&udev->dev, sizeof(struct ch341_spi_dev));
156+
if (!ctrl)
157+
return -ENOMEM;
158+
159+
ch341 = spi_controller_get_devdata(ctrl);
160+
ch341->ctrl = ctrl;
161+
ch341->udev = udev;
162+
ch341->write_pipe = usb_sndbulkpipe(udev, usb_endpoint_num(out));
163+
ch341->read_pipe = usb_rcvbulkpipe(udev, usb_endpoint_num(in));
164+
165+
ch341->rx_len = usb_endpoint_maxp(in);
166+
ch341->rx_buf = devm_kzalloc(&udev->dev, ch341->rx_len, GFP_KERNEL);
167+
if (!ch341->rx_buf)
168+
return -ENOMEM;
169+
170+
ch341->rx_urb = usb_alloc_urb(0, GFP_KERNEL);
171+
if (!ch341->rx_urb)
172+
return -ENOMEM;
173+
174+
ch341->tx_buf =
175+
devm_kzalloc(&udev->dev, CH341_PACKET_LENGTH, GFP_KERNEL);
176+
if (!ch341->tx_buf)
177+
return -ENOMEM;
178+
179+
usb_fill_bulk_urb(ch341->rx_urb, udev, ch341->read_pipe, ch341->rx_buf,
180+
ch341->rx_len, ch341_recv, ch341);
181+
182+
ret = usb_submit_urb(ch341->rx_urb, GFP_KERNEL);
183+
if (ret) {
184+
usb_free_urb(ch341->rx_urb);
185+
return -ENOMEM;
186+
}
187+
188+
ctrl->bus_num = -1;
189+
ctrl->mode_bits = SPI_CPHA;
190+
ctrl->transfer_one = ch341_transfer_one;
191+
ctrl->set_cs = ch341_set_cs;
192+
ctrl->auto_runtime_pm = false;
193+
194+
usb_set_intfdata(intf, ch341);
195+
196+
ret = ch341_config_stream(ch341);
197+
if (ret)
198+
return ret;
199+
200+
ret = ch341_enable_pins(ch341, true);
201+
if (ret)
202+
return ret;
203+
204+
ret = spi_register_controller(ctrl);
205+
if (ret)
206+
return ret;
207+
208+
ch341->spidev = spi_new_device(ctrl, &chip);
209+
if (!ch341->spidev)
210+
return -ENOMEM;
211+
212+
return 0;
213+
}
214+
215+
static void ch341_disconnect(struct usb_interface *intf)
216+
{
217+
struct ch341_spi_dev *ch341 = usb_get_intfdata(intf);
218+
219+
spi_unregister_device(ch341->spidev);
220+
spi_unregister_controller(ch341->ctrl);
221+
ch341_enable_pins(ch341, false);
222+
usb_free_urb(ch341->rx_urb);
223+
}
224+
225+
static const struct usb_device_id ch341_id_table[] = {
226+
{ USB_DEVICE(0x1a86, 0x5512) },
227+
{ }
228+
};
229+
MODULE_DEVICE_TABLE(usb, ch341_id_table);
230+
231+
static struct usb_driver ch341a_usb_driver = {
232+
.name = "spi-ch341",
233+
.probe = ch341_probe,
234+
.disconnect = ch341_disconnect,
235+
.id_table = ch341_id_table,
236+
};
237+
module_usb_driver(ch341a_usb_driver);
238+
239+
MODULE_AUTHOR("Johannes Thumshirn <[email protected]>");
240+
MODULE_DESCRIPTION("QiHeng Electronics ch341 USB2SPI");
241+
MODULE_LICENSE("GPL v2");

0 commit comments

Comments
 (0)