Skip to content

Commit

Permalink
Convert the whole driver to an MFD driver
Browse files Browse the repository at this point in the history
This splits the driver into 4 modules: core, i2c, gpio and spi.

It's that version that may be upstreamed.
  • Loading branch information
bibimbop committed May 17, 2023
1 parent d1f59c4 commit bbdbc85
Show file tree
Hide file tree
Showing 9 changed files with 1,013 additions and 940 deletions.
13 changes: 7 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
PWD := $(shell pwd)
KVERSION := $(shell uname -r)
KERNEL_DIR ?= /lib/modules/$(KVERSION)/build
KDIR ?= /lib/modules/$(KVERSION)/build

obj-m := ch341-buses.o

ch341-buses-objs := ch341-core.o ch341-i2c.o ch341-gpio.o ch341-spi.o
obj-m += ch341-core.o
obj-m += i2c-ch341.o
obj-m += gpio-ch341.o
obj-m += spi-ch341.o

all:
make -C $(KERNEL_DIR) M=$(PWD) modules
make -C $(KDIR) M=$(PWD) modules
clean:
make -C $(KERNEL_DIR) M=$(PWD) clean
make -C $(KDIR) M=$(PWD) clean
50 changes: 26 additions & 24 deletions README.rst
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
WinChipHead (沁恒) CH341 linux driver for I2C / SPI and GPIO mode
WinChipHead (沁恒) CH341 linux drivers for I2C / SPI and GPIO mode
=================================================================

The CH341 is declined in several flavors, and may support one or more
Expand All @@ -12,12 +12,12 @@ They work in 3 different modes, with only one being presented
depending on the USB PID::

- 0x5523: UART mode, covered by the USB `ch341` serial driver
- 0x5512: SPI/I2C/GPIO mode, covered by this ch341_buses driver
- 0x5512: SPI/I2C/GPIO mode, covered by this set of drivers
- 0x5584: Parallel printer mode, covered by the USB `usblp` driver

From linux kernel 5.10 to 5.16, the 0x5512 PID was unfortunately also
claimed by the driver for the UART part, and will conflict with this
driver. Blacklisting that module or deleting it will solve that
claimed by the driver for the UART part, and will conflict with these
drivers. Blacklisting that module or deleting it will solve that
problem. In `/etc/modprobe.d/blacklist.conf`, add the following line
to prevent loading of the serial driver::

Expand All @@ -32,7 +32,7 @@ to configure the chip into printer mode; for that case, connect the
SCL and SDA lines directly together.

The various CH341 appear to be indistinguishable from the
software. For instance the ch341-buses driver will present a GPIO
software. For instance the gpio-ch341 driver will present a GPIO
interface for the CH341T although physical pins are not present, and
the device will accept GPIO commands.

Expand All @@ -46,8 +46,7 @@ everywhere, involving some soldering, is available there::

https://eevblog.com/forum/repair/ch341a-serial-memory-programmer-power-supply-fix/

The ch341-buses driver has been tested with a CH341A, CH341B and
CH341T.
These drivers have been tested with a CH341A, CH341B and CH341T.

Some sample code for the CH341 is available at the manufacturer
website::
Expand All @@ -59,28 +58,37 @@ including datasheets.

https://github.com/boseji/CH341-Store.git

This driver is based on, merges, and expands the following
This set of drivers is based on, merges, and expands the following
pre-existing works::

https://github.com/gschorcht/spi-ch341-usb.git
https://github.com/gschorcht/i2c-ch341-usb.git

Warning: try not to yank the USB device out if it's being used. The
linux subsystems gpio and spi may crash or leak resources. This is not
a problem with the driver, but the subsystems themselves.
a problem with the drivers, but the subsystems themselves.


Building the driver
-------------------
Building the drivers
--------------------

The driver will build for the active kernel::
The drivers will build for the active kernel::

$ make

This will create `ch341-buses.ko`, which can the be insmod'ed.
This will create four drivers: `ch341-core.ko`, `gpio-ch341.ko`,
`i2c-ch341.ko` and `spi-ch341.ko` which can then be insmod'ed, in that
order.

The driver has been tested with a linux kernel 5.11. It will also
build for a linux kernel 5.14.
This driver used to be in a single piece, called `ch341-buses.ko`.
However since the goal is to upstream it, it was not possible to keep
it as is.

It is possibly to override the target kernel by setting the KDIR
variable in the working environment, to point to a built kernel tree.

These drivers have been tested with a linux kernel 6.2, and should
still build for older kernels.

Setup
-----
Expand All @@ -96,7 +104,7 @@ might make a system unstable.

The following is more safe. As root, create a group, add the user to
the group and create a udev rule for that group that will bind to the
devices recognized by the driver::
devices recognized by the core driver::

$ groupadd ch341
$ adduser "$USER" ch341
Expand Down Expand Up @@ -148,12 +156,6 @@ i2c AT24 eeproms can be read but not programmed properly because the
at24 linux driver tries to write a byte at a time, and doesn't wait at
all (or enough) between writes. Data corruption on writes does occur.

The driver doesn't support detection of I2C device present on the
bus. Apparently when a device is not present at a given address, the
CH341 will return an extra byte of data, but the driver doesn't
support that. This may be addressed in a future patch.


The GPIOs
---------

Expand Down Expand Up @@ -295,8 +297,8 @@ If all the devices are deleted, the SPI driver will release the SPI
lines, which become available again for GPIO operations.


Developing the driver
---------------------
Developing the drivers
----------------------

