project:cfg:BoardConfig_IPC: Added fastboot BoardConfig file and firmware post-scripts, distinguishing between the BoardConfigs for Luckfox Pico Pro and Luckfox Pico Max. project:app: Added fastboot_client and rk_smart_door for quick boot applications; updated rkipc app to adapt to the latest media library. media:samples: Added more usage examples. media:rockit: Fixed bugs; removed support for retrieving data frames from VPSS. media:isp: Updated rkaiq library and related tools to support connection to RKISP_Tuner. sysdrv:Makefile: Added support for compiling drv_ko on Luckfox Pico Ultra W using Ubuntu; added support for custom root filesystem. sysdrv:tools:board: Updated Buildroot optional mirror sources, updated some software versions, and stored device tree files and configuration files that undergo multiple modifications for U-Boot and kernel separately. sysdrv:source:mcu: Used RISC-V MCU SDK with RT-Thread system, mainly for initializing camera AE during quick boot. sysdrv:source:uboot: Added support for fastboot; added high baud rate DDR bin for serial firmware upgrades. sysdrv:source:kernel: Upgraded to version 5.10.160; increased NPU frequency for RV1106G3; added support for fastboot. Signed-off-by: luckfox-eng29 <eng29@luckfox.com>
2885 lines
88 KiB
Diff
2885 lines
88 KiB
Diff
From 81c6b931d822acb5650023a7bde1f997ffd0cabe Mon Sep 17 00:00:00 2001
|
|
From: luckfox-eng29 <eng29@luckfox.com>
|
|
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 <linux/slab.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/of.h>
|
|
+#include <linux/of_fdt.h>
|
|
+#include <linux/configfs.h>
|
|
+#include <linux/types.h>
|
|
+#include <linux/stat.h>
|
|
+#include <linux/limits.h>
|
|
+#include <linux/file.h>
|
|
+#include <linux/version.h>
|
|
+
|
|
+#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 <linux/gpio/driver.h>
|
|
+
|
|
#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 <tech@wch.cn>
|
|
+ *
|
|
+ * 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 <asm/byteorder.h>
|
|
+#include <asm/unaligned.h>
|
|
+#include <linux/errno.h>
|
|
+#include <linux/idr.h>
|
|
+#include <linux/init.h>
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/list.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/mutex.h>
|
|
+#include <linux/serial.h>
|
|
+#include <linux/slab.h>
|
|
+#include <linux/tty.h>
|
|
+#include <linux/tty_driver.h>
|
|
+#include <linux/tty_flip.h>
|
|
+#include <linux/uaccess.h>
|
|
+#include <linux/usb.h>
|
|
+#include <linux/usb/cdc.h>
|
|
+#include <linux/version.h>
|
|
+
|
|
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0))
|
|
+#include <linux/sched/signal.h>
|
|
+#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
|
|
|