From 81c6b931d822acb5650023a7bde1f997ffd0cabe Mon Sep 17 00:00:00 2001 From: luckfox-eng29 Date: Wed, 21 Aug 2024 11:11:23 +0800 Subject: [PATCH] kernel compatible luckfox --- .../kernel/arch/arm/configs/rv1106-bt.config | 7 + sysdrv/source/kernel/drivers/kmpp | 2 +- sysdrv/source/kernel/drivers/of/Kconfig | 7 + sysdrv/source/kernel/drivers/of/Makefile | 1 + sysdrv/source/kernel/drivers/of/dtbocfg.c | 429 ++++ sysdrv/source/kernel/drivers/of/overlay.c | 4 +- .../kernel/drivers/pinctrl/pinctrl-rockchip.h | 2 + sysdrv/source/kernel/drivers/rockit-ko | 2 +- .../kernel/drivers/staging/fbtft/fbtft-core.c | 6 +- .../kernel/drivers/staging/fbtft/fbtft.h | 25 +- .../source/kernel/drivers/usb/serial/Kconfig | 9 + .../source/kernel/drivers/usb/serial/Makefile | 1 + .../source/kernel/drivers/usb/serial/ch343.c | 2000 +++++++++++++++++ .../source/kernel/drivers/usb/serial/ch343.h | 243 ++ .../dt-bindings/soc/rockchip,boot-mode.h | 2 + 15 files changed, 2723 insertions(+), 17 deletions(-) create mode 100644 sysdrv/source/kernel/arch/arm/configs/rv1106-bt.config create mode 100644 sysdrv/source/kernel/drivers/of/dtbocfg.c create mode 100755 sysdrv/source/kernel/drivers/usb/serial/ch343.c create mode 100755 sysdrv/source/kernel/drivers/usb/serial/ch343.h diff --git a/sysdrv/source/kernel/arch/arm/configs/rv1106-bt.config b/sysdrv/source/kernel/arch/arm/configs/rv1106-bt.config new file mode 100644 index 000000000..07e74c4bd --- /dev/null +++ b/sysdrv/source/kernel/arch/arm/configs/rv1106-bt.config @@ -0,0 +1,7 @@ +CONFIG_BT=y +CONFIG_BT_RFCOMM=y +CONFIG_BT_RFCOMM_TTY=y +CONFIG_BT_HCIUART=y +CONFIG_BT_HCIUART_H4=y +CONFIG_RFKILL=y +CONFIG_RFKILL_RK=y diff --git a/sysdrv/source/kernel/drivers/of/Kconfig b/sysdrv/source/kernel/drivers/of/Kconfig index c177b4208..b20cc8f1f 100644 --- a/sysdrv/source/kernel/drivers/of/Kconfig +++ b/sysdrv/source/kernel/drivers/of/Kconfig @@ -121,4 +121,11 @@ config OF_DMA_DEFAULT_COHERENT # arches should select this if DMA is coherent by default for OF devices bool +config OF_DTBO + bool "Device Tree DTBO" + select OF_DYNAMIC + select OF_FLATTREE + select OF_RESOLVE + help + Device Tree DTBO endif # OF diff --git a/sysdrv/source/kernel/drivers/of/Makefile b/sysdrv/source/kernel/drivers/of/Makefile index 6e1e5212f..8b4510e79 100644 --- a/sysdrv/source/kernel/drivers/of/Makefile +++ b/sysdrv/source/kernel/drivers/of/Makefile @@ -13,5 +13,6 @@ obj-$(CONFIG_OF_RESERVED_MEM) += of_reserved_mem.o obj-$(CONFIG_OF_RESOLVE) += resolver.o obj-$(CONFIG_OF_OVERLAY) += overlay.o obj-$(CONFIG_OF_NUMA) += of_numa.o +obj-$(CONFIG_OF_DTBO) += dtbocfg.o obj-$(CONFIG_OF_UNITTEST) += unittest-data/ diff --git a/sysdrv/source/kernel/drivers/of/dtbocfg.c b/sysdrv/source/kernel/drivers/of/dtbocfg.c new file mode 100644 index 000000000..027bdd1ca --- /dev/null +++ b/sysdrv/source/kernel/drivers/of/dtbocfg.c @@ -0,0 +1,429 @@ +/********************************************************************************* + * + * Copyright (C) 2016-2023 Ichiro Kawazome + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + ********************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "dtbocfg" +#define DRIVER_VERSION "0.1.0" + +/** + * Device Tree Overlay Item Structure + */ +struct dtbocfg_overlay_item { + struct config_item item; +#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 17, 0)) + struct device_node* node; +#endif + int id; + void* dtbo; + int dtbo_size; +}; + +/** + * dtbocfg_overlay_create() - Create Device Tree Overlay + * @overlay: Pointer to Device Tree Overlay Item + * return Success(0) or Error Status. + */ +static int dtbocfg_overlay_item_create(struct dtbocfg_overlay_item *overlay) +{ + int ret_val; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 17, 0)) + { + int ovcs_id = 0; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0)) + ret_val = of_overlay_fdt_apply(overlay->dtbo,overlay->dtbo_size, &ovcs_id, NULL); +#else + ret_val = of_overlay_fdt_apply(overlay->dtbo,overlay->dtbo_size, &ovcs_id); +#endif + if (ret_val != 0) { + pr_err("%s: Failed to apply overlay (ret_val=%d)\n", __func__, ret_val); + goto failed; + } + overlay->id = ovcs_id; + pr_debug("%s: apply OK(id=%d)\n", __func__, ovcs_id); + } +#else + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0)) + of_fdt_unflatten_tree(overlay->dtbo, NULL, &overlay->node); +#else + of_fdt_unflatten_tree(overlay->dtbo, &overlay->node); +#endif + if (overlay->node == NULL) { + pr_err("%s: failed to unflatten tree\n", __func__); + ret_val = -EINVAL; + goto failed; + } + pr_debug("%s: unflattened OK\n", __func__); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 15, 0)) + { + int ovcs_id = 0; + + ret_val = of_overlay_apply(overlay->node, &ovcs_id); + if (ret_val != 0) { + pr_err("%s: Failed to apply overlay (ret_val=%d)\n", __func__, ret_val); + goto failed; + } + overlay->id = ovcs_id; + pr_debug("%s: apply OK(id=%d)\n", __func__, ovcs_id); + } +#else + { + of_node_set_flag(overlay->node, OF_DETACHED); + + ret_val = of_resolve_phandles(overlay->node); + if (ret_val != 0) { + pr_err("%s: Failed to resolve tree\n", __func__); + goto failed; + } + pr_debug("%s: resolved OK\n", __func__); + + ret_val = of_overlay_create(overlay->node); + if (ret_val < 0) { + pr_err("%s: Failed to create overlay (ret_val=%d)\n", __func__, ret_val); + goto failed; + } + overlay->id = ret_val; + } +#endif + +#endif + pr_debug("%s: create OK\n", __func__); + return 0; + + failed: + return ret_val; +} + +/** + * dtbocfg_overlay_item_release() - Relase Device Tree Overlay + * @overlay: Pointer to Device Tree Overlay Item + * return none + */ +static void dtbocfg_overlay_item_release(struct dtbocfg_overlay_item *overlay) +{ + if (overlay->id >= 0) { +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 15, 0)) + of_overlay_remove(&overlay->id); +#else + of_overlay_destroy(overlay->id); +#endif + overlay->id = -1; + } +} + +/** + * container_of_dtbocfg_overlay_item() - Get Device Tree Overlay Item Pointer from Configuration Item + * @item: Pointer to Configuration Item + * return Pointer to Device Tree Overlay Item + */ +static inline struct dtbocfg_overlay_item* container_of_dtbocfg_overlay_item(struct config_item *item) +{ + return item ? container_of(item, struct dtbocfg_overlay_item, item) : NULL; +} + +/** + * dtbocfg_overlay_item_status_store() - Set Status Attibute + * @item: Pointer to Configuration Item + * @page: Pointer to Value Buffer + * @count: Size of Value Buffer Size + * return Stored Size or Error Status. + */ +static ssize_t dtbocfg_overlay_item_status_store(struct config_item *item, const char *buf, size_t count) +{ + struct dtbocfg_overlay_item *overlay = container_of_dtbocfg_overlay_item(item); + ssize_t status; + unsigned long value; + if (0 != (status = kstrtoul(buf, 10, &value))) { + goto failed; + } + if (value == 0) { + if (overlay->id >= 0) { + dtbocfg_overlay_item_release(overlay); + } + } else { + if (overlay->id < 0) { + dtbocfg_overlay_item_create(overlay); + } + } + return count; + failed: + return -EPERM; +} + +/** + * dtbocfg_overlay_item_status_show() - Show Status Attibute + * @item : Pointer to Configuration Item + * @page : Pointer to Value for Store + * return String Size or Error Status. + */ +static ssize_t dtbocfg_overlay_item_status_show(struct config_item *item, char *page) +{ + struct dtbocfg_overlay_item *overlay = container_of_dtbocfg_overlay_item(item); + return sprintf(page, "%d\n", overlay->id >= 0 ? 1 : 0); +} + +/** + * dtbocfg_overlay_item_dtbo_write() - Write Device Tree Blob to Configuration Item + * @item : Pointer to Configuration Item + * @page : Pointer to Value Buffer + * @count: Size of Value Buffer + * return Stored Size or Error Status. + */ +static ssize_t dtbocfg_overlay_item_dtbo_write(struct config_item *item, const void *buf, size_t count) +{ + struct dtbocfg_overlay_item *overlay = container_of_dtbocfg_overlay_item(item); + + if (overlay->dtbo_size > 0) { + if (overlay->id >= 0) { + return -EPERM; + } + kfree(overlay->dtbo); + overlay->dtbo = NULL; + overlay->dtbo_size = 0; + } + + overlay->dtbo = kmemdup(buf, count, GFP_KERNEL); + if (overlay->dtbo == NULL) { + overlay->dtbo_size = 0; + return -ENOMEM; + } else { + overlay->dtbo_size = count; + return count; + } +} + +/** + * dtbocfg_overlay_item_dtbo_read() - Read Device Tree Blob from Configuration Item + * @item : Pointer to Configuration Item + * @page : Pointer to Value for Store, or NULL to query the buffer size + * @size : Size of the supplied buffer + * return Read Size + */ +static ssize_t dtbocfg_overlay_item_dtbo_read(struct config_item *item, void *buf, size_t size) +{ + struct dtbocfg_overlay_item *overlay = container_of_dtbocfg_overlay_item(item); + + if (overlay->dtbo == NULL) + return 0; + + if (buf != NULL) + memcpy(buf, overlay->dtbo, overlay->dtbo_size); + + return overlay->dtbo_size; +} + +/** + * Device Tree Blob Overlay Attribute Structure + */ +CONFIGFS_BIN_ATTR(dtbocfg_overlay_item_, dtbo, NULL, 1024 * 1024); // 1MiB should be way more than enough +CONFIGFS_ATTR(dtbocfg_overlay_item_, status); + +static struct configfs_attribute *dtbocfg_overlay_attrs[] = { + &dtbocfg_overlay_item_attr_status, + NULL, +}; + +static struct configfs_bin_attribute *dtbocfg_overlay_bin_attrs[] = { + &dtbocfg_overlay_item_attr_dtbo, + NULL, +}; + +/** + * dtbocfg_overlay_release() - Release Device Tree Overlay Item + * @item : Pointer to Configuration Item + * Return None + */ +static void dtbocfg_overlay_release(struct config_item *item) +{ + struct dtbocfg_overlay_item *overlay = container_of_dtbocfg_overlay_item(item); + + pr_debug("%s\n", __func__); + + dtbocfg_overlay_item_release(overlay); + + if (overlay->dtbo) { + kfree(overlay->dtbo); + overlay->dtbo = NULL; + overlay->dtbo_size = 0; + } + + kfree(overlay); +} + +/** + * Device Tree Blob Overlay Item Structure + */ +static struct configfs_item_operations dtbocfg_overlay_item_ops = { + .release = dtbocfg_overlay_release, +}; + +static struct config_item_type dtbocfg_overlay_item_type = { + .ct_item_ops = &dtbocfg_overlay_item_ops, + .ct_attrs = dtbocfg_overlay_attrs, + .ct_bin_attrs = dtbocfg_overlay_bin_attrs, + .ct_owner = THIS_MODULE, +}; + +/** + * dtbocfg_overlay_group_make_item() - Make Device Tree Overlay Group Item + * @group: Pointer to Configuration Group + * @name : Pointer to Group Name + * Return Pointer to Device Tree Overlay Group Item + */ +static struct config_item *dtbocfg_overlay_group_make_item(struct config_group *group, const char *name) +{ + struct dtbocfg_overlay_item *overlay; + + pr_debug("%s\n", __func__); + + overlay = kzalloc(sizeof(*overlay), GFP_KERNEL); + + if (!overlay) + return ERR_PTR(-ENOMEM); + overlay->id = -1; + overlay->dtbo = NULL; + overlay->dtbo_size = 0; + + config_item_init_type_name(&overlay->item, name, &dtbocfg_overlay_item_type); + return &overlay->item; +} + +/** + * dtbocfg_overlay_group_drop_item() - Drop Device Tree Overlay Group Item + * @group: Pointer to Configuration Group + * @item : Pointer to Device Tree Overlay Group Item + */ +static void dtbocfg_overlay_group_drop_item(struct config_group *group, struct config_item *item) +{ + struct dtbocfg_overlay_item *overlay = container_of_dtbocfg_overlay_item(item); + + pr_debug("%s\n", __func__); + + config_item_put(&overlay->item); +} + +/** + * Device Tree Blob Overlay Sub Group Structures + */ +static struct configfs_group_operations dtbocfg_overlays_ops = { + .make_item = dtbocfg_overlay_group_make_item, + .drop_item = dtbocfg_overlay_group_drop_item, +}; + +static struct config_item_type dtbocfg_overlays_type = { + .ct_group_ops = &dtbocfg_overlays_ops, + .ct_owner = THIS_MODULE, +}; + +static struct config_group dtbocfg_overlay_group; + +/** + * Device Tree Blob Overlay Root Sub System Structures + */ +static struct configfs_group_operations dtbocfg_root_ops = { + /* empty - we don't allow anything to be created */ +}; + +static struct config_item_type dtbocfg_root_type = { + .ct_group_ops = &dtbocfg_root_ops, + .ct_owner = THIS_MODULE, +}; + +static struct configfs_subsystem dtbocfg_root_subsys = { + .su_group = { + .cg_item = { + .ci_namebuf = "device-tree", + .ci_type = &dtbocfg_root_type, + }, + }, + .su_mutex = __MUTEX_INITIALIZER(dtbocfg_root_subsys.su_mutex), +}; + +/** + * dtbocfg_module_init() + */ +static int __init dtbocfg_module_init(void) +{ + int retval = 0; + + pr_info(DRIVER_NAME ": " DRIVER_VERSION "\n"); + + config_group_init(&dtbocfg_root_subsys.su_group); + config_group_init_type_name(&dtbocfg_overlay_group, "overlays", &dtbocfg_overlays_type); + + retval = configfs_register_subsystem(&dtbocfg_root_subsys); + if (retval != 0) { + pr_err( "%s: couldn't register subsys\n", __func__); + goto register_subsystem_failed; + } + + retval = configfs_register_group(&dtbocfg_root_subsys.su_group, &dtbocfg_overlay_group); + if (retval != 0) { + pr_err( "%s: couldn't register group\n", __func__); + goto register_group_failed; + } + + pr_info(DRIVER_NAME ": OK\n"); + return 0; + + register_group_failed: + configfs_unregister_subsystem(&dtbocfg_root_subsys); + register_subsystem_failed: + return retval; +} + +/** + * dtbocfg_module_exit() + */ +static void __exit dtbocfg_module_exit(void) +{ + configfs_unregister_group(&dtbocfg_overlay_group); + configfs_unregister_subsystem(&dtbocfg_root_subsys); +} + +module_init(dtbocfg_module_init); +module_exit(dtbocfg_module_exit); + +MODULE_AUTHOR("ikwzm"); +MODULE_DESCRIPTION("Device Tree Overlay Configuration File System"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sysdrv/source/kernel/drivers/of/overlay.c b/sysdrv/source/kernel/drivers/of/overlay.c index c8a0c0e9d..43a77d720 100644 --- a/sysdrv/source/kernel/drivers/of/overlay.c +++ b/sysdrv/source/kernel/drivers/of/overlay.c @@ -170,7 +170,9 @@ static int overlay_notify(struct overlay_changeset *ovcs, ret = blocking_notifier_call_chain(&overlay_notify_chain, action, &nd); - if (notifier_to_errno(ret)) { + if (ret == NOTIFY_OK || ret == NOTIFY_STOP) + return 0; + if (ret) { ret = notifier_to_errno(ret); pr_err("overlay changeset %s notifier error %d, target: %pOF\n", of_overlay_action_name[action], ret, nd.target); diff --git a/sysdrv/source/kernel/drivers/pinctrl/pinctrl-rockchip.h b/sysdrv/source/kernel/drivers/pinctrl/pinctrl-rockchip.h index 80b5552fd..dbf558d0f 100644 --- a/sysdrv/source/kernel/drivers/pinctrl/pinctrl-rockchip.h +++ b/sysdrv/source/kernel/drivers/pinctrl/pinctrl-rockchip.h @@ -18,6 +18,8 @@ #ifndef _PINCTRL_ROCKCHIP_H #define _PINCTRL_ROCKCHIP_H +#include + #define RK_GPIO0_A0 0 #define RK_GPIO0_A1 1 #define RK_GPIO0_A2 2 diff --git a/sysdrv/source/kernel/drivers/staging/fbtft/fbtft-core.c b/sysdrv/source/kernel/drivers/staging/fbtft/fbtft-core.c index d0c8d85f3..2baffa436 100644 --- a/sysdrv/source/kernel/drivers/staging/fbtft/fbtft-core.c +++ b/sysdrv/source/kernel/drivers/staging/fbtft/fbtft-core.c @@ -292,9 +292,9 @@ static void fbtft_update_display(struct fbtft_par *par, unsigned int start_line, throughput = throughput ? (len * 1000) / throughput : 0; throughput = throughput * 1000 / 1024; - dev_info(par->info->device, - "Display update: %ld kB/s, fps=%ld\n", - throughput, fps); + // dev_info(par->info->device, + // "Display update: %ld kB/s, fps=%ld\n", + // throughput, fps); par->first_update_done = true; } } diff --git a/sysdrv/source/kernel/drivers/staging/fbtft/fbtft.h b/sysdrv/source/kernel/drivers/staging/fbtft/fbtft.h index 06afaa9d5..7f97aaef3 100644 --- a/sysdrv/source/kernel/drivers/staging/fbtft/fbtft.h +++ b/sysdrv/source/kernel/drivers/staging/fbtft/fbtft.h @@ -407,17 +407,20 @@ do { \ dev_info(dev, format, ##arg); \ } while (0) -#define fbtft_par_dbg(level, par, format, arg...) \ -do { \ - if (unlikely((par)->debug & (level))) \ - dev_info((par)->info->device, format, ##arg); \ -} while (0) +/*#define fbtft_par_dbg(level, par, format, arg...) \ +do { \ + if (unlikely((par)->debug & (level))) \ + dev_info((par)->info->device, format, ##arg); \ +} while (0) + +#define fbtft_par_dbg_hex(level, par, dev, type, buf, num, format, arg...) \ +do { \ + if (unlikely((par)->debug & (level))) \ + fbtft_dbg_hex(dev, sizeof(type), buf,\ + (num) * sizeof(type), format, ##arg); \ +} while (0)*/ -#define fbtft_par_dbg_hex(level, par, dev, type, buf, num, format, arg...) \ -do { \ - if (unlikely((par)->debug & (level))) \ - fbtft_dbg_hex(dev, sizeof(type), buf,\ - (num) * sizeof(type), format, ##arg); \ -} while (0) +#define fbtft_par_dbg(level, par, format, arg...) ; +#define fbtft_par_dbg_hex(level, par, dev, type, buf, num, format, arg...) ; #endif /* __LINUX_FBTFT_H */ diff --git a/sysdrv/source/kernel/drivers/usb/serial/Kconfig b/sysdrv/source/kernel/drivers/usb/serial/Kconfig index 169251ec8..bbac02a93 100644 --- a/sysdrv/source/kernel/drivers/usb/serial/Kconfig +++ b/sysdrv/source/kernel/drivers/usb/serial/Kconfig @@ -112,6 +112,15 @@ config USB_SERIAL_CH341 To compile this driver as a module, choose M here: the module will be called ch341. +config USB_SERIAL_CH343 + tristate "USB Winchiphead CH343 Single Port Serial Driver" + help + Say Y here if you want to use a Winchiphead CH343 single port + USB to serial adapter. + + To compile this driver as a module, choose M here: the + module will be called ch343. + config USB_SERIAL_WHITEHEAT tristate "USB ConnectTech WhiteHEAT Serial Driver" select USB_EZUSB_FX2 diff --git a/sysdrv/source/kernel/drivers/usb/serial/Makefile b/sysdrv/source/kernel/drivers/usb/serial/Makefile index 2d491e434..dfab84cd4 100644 --- a/sysdrv/source/kernel/drivers/usb/serial/Makefile +++ b/sysdrv/source/kernel/drivers/usb/serial/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_USB_SERIAL_AIRCABLE) += aircable.o obj-$(CONFIG_USB_SERIAL_ARK3116) += ark3116.o obj-$(CONFIG_USB_SERIAL_BELKIN) += belkin_sa.o obj-$(CONFIG_USB_SERIAL_CH341) += ch341.o +obj-$(CONFIG_USB_SERIAL_CH343) += ch343.o obj-$(CONFIG_USB_SERIAL_CP210X) += cp210x.o obj-$(CONFIG_USB_SERIAL_CYBERJACK) += cyberjack.o obj-$(CONFIG_USB_SERIAL_CYPRESS_M8) += cypress_m8.o diff --git a/sysdrv/source/kernel/drivers/usb/serial/ch343.c b/sysdrv/source/kernel/drivers/usb/serial/ch343.c new file mode 100755 index 000000000..546630210 --- /dev/null +++ b/sysdrv/source/kernel/drivers/usb/serial/ch343.c @@ -0,0 +1,2000 @@ +/* + * USB serial driver for USB to UART(s) chip ch342/ch343/ch344/ch347/ch9101/ch9102/ch9103/ch9104, etc. + * + * Copyright (C) 2022 Nanjing Qinheng Microelectronics Co., Ltd. + * Web: http://wch.cn + * Author: WCH + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * System required: + * Kernel version beyond 3.4.x + * Update Log: + * V1.0 - initial version + * V1.1 - added support of chip ch344, ch9101 and ch9103 + * V1.2 - added gpio support of chip ch344 + * V1.3 - added support of chip ch347 + * V1.4 - added support of chip ch9104 + * V1.5 - added gpio character device + * - added supports for kernel version beyond 5.14.x + * - removed the gpio ioctl commands + */ + +#define DEBUG +#define VERBOSE_DEBUG + +#undef DEBUG +#undef VERBOSE_DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0)) +#include +#endif + +#include "ch343.h" + +#define DRIVER_AUTHOR "WCH" +#define DRIVER_DESC "USB serial driver for ch342/ch343/ch344/ch347/ch9101/ch9102/ch9103/ch9104, etc." +#define VERSION_DESC "V1.5 On 2022.12" + +#define IOCTL_MAGIC 'W' +#define IOCTL_CMD_GETCHIPTYPE _IOR(IOCTL_MAGIC, 0x84, u16) +#define IOCTL_CMD_CTRLIN _IOWR(IOCTL_MAGIC, 0x90, u16) +#define IOCTL_CMD_CTRLOUT _IOW(IOCTL_MAGIC, 0x91, u16) +#define IOCTL_CMD_GICOUNT _IOR(IOCTL_MAGIC, 0x92, u16) + +static struct usb_driver ch343_driver; +static struct tty_driver *ch343_tty_driver; +static struct usb_interface *g_intf; + +static DEFINE_IDR(ch343_minors); +static DEFINE_MUTEX(ch343_minors_lock); + +static void ch343_tty_set_termios(struct tty_struct *tty, struct ktermios *termios_old); + +/* + * Look up an ch343 structure by minor. If found and not disconnected, increment + * its refcount and return it with its mutex held. + */ +static struct ch343 *ch343_get_by_minor(unsigned int minor) +{ + struct ch343 *ch343; + + mutex_lock(&ch343_minors_lock); + ch343 = idr_find(&ch343_minors, minor); + if (ch343) { + mutex_lock(&ch343->mutex); + if (ch343->disconnected) { + mutex_unlock(&ch343->mutex); + ch343 = NULL; + } else { + tty_port_get(&ch343->port); + mutex_unlock(&ch343->mutex); + } + } + mutex_unlock(&ch343_minors_lock); + return ch343; +} + +/* + * Try to find an available minor number and if found, associate it with 'ch343'. + */ +static int ch343_alloc_minor(struct ch343 *ch343) +{ + int minor; + + mutex_lock(&ch343_minors_lock); + minor = idr_alloc(&ch343_minors, ch343, 0, CH343_TTY_MINORS, GFP_KERNEL); + mutex_unlock(&ch343_minors_lock); + + return minor; +} + +/* Release the minor number associated with 'ch343'. */ +static void ch343_release_minor(struct ch343 *ch343) +{ + mutex_lock(&ch343_minors_lock); + idr_remove(&ch343_minors, ch343->minor); + mutex_unlock(&ch343_minors_lock); +} + +/* + * Functions for CH343 control messages. + */ +static int ch343_control_out(struct ch343 *ch343, u8 request, u16 value, u16 index) +{ + int retval; + + retval = usb_autopm_get_interface(ch343->control); + if (retval) + return retval; + + retval = usb_control_msg(ch343->dev, usb_sndctrlpipe(ch343->dev, 0), request, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, value, index, NULL, 0, DEFAULT_TIMEOUT); + + usb_autopm_put_interface(ch343->control); + + return retval; +} + +static int ch343_control_in(struct ch343 *ch343, u8 request, u16 value, u16 index, char *buf, unsigned bufsize) +{ + int retval; + + retval = usb_autopm_get_interface(ch343->control); + if (retval) + return retval; + + retval = + usb_control_msg(ch343->dev, usb_rcvctrlpipe(ch343->dev, 0), request, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, value, index, buf, bufsize, DEFAULT_TIMEOUT); + + usb_autopm_put_interface(ch343->control); + + return retval; +} + +static int ch343_control_msg_out(struct ch343 *ch343, u8 request, u8 requesttype, u16 value, u16 index, void *buf, + unsigned bufsize) +{ + int retval; + char *buffer; + + buffer = kmalloc(bufsize, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + retval = copy_from_user(buffer, (char __user *)buf, bufsize); + if (retval) + goto out; + + retval = usb_autopm_get_interface(ch343->control); + if (retval) + goto out; + + retval = usb_control_msg(ch343->dev, usb_sndctrlpipe(ch343->dev, 0), request, requesttype, value, index, buf, + bufsize, DEFAULT_TIMEOUT); + + usb_autopm_put_interface(ch343->control); + +out: + kfree(buffer); + return retval; +} + +static int ch343_control_msg_in(struct ch343 *ch343, u8 request, u8 requesttype, u16 value, u16 index, void *buf, + unsigned bufsize) +{ + int retval; + char *buffer; + + buffer = kmalloc(bufsize, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + retval = usb_autopm_get_interface(ch343->control); + if (retval) + goto out; + + retval = usb_control_msg(ch343->dev, usb_rcvctrlpipe(ch343->dev, 0), request, requesttype, value, index, buffer, + bufsize, DEFAULT_TIMEOUT); + if (retval > 0) { + if (copy_to_user((char __user *)buf, buffer, retval)) { + retval = -EFAULT; + } + } + + usb_autopm_put_interface(ch343->control); + +out: + kfree(buffer); + return retval; +} + +static inline int ch343_set_control(struct ch343 *ch343, int control) +{ + if (ch343->iface <= 1) + return ch343_control_out(ch343, CMD_C2 + ch343->iface, ~control, 0x0000); + else if (ch343->iface <= 3) + return ch343_control_out(ch343, CMD_C2 + 0x10 + (ch343->iface - 2), ~control, 0x0000); + else + return -1; +} + +static inline int ch343_set_line(struct ch343 *ch343, struct usb_cdc_line_coding *line) +{ + return 0; +} + +static int ch343_get_status(struct ch343 *ch343) +{ + char *buffer; + int retval; + const unsigned size = 2; + unsigned long flags; + + buffer = kmalloc(size, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + retval = ch343_control_in(ch343, CMD_R, CMD_C3 + ch343->iface, 0, buffer, size); + if (retval != size) + goto out; + + /* setup the private status if available */ + spin_lock_irqsave(&ch343->read_lock, flags); + ch343->ctrlin = (~(*buffer)) & CH343_CTI_ST; + spin_unlock_irqrestore(&ch343->read_lock, flags); + +out: + kfree(buffer); + return retval; +} + +/* -------------------------------------------------------------------------- */ + +static int ch343_configure(struct ch343 *ch343) +{ + char *buffer; + int r; + const unsigned size = 2; + u8 chiptype; + + buffer = kmalloc(size, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + r = ch343_control_in(ch343, CMD_C6, 0, 0, buffer, size); + if (r != size) + goto out; + + chiptype = buffer[1]; + + switch (ch343->idProduct) { + case 0x55D2: + if (chiptype == 0x48) + ch343->chiptype = CHIP_CH342F; + else if (chiptype == 0x41) + ch343->chiptype = CHIP_CH342K; + break; + case 0x55D3: + if (chiptype == 0x08) + ch343->chiptype = CHIP_CH343GP; + else if (chiptype == 0x02) + ch343->chiptype = CHIP_CH343J; + else if (chiptype == 0x01) + ch343->chiptype = CHIP_CH343K; + else if (chiptype == 0x18) + ch343->chiptype = CHIP_CH343G_AUTOBAUD; + break; + case 0x55D4: + if (chiptype == 0x08) + ch343->chiptype = CHIP_CH9102F; + else if (chiptype == 0x09) + ch343->chiptype = CHIP_CH9102X; + break; + case 0x55D5: + if (chiptype == 0xC0) { + if ((buffer[0] & 0xF0) == 0x40) + ch343->chiptype = CHIP_CH344L; + else + ch343->chiptype = CHIP_CH344L_V2; + } else + ch343->chiptype = CHIP_CH344Q; + break; + case 0x55D7: + if (chiptype == 0x4B) + ch343->chiptype = CHIP_CH9103M; + break; + case 0x55D8: + if (chiptype == 0x08) + ch343->chiptype = CHIP_CH9101UH; + else if (chiptype == 0x0A) + ch343->chiptype = CHIP_CH9101RY; + break; + case 0x55DA: + case 0x55DB: + case 0x55DD: + ch343->chiptype = CHIP_CH347T; + break; + case 0x55DF: + ch343->chiptype = CHIP_CH9104L; + break; + default: + break; + } + + if (ch343->chiptype != CHIP_CH344L && ch343->chiptype != CHIP_CH344L_V2 && ch343->chiptype != CHIP_CH9104L) { + r = ch343_get_status(ch343); + if (r < 0) + goto out; + } + + dev_dbg(&ch343->data->dev, "%s - chip hver : 0x%2x, sver : 0x%2x, chip : %d\n", __func__, buffer[0], buffer[1], + ch343->chiptype); +out: + kfree(buffer); + return r < 0 ? r : 0; +} + +/* + * Write buffer management. + * All of these assume proper locks taken by the caller. + */ +static int ch343_wb_alloc(struct ch343 *ch343) +{ + int i, wbn; + struct ch343_wb *wb; + + wbn = 0; + i = 0; + for (;;) { + wb = &ch343->wb[wbn]; + if (!wb->use) { + wb->use = 1; + return wbn; + } + wbn = (wbn + 1) % CH343_NW; + if (++i >= CH343_NW) + return -1; + } +} + +static int ch343_wb_is_avail(struct ch343 *ch343) +{ + int i, n; + unsigned long flags; + + n = CH343_NW; + spin_lock_irqsave(&ch343->write_lock, flags); + for (i = 0; i < CH343_NW; i++) + n -= ch343->wb[i].use; + spin_unlock_irqrestore(&ch343->write_lock, flags); + return n; +} + +/* + * Finish write. Caller must hold ch343->write_lock + */ +static void ch343_write_done(struct ch343 *ch343, struct ch343_wb *wb) +{ + wb->use = 0; + ch343->transmitting--; + usb_autopm_put_interface_async(ch343->control); +} + +/* + * Poke write. + * + * the caller is responsible for locking + */ +static int ch343_start_wb(struct ch343 *ch343, struct ch343_wb *wb) +{ + int rc; + + ch343->transmitting++; + + wb->urb->transfer_buffer = wb->buf; + wb->urb->transfer_dma = wb->dmah; + wb->urb->transfer_buffer_length = wb->len; + wb->urb->dev = ch343->dev; + + rc = usb_submit_urb(wb->urb, GFP_ATOMIC); + if (rc < 0) { + dev_err(&ch343->data->dev, "%s - usb_submit_urb(write bulk) failed: %d\n", __func__, rc); + ch343_write_done(ch343, wb); + } + return rc; +} + +static void ch343_update_status(struct ch343 *ch343, unsigned char *data, size_t len) +{ + unsigned long flags; + u8 status; + u8 difference; + u8 type = data[0]; + u8 handled = 0; + + if (len < 4) + return; + + if (ch343->chiptype == CHIP_CH344L) { + if (data[0] != 0x00) + return; + type = data[1]; + } else if (ch343->chiptype == CHIP_CH344Q || ch343->chiptype == CHIP_CH344L_V2 || ch343->chiptype == CHIP_CH9104L) { + type = data[1]; + } + + if (type & CH343_CTT_M) { + status = ~data[len - 1] & CH343_CTI_ST; + if (ch343->chiptype == CHIP_CH344L || ch343->chiptype == CHIP_CH344L_V2) + status &= CH343_CTI_C; + + if (!ch343->clocal && (ch343->ctrlin & status & CH343_CTI_DC)) { + tty_port_tty_hangup(&ch343->port, false); + } + + spin_lock_irqsave(&ch343->read_lock, flags); + difference = status ^ ch343->ctrlin; + ch343->ctrlin = status; + ch343->oldcount = ch343->iocount; + + if (difference) { + if (difference & CH343_CTI_C) { + ch343->iocount.cts++; + } + if (difference & CH343_CTI_DS) { + ch343->iocount.dsr++; + } + if (difference & CH343_CTI_R) { + ch343->iocount.rng++; + } + if (difference & CH343_CTI_DC) { + ch343->iocount.dcd++; + } + spin_unlock_irqrestore(&ch343->read_lock, flags); + wake_up_interruptible(&ch343->wioctl); + } else + spin_unlock_irqrestore(&ch343->read_lock, flags); + handled = 1; + } + if (type & CH343_CTT_O) { + spin_lock_irqsave(&ch343->read_lock, flags); + ch343->oldcount = ch343->iocount; + ch343->iocount.overrun++; + spin_unlock_irqrestore(&ch343->read_lock, flags); + handled = 1; + } + if ((type & CH343_CTT_F) == CH343_CTT_F) { + spin_lock_irqsave(&ch343->read_lock, flags); + ch343->oldcount = ch343->iocount; + ch343->iocount.frame++; + spin_unlock_irqrestore(&ch343->read_lock, flags); + handled = 1; + } else if (type & CH343_CTT_P) { + spin_lock_irqsave(&ch343->read_lock, flags); + ch343->oldcount = ch343->iocount; + ch343->iocount.parity++; + spin_unlock_irqrestore(&ch343->read_lock, flags); + handled = 1; + } + if (!handled) + dev_err(&ch343->control->dev, + "%s - unknown status received:" + "len:%d, data0:0x%x, data1:0x%x\n", + __func__, (int)len, data[0], data[1]); +} + +/* Reports status changes with "interrupt" transfers */ +static void ch343_ctrl_irq(struct urb *urb) +{ + struct ch343 *ch343 = urb->context; + unsigned char *data = urb->transfer_buffer; + unsigned int len = urb->actual_length; + int status = urb->status; + int retval; + + switch (status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dev_dbg(&ch343->control->dev, "%s - urb shutting down with status: %d\n", __func__, status); + return; + default: + dev_dbg(&ch343->control->dev, "%s - nonzero urb status received: %d\n", __func__, status); + goto exit; + } + + usb_mark_last_busy(ch343->dev); + ch343_update_status(ch343, data, len); +exit: + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval && retval != -EPERM) + dev_err(&ch343->control->dev, "%s - usb_submit_urb failed: %d\n", __func__, retval); +} + +static int ch343_submit_read_urb(struct ch343 *ch343, int index, gfp_t mem_flags) +{ + int res; + + if (!test_and_clear_bit(index, &ch343->read_urbs_free)) + return 0; + + dev_vdbg(&ch343->data->dev, "%s - urb %d\n", __func__, index); + + res = usb_submit_urb(ch343->read_urbs[index], mem_flags); + if (res) { + if (res != -EPERM) { + dev_err(&ch343->data->dev, "%s - usb_submit_urb failed: %d\n", __func__, res); + } + set_bit(index, &ch343->read_urbs_free); + return res; + } + + return 0; +} + +static int ch343_submit_read_urbs(struct ch343 *ch343, gfp_t mem_flags) +{ + int res; + int i; + + for (i = 0; i < ch343->rx_buflimit; ++i) { + res = ch343_submit_read_urb(ch343, i, mem_flags); + if (res) + return res; + } + + return 0; +} + +static void ch343_process_read_urb(struct ch343 *ch343, struct urb *urb) +{ + if (!urb->actual_length) + return; + + tty_insert_flip_string(&ch343->port, urb->transfer_buffer, urb->actual_length); + tty_flip_buffer_push(&ch343->port); +} + +static void ch343_read_bulk_callback(struct urb *urb) +{ + struct ch343_rb *rb = urb->context; + struct ch343 *ch343 = rb->instance; + int status = urb->status; + + dev_vdbg(&ch343->data->dev, "%s - urb %d, len %d\n", __func__, rb->index, urb->actual_length); + + if (!ch343->dev) { + set_bit(rb->index, &ch343->read_urbs_free); + dev_dbg(&ch343->data->dev, "%s - disconnected\n", __func__); + return; + } + + if (status) { + set_bit(rb->index, &ch343->read_urbs_free); + dev_dbg(&ch343->data->dev, "%s - non-zero urb status: %d\n", __func__, status); + return; + } + + usb_mark_last_busy(ch343->dev); + ch343_process_read_urb(ch343, urb); + set_bit(rb->index, &ch343->read_urbs_free); + ch343_submit_read_urb(ch343, rb->index, GFP_ATOMIC); +} + +/* data interface wrote those outgoing bytes */ +static void ch343_write_bulk(struct urb *urb) +{ + struct ch343_wb *wb = urb->context; + struct ch343 *ch343 = wb->instance; + unsigned long flags; + int status = urb->status; + + dev_vdbg(&ch343->data->dev, "%s, len %d\n", __func__, urb->actual_length); + if (status || (urb->actual_length != urb->transfer_buffer_length)) + dev_vdbg(&ch343->data->dev, "%s - len %d/%d, status %d\n", __func__, urb->actual_length, + urb->transfer_buffer_length, status); + + spin_lock_irqsave(&ch343->write_lock, flags); + ch343_write_done(ch343, wb); + spin_unlock_irqrestore(&ch343->write_lock, flags); + schedule_work(&ch343->work); +} + +static void ch343_softint(struct work_struct *work) +{ + struct ch343 *ch343 = container_of(work, struct ch343, work); + + dev_dbg(&ch343->data->dev, "%s\n", __func__); + + tty_port_tty_wakeup(&ch343->port); +} + +/* + * TTY handlers + */ +static int ch343_tty_install(struct tty_driver *driver, struct tty_struct *tty) +{ + struct ch343 *ch343; + int retval; + + dev_dbg(tty->dev, "%s\n", __func__); + + ch343 = ch343_get_by_minor(tty->index); + if (!ch343) + return -ENODEV; + + retval = tty_standard_install(driver, tty); + if (retval) + goto error_init_termios; + + tty->driver_data = ch343; + + return 0; + +error_init_termios: + tty_port_put(&ch343->port); + return retval; +} + +static int ch343_tty_open(struct tty_struct *tty, struct file *filp) +{ + struct ch343 *ch343 = tty->driver_data; + + dev_dbg(tty->dev, "%s\n", __func__); + + return tty_port_open(&ch343->port, tty, filp); +} + +static void ch343_port_dtr_rts(struct tty_port *port, int raise) +{ + struct ch343 *ch343 = container_of(port, struct ch343, port); + int res; + + dev_dbg(&ch343->data->dev, "%s, raise:%d\n", __func__, raise); + + if (raise) + ch343->ctrlout |= CH343_CTO_D | CH343_CTO_R; + else + ch343->ctrlout &= ~(CH343_CTO_D | CH343_CTO_R); + + res = ch343_set_control(ch343, ch343->ctrlout); + if (res) + dev_err(&ch343->control->dev, "failed to set dtr/rts\n"); +} + +static int ch343_port_activate(struct tty_port *port, struct tty_struct *tty) +{ + struct ch343 *ch343 = container_of(port, struct ch343, port); + int retval = -ENODEV; + int i; + + dev_dbg(&ch343->control->dev, "%s\n", __func__); + + mutex_lock(&ch343->mutex); + if (ch343->disconnected) + goto disconnected; + + retval = usb_autopm_get_interface(ch343->control); + if (retval) + goto error_get_interface; + + /* + * FIXME: Why do we need this? Allocating 64K of physically contiguous + * memory is really nasty... + */ + set_bit(TTY_NO_WRITE_SPLIT, &tty->flags); + ch343->control->needs_remote_wakeup = 1; + + // retval = ch343_configure(ch343); + // if (retval) + // goto error_configure; + + ch343_tty_set_termios(tty, NULL); + + retval = usb_submit_urb(ch343->ctrlurb, GFP_KERNEL); + if (retval) { + dev_err(&ch343->control->dev, "%s - usb_submit_urb(ctrl cmd) failed\n", __func__); + goto error_submit_urb; + } + + retval = ch343_submit_read_urbs(ch343, GFP_KERNEL); + if (retval) + goto error_submit_read_urbs; + usb_autopm_put_interface(ch343->control); + + mutex_unlock(&ch343->mutex); + + return 0; + +error_submit_read_urbs: + for (i = 0; i < ch343->rx_buflimit; i++) + usb_kill_urb(ch343->read_urbs[i]); +error_submit_urb: + usb_kill_urb(ch343->ctrlurb); + // error_configure: + usb_autopm_put_interface(ch343->control); +error_get_interface: +disconnected: + mutex_unlock(&ch343->mutex); + + return usb_translate_errors(retval); +} + +static void ch343_port_destruct(struct tty_port *port) +{ + struct ch343 *ch343 = container_of(port, struct ch343, port); + + dev_dbg(&ch343->control->dev, "%s\n", __func__); + + ch343_release_minor(ch343); + usb_put_intf(ch343->control); + kfree(ch343); +} + +static void ch343_port_shutdown(struct tty_port *port) +{ + struct ch343 *ch343 = container_of(port, struct ch343, port); + struct urb *urb; + struct ch343_wb *wb; + int i; + + dev_dbg(&ch343->control->dev, "%s\n", __func__); + + usb_autopm_get_interface_no_resume(ch343->control); + ch343->control->needs_remote_wakeup = 0; + usb_autopm_put_interface(ch343->control); + + for (;;) { + urb = usb_get_from_anchor(&ch343->delayed); + if (!urb) + break; + wb = urb->context; + wb->use = 0; + usb_autopm_put_interface_async(ch343->control); + } + + usb_kill_urb(ch343->ctrlurb); + for (i = 0; i < CH343_NW; i++) + usb_kill_urb(ch343->wb[i].urb); + for (i = 0; i < ch343->rx_buflimit; i++) + usb_kill_urb(ch343->read_urbs[i]); +} + +static void ch343_tty_cleanup(struct tty_struct *tty) +{ + struct ch343 *ch343 = tty->driver_data; + dev_dbg(&ch343->control->dev, "%s\n", __func__); + tty_port_put(&ch343->port); +} + +static void ch343_tty_hangup(struct tty_struct *tty) +{ + struct ch343 *ch343 = tty->driver_data; + dev_dbg(&ch343->control->dev, "%s\n", __func__); + tty_port_hangup(&ch343->port); +} + +static void ch343_tty_close(struct tty_struct *tty, struct file *filp) +{ + struct ch343 *ch343 = tty->driver_data; + dev_dbg(&ch343->control->dev, "%s\n", __func__); + tty_port_close(&ch343->port, tty, filp); +} + +static int ch343_tty_write(struct tty_struct *tty, const unsigned char *buf, int count) +{ + struct ch343 *ch343 = tty->driver_data; + int stat; + unsigned long flags; + int wbn; + struct ch343_wb *wb; + + if (!count) + return 0; + + dev_vdbg(&ch343->data->dev, "%s - count %d\n", __func__, count); + + spin_lock_irqsave(&ch343->write_lock, flags); + wbn = ch343_wb_alloc(ch343); + if (wbn < 0) { + spin_unlock_irqrestore(&ch343->write_lock, flags); + return 0; + } + wb = &ch343->wb[wbn]; + + if (!ch343->dev) { + wb->use = 0; + spin_unlock_irqrestore(&ch343->write_lock, flags); + return -ENODEV; + } + + count = (count > ch343->writesize) ? ch343->writesize : count; + + memcpy(wb->buf, buf, count); + wb->len = count; + + stat = usb_autopm_get_interface_async(ch343->control); + if (stat) { + wb->use = 0; + spin_unlock_irqrestore(&ch343->write_lock, flags); + return stat; + } + + if (ch343->susp_count) { + usb_anchor_urb(wb->urb, &ch343->delayed); + spin_unlock_irqrestore(&ch343->write_lock, flags); + return count; + } + + stat = ch343_start_wb(ch343, wb); + spin_unlock_irqrestore(&ch343->write_lock, flags); + + if (stat < 0) + return stat; + return count; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)) +static unsigned int ch343_tty_write_room(struct tty_struct *tty) +#else +static int ch343_tty_write_room(struct tty_struct *tty) +#endif +{ + struct ch343 *ch343 = tty->driver_data; + /* + * Do not let the line discipline to know that we have a reserve, + * or it might get too enthusiastic. + */ + return ch343_wb_is_avail(ch343) ? ch343->writesize : 0; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)) +static unsigned int ch343_tty_chars_in_buffer(struct tty_struct *tty) +#else +static int ch343_tty_chars_in_buffer(struct tty_struct *tty) +#endif +{ + struct ch343 *ch343 = tty->driver_data; + /* + * if the device was unplugged then any remaining characters fell out + * of the connector ;) + */ + if (ch343->disconnected) + return 0; + /* + * This is inaccurate (overcounts), but it works. + */ + return (CH343_NW - ch343_wb_is_avail(ch343)) * ch343->writesize; +} + +static int ch343_tty_break_ctl(struct tty_struct *tty, int state) +{ + struct ch343 *ch343 = tty->driver_data; + int retval; + uint16_t reg_contents; + uint8_t *regbuf; + + dev_dbg(&ch343->control->dev, "%s\n", __func__); + + regbuf = kmalloc(2, GFP_KERNEL); + if (!regbuf) + return -1; + + if (state != 0) { + if ((ch343->chiptype == CHIP_CH344L) || (ch343->chiptype == CHIP_CH344Q) || + (ch343->chiptype == CHIP_CH344L_V2) || (ch343->chiptype == CHIP_CH9104L)) { + regbuf[0] = ch343->iface; + regbuf[1] = 0x01; + } else { + regbuf[0] = CH343_N_B; + regbuf[1] = 0x00; + } + } else { + if ((ch343->chiptype == CHIP_CH344L) || (ch343->chiptype == CHIP_CH344Q) || + (ch343->chiptype == CHIP_CH344L_V2) || (ch343->chiptype == CHIP_CH9104L)) { + regbuf[0] = ch343->iface; + regbuf[1] = 0x00; + } else { + regbuf[0] = CH343_N_B | CH343_N_AB; + regbuf[1] = 0x00; + } + } + reg_contents = get_unaligned_le16(regbuf); + + if ((ch343->chiptype == CHIP_CH344L) || (ch343->chiptype == CHIP_CH344Q) || (ch343->chiptype == CHIP_CH344L_V2) || + (ch343->chiptype == CHIP_CH9104L)) { + retval = ch343_control_out(ch343, CMD_C4, reg_contents, 0x00); + } else { + if (ch343->iface) + retval = ch343_control_out(ch343, CMD_C4, 0x00, reg_contents); + else + retval = ch343_control_out(ch343, CMD_C4, reg_contents, 0x00); + } + + if (retval < 0) + dev_err(&ch343->control->dev, "%s - USB control write error (%d)\n", __func__, retval); + + kfree(regbuf); + + return retval; +} + +static int ch343_tty_tiocmget(struct tty_struct *tty) +{ + struct ch343 *ch343 = tty->driver_data; + unsigned long flags; + unsigned int result; + + dev_dbg(&ch343->control->dev, "%s\n", __func__); + + spin_lock_irqsave(&ch343->read_lock, flags); + result = (ch343->ctrlout & CH343_CTO_D ? TIOCM_DTR : 0) | (ch343->ctrlout & CH343_CTO_R ? TIOCM_RTS : 0) | + (ch343->ctrlin & CH343_CTI_C ? TIOCM_CTS : 0) | (ch343->ctrlin & CH343_CTI_DS ? TIOCM_DSR : 0) | + (ch343->ctrlin & CH343_CTI_R ? TIOCM_RI : 0) | (ch343->ctrlin & CH343_CTI_DC ? TIOCM_CD : 0); + spin_unlock_irqrestore(&ch343->read_lock, flags); + + return result; +} + +static int ch343_tty_tiocmset(struct tty_struct *tty, unsigned int set, unsigned int clear) +{ + struct ch343 *ch343 = tty->driver_data; + unsigned int newctrl; + + dev_dbg(&ch343->control->dev, "%s\n", __func__); + + newctrl = ch343->ctrlout; + set = (set & TIOCM_DTR ? CH343_CTO_D : 0) | (set & TIOCM_RTS ? CH343_CTO_R : 0); + clear = (clear & TIOCM_DTR ? CH343_CTO_D : 0) | (clear & TIOCM_RTS ? CH343_CTO_R : 0); + + newctrl = (newctrl & ~clear) | set; + + if (ch343->ctrlout == newctrl) { + return 0; + } + + return ch343_set_control(ch343, ch343->ctrlout = newctrl); +} + +static int ch343_get_serial_info(struct ch343 *ch343, struct serial_struct __user *info) +{ + struct serial_struct tmp; + + if (!info) + return -EINVAL; + + memset(&tmp, 0, sizeof(tmp)); + tmp.flags = ASYNC_LOW_LATENCY; + tmp.xmit_fifo_size = ch343->writesize; + tmp.baud_base = le32_to_cpu(ch343->line.dwDTERate); + tmp.close_delay = ch343->port.close_delay / 10; + tmp.closing_wait = + ch343->port.closing_wait == ASYNC_CLOSING_WAIT_NONE ? ASYNC_CLOSING_WAIT_NONE : ch343->port.closing_wait / 10; + + if (copy_to_user(info, &tmp, sizeof(tmp))) + return -EFAULT; + else + return 0; +} + +static int ch343_set_serial_info(struct ch343 *ch343, struct serial_struct __user *newinfo) +{ + struct serial_struct new_serial; + unsigned int closing_wait, close_delay; + int retval = 0; + + if (copy_from_user(&new_serial, newinfo, sizeof(new_serial))) + return -EFAULT; + + close_delay = new_serial.close_delay * 10; + closing_wait = + new_serial.closing_wait == ASYNC_CLOSING_WAIT_NONE ? ASYNC_CLOSING_WAIT_NONE : new_serial.closing_wait * 10; + + mutex_lock(&ch343->port.mutex); + + if (!capable(CAP_SYS_ADMIN)) { + if ((close_delay != ch343->port.close_delay) || (closing_wait != ch343->port.closing_wait)) + retval = -EPERM; + else + retval = -EOPNOTSUPP; + } else { + ch343->port.close_delay = close_delay; + ch343->port.closing_wait = closing_wait; + } + + mutex_unlock(&ch343->port.mutex); + return retval; +} + +static int ch343_wait_serial_change(struct ch343 *ch343, unsigned long arg) +{ + int rv = 0; + DECLARE_WAITQUEUE(wait, current); + struct async_icount old, new; + + do { + spin_lock_irq(&ch343->read_lock); + old = ch343->oldcount; + new = ch343->iocount; + ch343->oldcount = new; + spin_unlock_irq(&ch343->read_lock); + + if ((arg & TIOCM_CTS) && old.cts != new.cts) + break; + if ((arg & TIOCM_DSR) && old.dsr != new.dsr) + break; + if ((arg & TIOCM_RI) && old.rng != new.rng) + break; + if ((arg & TIOCM_CD) && old.dcd != new.dcd) + break; + + add_wait_queue(&ch343->wioctl, &wait); + set_current_state(TASK_INTERRUPTIBLE); + schedule(); + remove_wait_queue(&ch343->wioctl, &wait); + if (ch343->disconnected) { + if (arg & TIOCM_CD) + break; + else + rv = -ENODEV; + } else { + if (signal_pending(current)) + rv = -ERESTARTSYS; + } + } while (!rv); + + return rv; +} + +static int ch343_get_serial_usage(struct ch343 *ch343, struct serial_icounter_struct __user *count) +{ + struct serial_icounter_struct icount; + int rv = 0; + + memset(&icount, 0, sizeof(icount)); + icount.cts = ch343->iocount.cts; + icount.dsr = ch343->iocount.dsr; + icount.rng = ch343->iocount.rng; + icount.dcd = ch343->iocount.dcd; + icount.frame = ch343->iocount.frame; + icount.overrun = ch343->iocount.overrun; + icount.parity = ch343->iocount.parity; + icount.brk = ch343->iocount.brk; + + if (copy_to_user(count, &icount, sizeof(icount)) > 0) + rv = -EFAULT; + + return rv; +} + +static int ch343_tty_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg) +{ + struct ch343 *ch343 = tty->driver_data; + int rv = 0; + unsigned long arg1, arg2, arg3, arg4, arg5, arg6; + u32 __user *argval = (u32 __user *)arg; + u8 *buffer; + + dev_dbg(&ch343->control->dev, "%s\n", __func__); + + buffer = kmalloc(512, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + switch (cmd) { + case TIOCGSERIAL: /* gets serial port data */ + rv = ch343_get_serial_info(ch343, (struct serial_struct __user *)arg); + break; + case TIOCSSERIAL: + rv = ch343_set_serial_info(ch343, (struct serial_struct __user *)arg); + break; + case TIOCMIWAIT: + rv = usb_autopm_get_interface(ch343->control); + if (rv < 0) { + rv = -EIO; + break; + } + rv = ch343_wait_serial_change(ch343, arg); + usb_autopm_put_interface(ch343->control); + break; + case IOCTL_CMD_GICOUNT: + case TIOCGICOUNT: + rv = ch343_get_serial_usage(ch343, (struct serial_icounter_struct __user *)arg); + break; + case IOCTL_CMD_GETCHIPTYPE: + if (put_user(ch343->chiptype, argval)) { + rv = -EFAULT; + goto out; + } + break; + case IOCTL_CMD_CTRLIN: + get_user(arg1, (u8 __user *)arg); + get_user(arg2, ((u8 __user *)arg + 1)); + get_user(arg3, (u16 __user *)((u8 *)arg + 2)); + get_user(arg4, (u16 __user *)((u8 *)arg + 4)); + get_user(arg5, (u16 __user *)((u8 *)arg + 6)); + arg6 = (unsigned long)((u8 __user *)arg + 8); + rv = ch343_control_msg_in(ch343, (u8)arg1, (u8)arg2, (u16)arg3, (u16)arg4, (u8 __user *)arg6, (u16)arg5); + break; + case IOCTL_CMD_CTRLOUT: + get_user(arg1, (u8 __user *)arg); + get_user(arg2, ((u8 __user *)arg + 1)); + get_user(arg3, (u16 __user *)((u8 *)arg + 2)); + get_user(arg4, (u16 __user *)((u8 *)arg + 4)); + get_user(arg5, (u16 __user *)((u8 *)arg + 6)); + arg6 = (unsigned long)((u8 __user *)arg + 8); + rv = ch343_control_msg_out(ch343, (u8)arg1, (u8)arg2, (u16)arg3, (u16)arg4, (u8 __user *)arg6, (u16)arg5); + if (rv != (u16)arg5) { + rv = -EINVAL; + goto out; + } + break; + default: + rv = -ENOIOCTLCMD; + break; + } + +out: + kfree(buffer); + return rv < 0 ? rv : 0; +} + +static int ch343_get(CHIPTYPE chiptype, unsigned int bval, unsigned char *fct, unsigned char *dvs) +{ + unsigned char a; + unsigned char b; + unsigned long c; + + if (((chiptype == CHIP_CH347T) || (chiptype == CHIP_CH344Q) || (chiptype == CHIP_CH9104L)) && bval >= 2000000) { + *fct = (unsigned char)(bval / 200); + *dvs = (unsigned char)((bval / 200) >> 8); + } + + switch (bval) { + case 6000000: + case 4000000: + case 2400000: + case 921600: + case 307200: + case 256000: + b = 7; + c = 12000000; + break; + default: + if (bval > 6000000 / 255) { + b = 3; + c = 6000000; + } else if (bval > 750000 / 255) { + b = 2; + c = 750000; + } else if (bval > 93750 / 255) { + b = 1; + c = 93750; + } else { + b = 0; + c = 11719; + } + break; + } + a = (unsigned char)(c / bval); + if (a == 0 || a == 0xFF) + return -EINVAL; + if ((c / a - bval) > (bval - c / (a + 1))) + a++; + a = 256 - a; + + *fct = a; + *dvs = b; + + return 0; +} + +static void ch343_tty_set_termios(struct tty_struct *tty, struct ktermios *termios_old) +{ + struct ch343 *ch343 = tty->driver_data; + struct ktermios *termios = &tty->termios; + struct usb_ch343_line_coding newline; + int newctrl = ch343->ctrlout; + + unsigned char dvs = 0; + unsigned char reg_count = 0; + unsigned char fct = 0; + unsigned char reg_value = 0; + unsigned short value = 0; + unsigned short index = 0; + + dev_dbg(tty->dev, "%s\n", __func__); + + if (termios_old && !tty_termios_hw_change(&tty->termios, termios_old)) { + return; + } + + newline.dwDTERate = tty_get_baud_rate(tty); + + if (newline.dwDTERate == 0) + newline.dwDTERate = 9600; + ch343_get(ch343->chiptype, newline.dwDTERate, &fct, &dvs); + + newline.bCharFormat = termios->c_cflag & CSTOPB ? 2 : 1; + if (newline.bCharFormat == 2) + reg_value |= CH343_L_SB; + + newline.bParityType = + termios->c_cflag & PARENB ? (termios->c_cflag & PARODD ? 1 : 2) + (termios->c_cflag & CMSPAR ? 2 : 0) : 0; + + switch (newline.bParityType) { + case 0x01: + reg_value |= CH343_L_P_O; + break; + case 0x02: + reg_value |= CH343_L_P_E; + break; + case 0x03: + reg_value |= CH343_L_P_M; + break; + case 0x04: + reg_value |= CH343_L_P_S; + break; + default: + break; + } + + switch (termios->c_cflag & CSIZE) { + case CS5: + newline.bDataBits = 5; + reg_value |= CH343_L_C5; + break; + case CS6: + newline.bDataBits = 6; + reg_value |= CH343_L_C6; + break; + case CS7: + newline.bDataBits = 7; + reg_value |= CH343_L_C7; + break; + case CS8: + default: + newline.bDataBits = 8; + reg_value |= CH343_L_C8; + break; + } + + /* FIXME: Needs to clear unsupported bits in the termios */ + ch343->clocal = ((termios->c_cflag & CLOCAL) != 0); + + if (C_BAUD(tty) == B0) { + newline.dwDTERate = ch343->line.dwDTERate; + newctrl &= ~CH343_CTO_D; + } else if (termios_old && (termios_old->c_cflag & CBAUD) == B0) { + newctrl |= CH343_CTO_D; + } + + reg_value |= CH343_L_E_R | CH343_L_E_T; + reg_count |= CH343_L_R_CT | CH343_L_R_CL | CH343_L_R_T; + + value |= reg_count; + value |= (unsigned short)reg_value << 8; + + index |= 0x00 | dvs; + index |= (unsigned short)fct << 8; + if (ch343->iface <= 1) + ch343_control_out(ch343, CMD_C1 + ch343->iface, value, index); + else if (ch343->iface <= 3) + ch343_control_out(ch343, CMD_C1 + 0x10 + (ch343->iface - 2), value, index); + + if (memcmp(&ch343->line, &newline, sizeof newline)) { + memcpy(&ch343->line, &newline, sizeof newline); + dev_dbg(&ch343->control->dev, "%s - set line: %d %d %d %d\n", __func__, newline.dwDTERate, newline.bCharFormat, + newline.bParityType, newline.bDataBits); + } + + if (C_CRTSCTS(tty)) { + newctrl |= CH343_CTO_A | CH343_CTO_R; + } else + newctrl &= ~CH343_CTO_A; + + if (newctrl != ch343->ctrlout) + ch343_set_control(ch343, ch343->ctrlout = newctrl); +} + +static const struct tty_port_operations ch343_port_ops = { + .dtr_rts = ch343_port_dtr_rts, + .shutdown = ch343_port_shutdown, + .activate = ch343_port_activate, + .destruct = ch343_port_destruct, +}; + +/* Little helpers: write/read buffers free */ +static void ch343_write_buffers_free(struct ch343 *ch343) +{ + int i; + struct ch343_wb *wb; + struct usb_device *usb_dev = interface_to_usbdev(ch343->control); + + for (wb = &ch343->wb[0], i = 0; i < CH343_NW; i++, wb++) + usb_free_coherent(usb_dev, ch343->writesize, wb->buf, wb->dmah); +} + +static void ch343_read_buffers_free(struct ch343 *ch343) +{ + struct usb_device *usb_dev = interface_to_usbdev(ch343->control); + int i; + + for (i = 0; i < ch343->rx_buflimit; i++) + usb_free_coherent(usb_dev, ch343->readsize, ch343->read_buffers[i].base, ch343->read_buffers[i].dma); +} + +/* Little helper: write buffers allocate */ +static int ch343_write_buffers_alloc(struct ch343 *ch343) +{ + int i; + struct ch343_wb *wb; + + for (wb = &ch343->wb[0], i = 0; i < CH343_NW; i++, wb++) { + wb->buf = usb_alloc_coherent(ch343->dev, ch343->writesize, GFP_KERNEL, &wb->dmah); + if (!wb->buf) { + while (i != 0) { + --i; + --wb; + usb_free_coherent(ch343->dev, ch343->writesize, wb->buf, wb->dmah); + } + return -ENOMEM; + } + } + return 0; +} + +static int ch343_open(struct inode *inode, struct file *file) +{ + struct ch343 *ch343; + struct usb_interface *interface; + int subminor; + int retval = 0; + + subminor = iminor(inode); + + interface = usb_find_interface(&ch343_driver, subminor); + if (!interface) { + pr_err("%s - error, can't find device for minor %d\n", __func__, subminor); + retval = -ENODEV; + goto exit; + } + + ch343 = usb_get_intfdata(interface); + if (!ch343) { + retval = -ENODEV; + goto exit; + } + + /* save our object in the file's private structure */ + file->private_data = ch343; + +exit: + return retval; +} + +static int ch343_release(struct inode *inode, struct file *file) +{ + struct ch343 *ch343; + + ch343 = file->private_data; + if (ch343 == NULL) + return -ENODEV; + + return 0; +} + +static long ch343_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct ch343 *ch343; + int rv = 0; + u8 *buffer; + unsigned long arg1, arg2, arg3, arg4, arg5, arg6; + u32 __user *argval = (u32 __user *)arg; + + ch343 = file->private_data; + if (ch343 == NULL) + return -ENODEV; + + buffer = kmalloc(512, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + switch (cmd) { + case IOCTL_CMD_GETCHIPTYPE: + if (put_user(ch343->chiptype, argval)) { + rv = -EFAULT; + goto out; + } + break; + case IOCTL_CMD_CTRLIN: + get_user(arg1, (u8 __user *)arg); + get_user(arg2, ((u8 __user *)arg + 1)); + get_user(arg3, (u16 __user *)((u8 *)arg + 2)); + get_user(arg4, (u16 __user *)((u8 *)arg + 4)); + get_user(arg5, (u16 __user *)((u8 *)arg + 6)); + arg6 = (unsigned long)((u8 __user *)arg + 8); + rv = ch343_control_msg_in(ch343, (u8)arg1, (u8)arg2, (u16)arg3, (u16)arg4, (u8 __user *)arg6, (u16)arg5); + break; + case IOCTL_CMD_CTRLOUT: + get_user(arg1, (u8 __user *)arg); + get_user(arg2, ((u8 __user *)arg + 1)); + get_user(arg3, (u16 __user *)((u8 *)arg + 2)); + get_user(arg4, (u16 __user *)((u8 *)arg + 4)); + get_user(arg5, (u16 __user *)((u8 *)arg + 6)); + arg6 = (unsigned long)((u8 __user *)arg + 8); + rv = ch343_control_msg_out(ch343, (u8)arg1, (u8)arg2, (u16)arg3, (u16)arg4, (u8 __user *)arg6, (u16)arg5); + if (rv != (u16)arg5) { + rv = -EINVAL; + goto out; + } + break; + default: + rv = -ENOIOCTLCMD; + break; + } + +out: + kfree(buffer); + return rv; +} + +static const struct file_operations ch343_fops = { + .owner = THIS_MODULE, + .open = ch343_open, + .unlocked_ioctl = ch343_ioctl, + .release = ch343_release, +}; + +/* + * usb class driver info in order to get a minor number from the usb core, + * and to have the device registered with the driver core + */ +static struct usb_class_driver ch343_class = { + .name = "ch343_iodev%d", + .fops = &ch343_fops, + .minor_base = USB_MINOR_BASE, +}; + +/* + * USB probe and disconnect routines. + */ +static int ch343_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_cdc_union_desc *union_header = NULL; + unsigned char *buffer = intf->altsetting->extra; + int buflen = intf->altsetting->extralen; + struct usb_interface *control_interface; + struct usb_interface *data_interface; + struct usb_endpoint_descriptor *epctrl = NULL; + struct usb_endpoint_descriptor *epread = NULL; + struct usb_endpoint_descriptor *epwrite = NULL; + struct usb_device *usb_dev = interface_to_usbdev(intf); + struct ch343 *ch343; + int minor; + int ctrlsize, readsize; + u8 *buf; + unsigned long quirks; + int num_rx_buf = CH343_NR; + int i; + unsigned int elength = 0; + struct device *tty_dev; + int rv = -ENOMEM; + + /* normal quirks */ + quirks = (unsigned long)id->driver_info; + if (!buffer) { + dev_err(&intf->dev, "Weird descriptor references\n"); + return -EINVAL; + } + + while (buflen > 0) { + elength = buffer[0]; + if (!elength) { + dev_err(&intf->dev, "skipping garbage byte\n"); + elength = 1; + goto next_desc; + } + if (buffer[1] != USB_DT_CS_INTERFACE) { + dev_err(&intf->dev, "skipping garbage\n"); + goto next_desc; + } + + switch (buffer[2]) { + case USB_CDC_UNION_TYPE: /* we've found it */ + if (elength < sizeof(struct usb_cdc_union_desc)) + goto next_desc; + if (union_header) { + dev_err(&intf->dev, + "More than one " + "union descriptor, skipping ...\n"); + goto next_desc; + } + union_header = (struct usb_cdc_union_desc *)buffer; + break; + default: + /* + * there are LOTS more CDC descriptors that + * could legitimately be found here. + */ + break; + } + next_desc: + buflen -= elength; + buffer += elength; + } + + control_interface = usb_ifnum_to_if(usb_dev, union_header->bMasterInterface0); + data_interface = usb_ifnum_to_if(usb_dev, union_header->bSlaveInterface0); + + if (intf != control_interface) + return -ENODEV; + + if (usb_interface_claimed(data_interface)) { + dev_dbg(&intf->dev, "The data interface isn't available\n"); + return -EBUSY; + } + + if (data_interface->cur_altsetting->desc.bNumEndpoints < 2 || + control_interface->cur_altsetting->desc.bNumEndpoints == 0) + return -EINVAL; + + epctrl = &control_interface->cur_altsetting->endpoint[0].desc; + epwrite = &data_interface->cur_altsetting->endpoint[0].desc; + epread = &data_interface->cur_altsetting->endpoint[1].desc; + + /* workaround for switched endpoints */ + if (!usb_endpoint_dir_in(epread)) { + /* descriptors are swapped */ + dev_dbg(&intf->dev, "The data interface has switched endpoints\n"); + swap(epread, epwrite); + } + + ch343 = kzalloc(sizeof(struct ch343), GFP_KERNEL); + if (ch343 == NULL) + goto alloc_fail; + + ch343->idVendor = id->idVendor; + ch343->idProduct = id->idProduct; + ch343->iface = control_interface->cur_altsetting->desc.bInterfaceNumber / 2; + + minor = ch343_alloc_minor(ch343); + if (minor < 0) { + dev_err(&intf->dev, "no more free ch343 devices\n"); + kfree(ch343); + return -ENODEV; + } + + ctrlsize = usb_endpoint_maxp(epctrl); + readsize = usb_endpoint_maxp(epread); + ch343->writesize = usb_endpoint_maxp(epwrite) * 20; + ch343->control = control_interface; + ch343->data = data_interface; + ch343->minor = minor; + ch343->dev = usb_dev; + ch343->ctrlsize = ctrlsize; + ch343->readsize = readsize; + ch343->rx_buflimit = num_rx_buf; + + dev_dbg(&intf->dev, "ep%d ctrl: %d, ep%d read: %d, ep%d write: %d\n", usb_endpoint_num(epctrl), + usb_endpoint_maxp(epctrl), usb_endpoint_num(epread), usb_endpoint_maxp(epread), usb_endpoint_num(epwrite), + usb_endpoint_maxp(epwrite)); + + INIT_WORK(&ch343->work, ch343_softint); + init_waitqueue_head(&ch343->wioctl); + spin_lock_init(&ch343->write_lock); + spin_lock_init(&ch343->read_lock); + mutex_init(&ch343->mutex); + ch343->rx_endpoint = usb_rcvbulkpipe(usb_dev, epread->bEndpointAddress); + tty_port_init(&ch343->port); + ch343->port.ops = &ch343_port_ops; + init_usb_anchor(&ch343->delayed); + ch343->quirks = quirks; + + buf = usb_alloc_coherent(usb_dev, ctrlsize, GFP_KERNEL, &ch343->ctrl_dma); + if (!buf) + goto alloc_fail2; + ch343->ctrl_buffer = buf; + + if (ch343_write_buffers_alloc(ch343) < 0) + goto alloc_fail4; + + ch343->ctrlurb = usb_alloc_urb(0, GFP_KERNEL); + if (!ch343->ctrlurb) + goto alloc_fail5; + + for (i = 0; i < num_rx_buf; i++) { + struct ch343_rb *rb = &(ch343->read_buffers[i]); + struct urb *urb; + + rb->base = usb_alloc_coherent(ch343->dev, readsize, GFP_KERNEL, &rb->dma); + if (!rb->base) + goto alloc_fail6; + rb->index = i; + rb->instance = ch343; + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) + goto alloc_fail6; + + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + urb->transfer_dma = rb->dma; + usb_fill_bulk_urb(urb, ch343->dev, ch343->rx_endpoint, rb->base, ch343->readsize, ch343_read_bulk_callback, rb); + + ch343->read_urbs[i] = urb; + __set_bit(i, &ch343->read_urbs_free); + } + for (i = 0; i < CH343_NW; i++) { + struct ch343_wb *snd = &(ch343->wb[i]); + + snd->urb = usb_alloc_urb(0, GFP_KERNEL); + if (snd->urb == NULL) + goto alloc_fail7; + + usb_fill_bulk_urb(snd->urb, usb_dev, usb_sndbulkpipe(usb_dev, epwrite->bEndpointAddress), NULL, + ch343->writesize, ch343_write_bulk, snd); + snd->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + snd->instance = ch343; + } + + usb_set_intfdata(intf, ch343); + + usb_fill_int_urb(ch343->ctrlurb, usb_dev, usb_rcvintpipe(usb_dev, epctrl->bEndpointAddress), ch343->ctrl_buffer, + ctrlsize, ch343_ctrl_irq, ch343, epctrl->bInterval ? epctrl->bInterval : 16); + ch343->ctrlurb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + ch343->ctrlurb->transfer_dma = ch343->ctrl_dma; + + dev_info(&intf->dev, "ttyCH343USB%d: usb to uart device\n", minor); + + usb_driver_claim_interface(&ch343_driver, data_interface, ch343); + usb_set_intfdata(data_interface, ch343); + usb_get_intf(control_interface); + + rv = ch343_configure(ch343); + if (rv) + goto alloc_fail7; + + if (ch343->iface == 0) { + /* register the device now, as it is ready */ + rv = usb_register_dev(intf, &ch343_class); + if (rv) { + /* error when registering this driver */ + dev_err(&intf->dev, "Not able to get a minor for this device.\n"); + } else { + g_intf = intf; + } + } + + tty_dev = tty_port_register_device(&ch343->port, ch343_tty_driver, minor, &control_interface->dev); + if (IS_ERR(tty_dev)) { + rv = PTR_ERR(tty_dev); + goto alloc_fail7; + } + + return 0; + +alloc_fail7: + usb_set_intfdata(intf, NULL); + for (i = 0; i < CH343_NW; i++) + usb_free_urb(ch343->wb[i].urb); +alloc_fail6: + for (i = 0; i < num_rx_buf; i++) + usb_free_urb(ch343->read_urbs[i]); + ch343_read_buffers_free(ch343); + usb_free_urb(ch343->ctrlurb); +alloc_fail5: + ch343_write_buffers_free(ch343); +alloc_fail4: + usb_free_coherent(usb_dev, ctrlsize, ch343->ctrl_buffer, ch343->ctrl_dma); +alloc_fail2: + ch343_release_minor(ch343); + kfree(ch343); +alloc_fail: + return rv; +} + +static void stop_data_traffic(struct ch343 *ch343) +{ + int i; + + usb_kill_urb(ch343->ctrlurb); + for (i = 0; i < CH343_NW; i++) + usb_kill_urb(ch343->wb[i].urb); + for (i = 0; i < ch343->rx_buflimit; i++) + usb_kill_urb(ch343->read_urbs[i]); + cancel_work_sync(&ch343->work); +} + +static void ch343_disconnect(struct usb_interface *intf) +{ + struct ch343 *ch343 = usb_get_intfdata(intf); + struct usb_device *usb_dev = interface_to_usbdev(intf); + struct tty_struct *tty; + int i; + + dev_dbg(&intf->dev, "%s\n", __func__); + + /* sibling interface is already cleaning up */ + if (!ch343) + return; + + /* give back minor */ + if ((ch343->iface == 0) && (g_intf != NULL)) { + usb_deregister_dev(g_intf, &ch343_class); + } + + mutex_lock(&ch343->mutex); + ch343->disconnected = true; + wake_up_all(&ch343->wioctl); + usb_set_intfdata(ch343->control, NULL); + usb_set_intfdata(ch343->data, NULL); + mutex_unlock(&ch343->mutex); + + tty = tty_port_tty_get(&ch343->port); + if (tty) { + tty_vhangup(tty); + tty_kref_put(tty); + } + + stop_data_traffic(ch343); + + tty_unregister_device(ch343_tty_driver, ch343->minor); + + usb_free_urb(ch343->ctrlurb); + for (i = 0; i < CH343_NW; i++) + usb_free_urb(ch343->wb[i].urb); + for (i = 0; i < ch343->rx_buflimit; i++) + usb_free_urb(ch343->read_urbs[i]); + ch343_write_buffers_free(ch343); + usb_free_coherent(usb_dev, ch343->ctrlsize, ch343->ctrl_buffer, ch343->ctrl_dma); + ch343_read_buffers_free(ch343); + + usb_driver_release_interface(&ch343_driver, intf == ch343->control ? ch343->data : ch343->control); + + tty_port_put(&ch343->port); + dev_info(&intf->dev, "%s\n", "ch343 usb device disconnect."); +} + +#ifdef CONFIG_PM +static int ch343_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct ch343 *ch343 = usb_get_intfdata(intf); + int cnt; + + dev_dbg(&intf->dev, "%s\n", __func__); + + spin_lock_irq(&ch343->write_lock); + if (PMSG_IS_AUTO(message)) { + if (ch343->transmitting) { + spin_unlock_irq(&ch343->write_lock); + return -EBUSY; + } + } + cnt = ch343->susp_count++; + spin_unlock_irq(&ch343->write_lock); + + if (cnt) + return 0; + + stop_data_traffic(ch343); + + return 0; +} + +static int ch343_resume(struct usb_interface *intf) +{ + struct ch343 *ch343 = usb_get_intfdata(intf); + struct urb *urb; + int rv = 0; + + dev_dbg(&intf->dev, "%s\n", __func__); + + spin_lock_irq(&ch343->write_lock); + + if (--ch343->susp_count) + goto out; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0)) + if (tty_port_initialized(&ch343->port)) { +#else + if (test_bit(ASYNCB_INITIALIZED, &ch343->port.flags)) { +#endif + rv = usb_submit_urb(ch343->ctrlurb, GFP_ATOMIC); + + for (;;) { + urb = usb_get_from_anchor(&ch343->delayed); + if (!urb) + break; + + ch343_start_wb(ch343, urb->context); + } + + /* + * delayed error checking because we must + * do the write path at all cost + */ + if (rv < 0) + goto out; + + rv = ch343_submit_read_urbs(ch343, GFP_ATOMIC); + } +out: + spin_unlock_irq(&ch343->write_lock); + + return rv; +} + +static int ch343_reset_resume(struct usb_interface *intf) +{ + struct ch343 *ch343 = usb_get_intfdata(intf); + + dev_dbg(&intf->dev, "%s\n", __func__); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0)) + if (tty_port_initialized(&ch343->port)) +#else + if (test_bit(ASYNCB_INITIALIZED, &ch343->port.flags)) +#endif + tty_port_tty_hangup(&ch343->port, false); + + return ch343_resume(intf); +} + +#endif /* CONFIG_PM */ + +/* + * USB driver structure. + */ + +static const struct usb_device_id ch343_ids[] = {{USB_DEVICE_INTERFACE_PROTOCOL(0x1a86, 0x55D2, /* ch342 chip */ + USB_CDC_ACM_PROTO_AT_V25TER)}, + + {USB_DEVICE_INTERFACE_PROTOCOL(0x1a86, 0x55D3, /* ch343 chip */ + USB_CDC_ACM_PROTO_AT_V25TER)}, + + {USB_DEVICE_INTERFACE_PROTOCOL(0x1a86, 0x55D5, /* ch344 chip */ + USB_CDC_ACM_PROTO_AT_V25TER)}, + + {USB_DEVICE_INTERFACE_PROTOCOL(0x1a86, 0x55DA, /* ch347 chip mode0*/ + USB_CDC_ACM_PROTO_AT_V25TER)}, + + {USB_DEVICE_INTERFACE_PROTOCOL(0x1a86, 0x55DB, /* ch347 chip mode1*/ + USB_CDC_ACM_PROTO_AT_V25TER)}, + + {USB_DEVICE_INTERFACE_PROTOCOL(0x1a86, 0x55DD, /* ch347 chip mode3*/ + USB_CDC_ACM_PROTO_AT_V25TER)}, + + {USB_DEVICE_INTERFACE_PROTOCOL(0x1a86, 0x55D8, /* ch9101 chip */ + USB_CDC_ACM_PROTO_AT_V25TER)}, + + {USB_DEVICE_INTERFACE_PROTOCOL(0x1a86, 0x55D4, /* ch9102 chip */ + USB_CDC_ACM_PROTO_AT_V25TER)}, + + {USB_DEVICE_INTERFACE_PROTOCOL(0x1a86, 0x55D7, /* ch9103 chip */ + USB_CDC_ACM_PROTO_AT_V25TER)}, + + {USB_DEVICE_INTERFACE_PROTOCOL(0x1a86, 0x55DF, /* ch9104 chip */ + USB_CDC_ACM_PROTO_AT_V25TER)}, + + {}}; + +MODULE_DEVICE_TABLE(usb, ch343_ids); + +static struct usb_driver ch343_driver = { + .name = "usb_ch343", + .probe = ch343_probe, + .disconnect = ch343_disconnect, +#ifdef CONFIG_PM + .suspend = ch343_suspend, + .resume = ch343_resume, + .reset_resume = ch343_reset_resume, +#endif + .id_table = ch343_ids, +#ifdef CONFIG_PM + .supports_autosuspend = 1, +#endif + .disable_hub_initiated_lpm = 1, +}; + +/* + * TTY driver structures. + */ +static const struct tty_operations ch343_ops = { + .install = ch343_tty_install, + .open = ch343_tty_open, + .close = ch343_tty_close, + .cleanup = ch343_tty_cleanup, + .hangup = ch343_tty_hangup, + .write = ch343_tty_write, + .write_room = ch343_tty_write_room, + .ioctl = ch343_tty_ioctl, + .chars_in_buffer = ch343_tty_chars_in_buffer, + .break_ctl = ch343_tty_break_ctl, + .set_termios = ch343_tty_set_termios, + .tiocmget = ch343_tty_tiocmget, + .tiocmset = ch343_tty_tiocmset, +}; + +/* + * Init / exit. + */ +static int __init ch343_init(void) +{ + int retval; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)) + ch343_tty_driver = tty_alloc_driver(CH343_TTY_MINORS, TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV); + if (IS_ERR(ch343_tty_driver)) + return PTR_ERR(ch343_tty_driver); +#else + ch343_tty_driver = alloc_tty_driver(CH343_TTY_MINORS); + if (!ch343_tty_driver) + return -ENOMEM; +#endif + ch343_tty_driver->driver_name = "usbch343", ch343_tty_driver->name = "ttyCH343USB", + ch343_tty_driver->major = CH343_TTY_MAJOR, ch343_tty_driver->minor_start = 0, + ch343_tty_driver->type = TTY_DRIVER_TYPE_SERIAL, ch343_tty_driver->subtype = SERIAL_TYPE_NORMAL, +#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 0)) + ch343_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; +#endif + ch343_tty_driver->init_termios = tty_std_termios; + ch343_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL; + tty_set_operations(ch343_tty_driver, &ch343_ops); + + retval = tty_register_driver(ch343_tty_driver); + if (retval) { +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)) + tty_driver_kref_put(ch343_tty_driver); +#else + put_tty_driver(ch343_tty_driver); +#endif + return retval; + } + + retval = usb_register(&ch343_driver); + if (retval) { + tty_unregister_driver(ch343_tty_driver); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)) + tty_driver_kref_put(ch343_tty_driver); +#else + put_tty_driver(ch343_tty_driver); +#endif + return retval; + } + + printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_DESC "\n"); + printk(KERN_INFO KBUILD_MODNAME ": " VERSION_DESC "\n"); + + return 0; +} + +static void __exit ch343_exit(void) +{ + usb_deregister(&ch343_driver); + tty_unregister_driver(ch343_tty_driver); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)) + tty_driver_kref_put(ch343_tty_driver); +#else + put_tty_driver(ch343_tty_driver); +#endif + idr_destroy(&ch343_minors); + printk(KERN_INFO KBUILD_MODNAME + ": " + "ch343 driver exit.\n"); +} + +module_init(ch343_init); +module_exit(ch343_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_VERSION(VERSION_DESC); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_CHARDEV_MAJOR(CH343_TTY_MAJOR); diff --git a/sysdrv/source/kernel/drivers/usb/serial/ch343.h b/sysdrv/source/kernel/drivers/usb/serial/ch343.h new file mode 100755 index 000000000..c53a87500 --- /dev/null +++ b/sysdrv/source/kernel/drivers/usb/serial/ch343.h @@ -0,0 +1,243 @@ +#ifndef _CH343_H +#define _CH343_H + +/* + * Baud rate and default timeout + */ +#define DEFAULT_BAUD_RATE 9600 +#define DEFAULT_TIMEOUT 2000 + +/* + * CMSPAR, some architectures can't have space and mark parity. + */ + +#ifndef CMSPAR +#define CMSPAR 0 +#endif + +/* + * Major and minor numbers. + */ + +#define CH343_TTY_MAJOR 170 +#define CH343_TTY_MINORS 256 + +#define USB_MINOR_BASE 70 + +/* + * Requests. + */ + +#define USB_RT_CH343 (USB_TYPE_CLASS | USB_RECIP_INTERFACE) + +#define CMD_R 0x95 +#define CMD_W 0x9A +#define CMD_C1 0xA1 +#define CMD_C2 0xA4 +#define CMD_C3 0x05 +#define CMD_C4 0xA8 +#define CMD_C5 0x5E +#define CMD_C6 0x5F + +#define CH343_CTO_O 0x10 +#define CH343_CTO_D 0x20 +#define CH343_CTO_R 0x40 +#define CH343_CTO_A 0x80 +#define CH343_CTI_C 0x01 +#define CH343_CTI_DS 0x02 +#define CH343_CTI_R 0x04 +#define CH343_CTI_DC 0x08 +#define CH343_CTI_ST 0x0f + +#define CH343_CTT_M 0x08 +#define CH343_CTT_F 0x44 +#define CH343_CTT_P 0x04 +#define CH343_CTT_O 0x02 + +#define CH343_LO 0x02 +#define CH343_LE 0x04 +#define CH343_LB +#define CH343_LP 0x00 +#define CH343_LF 0x40 +#define CH343_LM 0x08 + +#define CH343_L_R_CT 0x80 +#define CH343_L_R_CL 0x04 +#define CH343_L_R_T 0x08 + +#define CH343_L_E_R 0x80 +#define CH343_L_E_T 0x40 +#define CH343_L_P_S 0x38 +#define CH343_L_P_M 0x28 +#define CH343_L_P_E 0x18 +#define CH343_L_P_O 0x08 +#define CH343_L_SB 0x04 +#define CH343_L_C8 0x03 +#define CH343_L_C7 0x02 +#define CH343_L_C6 0x01 +#define CH343_L_C5 0x00 + +#define CH343_N_B 0x80 +#define CH343_N_AB 0x10 + +/* + * Internal driver structures. + */ + +/* + * The only reason to have several buffers is to accommodate assumptions + * in line disciplines. They ask for empty space amount, receive our URB size, + * and proceed to issue several 1-character writes, assuming they will fit. + * The very first write takes a complete URB. Fortunately, this only happens + * when processing onlcr, so we only need 2 buffers. These values must be + * powers of 2. + */ +#define CH343_NW 16 +#define CH343_NR 16 + +struct ch343_wb { + unsigned char *buf; + dma_addr_t dmah; + int len; + int use; + struct urb *urb; + struct ch343 *instance; +}; + +struct ch343_rb { + int size; + unsigned char *base; + dma_addr_t dma; + int index; + struct ch343 *instance; +}; + +struct usb_ch343_line_coding { + __u32 dwDTERate; + __u8 bCharFormat; +#define USB_CH343_1_STOP_BITS 0 +#define USB_CH343_1_5_STOP_BITS 1 +#define USB_CH343_2_STOP_BITS 2 + + __u8 bParityType; +#define USB_CH343_NO_PARITY 0 +#define USB_CH343_ODD_PARITY 1 +#define USB_CH343_EVEN_PARITY 2 +#define USB_CH343_MARK_PARITY 3 +#define USB_CH343_SPACE_PARITY 4 + + __u8 bDataBits; +} __attribute__((packed)); + +typedef enum { + CHIP_CH342F = 0x00, + CHIP_CH342K, + CHIP_CH343GP, + CHIP_CH343G_AUTOBAUD, + CHIP_CH343K, + CHIP_CH343J, + CHIP_CH344L, + CHIP_CH344L_V2, + CHIP_CH344Q, + CHIP_CH347T, + CHIP_CH9101UH, + CHIP_CH9101RY, + CHIP_CH9102F, + CHIP_CH9102X, + CHIP_CH9103M, + CHIP_CH9104L, +} CHIPTYPE; + +struct gpioinfo { + int group; + int pin; +}; + +struct ch343_gpio { + int gpiocount; + struct gpioinfo io[64]; +}; + +struct ch343_gpio ch343_gpios[] = { + { 0, {}}, + { 0, {}}, + { 0, {}}, + { 0, {}}, + { 0, {}}, + { 0, {}}, + /* CH344L */ + { 8, {}}, + /* CH344L-V2 */ + { 8, {}}, + /* CH344Q */ + { 8, {}}, + /* CH347T */ + { 4, {}}, + /* CH9101UH */ + { 5, {{3, 2}, {3, 3}, {1, 3}, {1, 2}, {1, 5}, {2, 4}}}, + /* CH9101RY */ + { 4, {{1, 3}, {3, 3}, {3, 2}, {2, 4}}}, + /* CH9102F */ + { 5, {{2, 1}, {2, 7}, {2, 4}, {2, 6}, {2, 3}}}, + /* CH9102X */ + { 6, {{2, 3}, {2, 5}, {2, 1}, {2, 7}, {3, 0}, {2, 2}}}, + /* CH9103M */ + {12, {{1, 3}, {1, 2}, {3, 2}, {2, 6}, {1, 0}, {1, 6}, {2, 3}, {2, 5}, {3, 0}, {2, 2}, {1, 5}, {2, 4}}}, + /* CH9104L */ + {24, {}}, +}; + +struct ch343 { + struct usb_device *dev; /* the corresponding usb device */ + struct usb_interface *control; /* control interface */ + struct usb_interface *data; /* data interface */ + struct tty_port port; /* our tty port data */ + struct urb *ctrlurb; /* urbs */ + u8 *ctrl_buffer; /* buffers of urbs */ + dma_addr_t ctrl_dma; /* dma handles of buffers */ + struct ch343_wb wb[CH343_NW]; + unsigned long read_urbs_free; + struct urb *read_urbs[CH343_NR]; + struct ch343_rb read_buffers[CH343_NR]; + int rx_buflimit; + int rx_endpoint; + spinlock_t read_lock; + int write_used; /* number of non-empty write buffers */ + int transmitting; + spinlock_t write_lock; + struct mutex mutex; + bool disconnected; + struct usb_ch343_line_coding line; /* bits, stop, parity */ + struct work_struct work; /* work queue entry for line discipline waking up */ + unsigned int ctrlin; /* input control lines (DCD, DSR, RI, break, overruns) */ + unsigned int ctrlout; /* output control lines (DTR, RTS) */ + struct async_icount iocount; /* counters for control line changes */ + struct async_icount oldcount; /* for comparison of counter */ + wait_queue_head_t wioctl; /* for ioctl */ + unsigned int writesize; /* max packet size for the output bulk endpoint */ + unsigned int readsize, ctrlsize; /* buffer sizes for freeing */ + unsigned int minor; /* ch343 minor number */ + unsigned char clocal; /* termios CLOCAL */ + unsigned int susp_count; /* number of suspended interfaces */ + u8 bInterval; + struct usb_anchor delayed; /* writes queued for a device about to be woken */ + unsigned long quirks; + u8 iface; + CHIPTYPE chiptype; + u16 idVendor; + u16 idProduct; + u8 gpio5dir; +}; + +#define CDC_DATA_INTERFACE_TYPE 0x0a + +/* constants describing various quirks and errors */ +#define NO_UNION_NORMAL BIT(0) +#define SINGLE_RX_URB BIT(1) +#define NO_CAP_LINE BIT(2) +#define NO_DATA_INTERFACE BIT(4) +#define IGNORE_DEVICE BIT(5) +#define QUIRK_CONTROL_LINE_STATE BIT(6) +#define CLEAR_HALT_CONDITIONS BIT(7) + +#endif diff --git a/sysdrv/source/kernel/include/dt-bindings/soc/rockchip,boot-mode.h b/sysdrv/source/kernel/include/dt-bindings/soc/rockchip,boot-mode.h index ec4e5dd83..0a7a2133a 100644 --- a/sysdrv/source/kernel/include/dt-bindings/soc/rockchip,boot-mode.h +++ b/sysdrv/source/kernel/include/dt-bindings/soc/rockchip,boot-mode.h @@ -23,4 +23,6 @@ /* reboot system quiescent */ #define BOOT_QUIESCENT (REBOOT_FLAG + 14) +#define BOOT_TO_UBOOT (REBOOT_FLAG + 16) + #endif -- 2.34.1