This driver (and other USB drivers) can easily be developed and
tested in a VM, using QEMU and virtme (available in some distributions or at
Expand Down
117 changes: 42 additions & 75 deletions ch341-core.c
Original file line number Diff line number Diff line change
@@ -1,101 +1,69 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Driver for the CH341A, and CH341B USB to I2C/SPI/GPIO adapter
* Driver for the CH341T USB to I2C adapter
* Core driver for the CH341A, CH341B and CH341T in I2C/SPI/GPIO
* mode. There are cell drivers available for I2C and GPIO. SPI is not
* yet supported.
*
* Copyright 2021, Frank Zago
* Copyright 2022, Frank Zago
* Copyright (c) 2017 Gunar Schorcht ([email protected])
* Copyright (c) 2016 Tse Lun Bien
* Copyright (c) 2014 Marco Gittler
* Copyright (c) 2006-2007 Till Harbaum ([email protected])
*
* The full UART functionality is handled by the CH341 serial driver
*/

#include <linux/kernel.h>
#include <linux/module.h>

#include "ch341.h"
#include <linux/mfd/core.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/usb.h>

static void ch341_usb_free_device(struct ch341_device *dev)
{
ch341_spi_remove(dev);
ch341_gpio_remove(dev);
ch341_i2c_remove(dev);

usb_set_intfdata(dev->iface, NULL);
usb_put_dev(dev->usb_dev);

kfree(dev);
}
static const struct mfd_cell ch341_devs[] = {
{ .name = "ch341-gpio", },
{ .name = "ch341-i2c", },
};

static int ch341_usb_probe(struct usb_interface *iface,
const struct usb_device_id *usb_id)
{
struct usb_host_endpoint *endpoints;
struct ch341_device *dev;
int rc;

dev = kzalloc(sizeof(struct ch341_device), GFP_KERNEL);
if (!dev)
struct usb_endpoint_descriptor *bulk_out;
struct usb_endpoint_descriptor *bulk_in;
struct usb_endpoint_descriptor *intr_in;
struct ch341_ddata *ddata;
int ret;

ddata = devm_kzalloc(&iface->dev, sizeof(*ddata), GFP_KERNEL);
if (!ddata)
return -ENOMEM;

dev->usb_dev = usb_get_dev(interface_to_usbdev(iface));
dev->iface = iface;
mutex_init(&dev->usb_lock);
ddata->usb_dev = interface_to_usbdev(iface);
mutex_init(&ddata->usb_lock);

if (iface->cur_altsetting->desc.bNumEndpoints != 3) {
rc = -EIO;
goto free_dev;
ret = usb_find_common_endpoints(iface->cur_altsetting, &bulk_in,
&bulk_out, &intr_in, NULL);
if (ret) {
dev_err(&iface->dev, "Could not find all endpoints\n");
return -ENODEV;
}

endpoints = iface->cur_altsetting->endpoint;
if (!usb_endpoint_is_bulk_in(&endpoints[0].desc) ||
!usb_endpoint_is_bulk_out(&endpoints[1].desc) ||
!usb_endpoint_xfer_int(&endpoints[2].desc)) {
rc = -EIO;
goto free_dev;
}

dev->ep_in = endpoints[0].desc.bEndpointAddress;
dev->ep_out = endpoints[1].desc.bEndpointAddress;
dev->ep_intr = endpoints[2].desc.bEndpointAddress;
dev->ep_intr_interval = endpoints[2].desc.bInterval;

usb_set_intfdata(iface, dev);

rc = ch341_i2c_init(dev);
if (rc)
goto free_dev;

rc = ch341_gpio_init(dev);
if (rc)
goto rem_i2c;
ddata->ep_in = bulk_in->bEndpointAddress;
ddata->ep_out = bulk_out->bEndpointAddress;
ddata->ep_intr = intr_in->bEndpointAddress;
ddata->ep_intr_interval = intr_in->bInterval;

rc = ch341_spi_init(dev);
if (rc)
goto rem_gpio;
usb_set_intfdata(iface, ddata);

return 0;
ret = devm_mfd_add_devices(&iface->dev, PLATFORM_DEVID_AUTO, ch341_devs,
ARRAY_SIZE(ch341_devs), NULL, 0, NULL);
if (ret)
dev_err(&iface->dev, "Failed to add child devices\n");

rem_gpio:
ch341_gpio_remove(dev);

rem_i2c:
ch341_i2c_remove(dev);

free_dev:
usb_put_dev(dev->usb_dev);
kfree(dev);

return rc;
return ret;
}

static void ch341_usb_disconnect(struct usb_interface *usb_if)
{
struct ch341_device *dev = usb_get_intfdata(usb_if);

ch341_usb_free_device(dev);
}

static const struct usb_device_id ch341_usb_table[] = {
Expand All @@ -105,14 +73,13 @@ static const struct usb_device_id ch341_usb_table[] = {
MODULE_DEVICE_TABLE(usb, ch341_usb_table);

static struct usb_driver ch341_usb_driver = {
.name = "ch341-buses",
.name = "ch341-mfd",
.id_table = ch341_usb_table,
.probe = ch341_usb_probe,
.disconnect = ch341_usb_disconnect
.disconnect = ch341_usb_disconnect,
};

module_usb_driver(ch341_usb_driver);

MODULE_AUTHOR("Various");
MODULE_DESCRIPTION("ch341 USB to I2C/SPI/GPIO adapter");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Frank Zago <[email protected]>");
MODULE_DESCRIPTION("CH341 USB to I2C/SPI/GPIO adapter");
MODULE_LICENSE("GPL");
Loading

0 comments on commit bbdbc85

Please sign in to comment.