From patchwork Fri Jan 4 11:21:27 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Andreas_F=C3=A4rber?= X-Patchwork-Id: 1020694 X-Patchwork-Delegate: davem@davemloft.net Return-Path: X-Original-To: patchwork-incoming-netdev@ozlabs.org Delivered-To: patchwork-incoming-netdev@ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netdev-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=suse.de Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 43WMnP634fz9s9G for ; Fri, 4 Jan 2019 22:22:01 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1728090AbfADLVr (ORCPT ); Fri, 4 Jan 2019 06:21:47 -0500 Received: from mx2.suse.de ([195.135.220.15]:52406 "EHLO mx1.suse.de" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1726404AbfADLVq (ORCPT ); Fri, 4 Jan 2019 06:21:46 -0500 X-Virus-Scanned: by amavisd-new at test-mx.suse.de Received: from relay2.suse.de (unknown [195.135.220.254]) by mx1.suse.de (Postfix) with ESMTP id 1F04BAE5F; Fri, 4 Jan 2019 11:21:44 +0000 (UTC) From: =?utf-8?q?Andreas_F=C3=A4rber?= To: linux-lpwan@lists.infradead.org, linux-serial@vger.kernel.org Cc: linux-usb@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Johan Hovold , Rob Herring , =?utf-8?q?Andreas_F=C3=A4rber?= , "David S. Miller" , netdev@vger.kernel.org Subject: [PATCH lora-next 1/5] net: lora: sx130x: Factor out SPI specific parts Date: Fri, 4 Jan 2019 12:21:27 +0100 Message-Id: <20190104112131.14451-2-afaerber@suse.de> X-Mailer: git-send-email 2.16.4 In-Reply-To: <20190104112131.14451-1-afaerber@suse.de> References: <20190104112131.14451-1-afaerber@suse.de> MIME-Version: 1.0 Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org Prepare for the picoGW by factoring code out into helpers using device rather than spi_device. While touching those lines, clean up the error output. Split the probe function in two, to allow derived drivers to insert code before the common probing code. This may need some more work. Signed-off-by: Andreas Färber --- drivers/net/lora/sx130x.c | 139 +++++++++++++++++++++++++++----------------- include/linux/lora/sx130x.h | 7 +++ 2 files changed, 92 insertions(+), 54 deletions(-) diff --git a/drivers/net/lora/sx130x.c b/drivers/net/lora/sx130x.c index 9cae9cea195f..840052955874 100644 --- a/drivers/net/lora/sx130x.c +++ b/drivers/net/lora/sx130x.c @@ -133,7 +133,7 @@ static bool sx130x_readable_noinc_reg(struct device *dev, unsigned int reg) } } -static struct regmap_config sx130x_regmap_config = { +const struct regmap_config sx130x_regmap_config = { .reg_bits = 8, .val_bits = 8, @@ -151,6 +151,7 @@ static struct regmap_config sx130x_regmap_config = { .num_ranges = ARRAY_SIZE(sx130x_regmap_ranges), .max_register = SX1301_MAX_REGISTER, }; +EXPORT_SYMBOL_GPL(sx130x_regmap_config); static int sx130x_field_write(struct sx130x_priv *priv, enum sx130x_fields field_id, u8 val) @@ -537,110 +538,98 @@ static const struct net_device_ops sx130x_net_device_ops = { .ndo_start_xmit = sx130x_loradev_start_xmit, }; -static int sx130x_probe(struct spi_device *spi) +int sx130x_early_probe(struct regmap *regmap, struct gpio_desc *rst) { + struct device *dev = regmap_get_device(regmap); struct net_device *netdev; struct sx130x_priv *priv; - struct gpio_desc *rst; int ret; int i; - unsigned int ver; - unsigned int val; - - rst = devm_gpiod_get_optional(&spi->dev, "reset", GPIOD_OUT_LOW); - if (IS_ERR(rst)) { - if (PTR_ERR(rst) != -EPROBE_DEFER) - dev_err(&spi->dev, "Failed to obtain reset GPIO\n"); - return PTR_ERR(rst); - } - - gpiod_set_value_cansleep(rst, 1); - msleep(100); - gpiod_set_value_cansleep(rst, 0); - msleep(100); - - spi->bits_per_word = 8; - spi_setup(spi); - netdev = devm_alloc_loradev(&spi->dev, sizeof(*priv)); + netdev = devm_alloc_loradev(dev, sizeof(*priv)); if (!netdev) return -ENOMEM; netdev->netdev_ops = &sx130x_net_device_ops; - SET_NETDEV_DEV(netdev, &spi->dev); + SET_NETDEV_DEV(netdev, dev); priv = netdev_priv(netdev); + priv->regmap = regmap; priv->rst_gpio = rst; mutex_init(&priv->io_lock); - spi_set_drvdata(spi, netdev); - priv->dev = &spi->dev; - - priv->regmap = devm_regmap_init_spi(spi, &sx130x_regmap_config); - if (IS_ERR(priv->regmap)) { - ret = PTR_ERR(priv->regmap); - dev_err(&spi->dev, "Regmap allocation failed: %d\n", ret); - return ret; - } + dev_set_drvdata(dev, netdev); + priv->dev = dev; for (i = 0; i < ARRAY_SIZE(sx130x_regmap_fields); i++) { const struct reg_field *reg_fields = sx130x_regmap_fields; - priv->regmap_fields[i] = devm_regmap_field_alloc(&spi->dev, + priv->regmap_fields[i] = devm_regmap_field_alloc(dev, priv->regmap, reg_fields[i]); if (IS_ERR(priv->regmap_fields[i])) { ret = PTR_ERR(priv->regmap_fields[i]); - dev_err(&spi->dev, "Cannot allocate regmap field: %d\n", ret); + dev_err(dev, "Cannot allocate regmap field (%d)\n", ret); return ret; } } + return 0; +} +EXPORT_SYMBOL_GPL(sx130x_early_probe); + +int sx130x_probe(struct device *dev) +{ + struct net_device *netdev = dev_get_drvdata(dev); + struct sx130x_priv *priv = netdev_priv(netdev); + unsigned int ver; + unsigned int val; + int ret; ret = regmap_read(priv->regmap, SX1301_VER, &ver); if (ret) { - dev_err(&spi->dev, "version read failed\n"); + dev_err(dev, "version read failed (%d)\n", ret); return ret; } if (ver != SX1301_CHIP_VERSION) { - dev_err(&spi->dev, "unexpected version: %u\n", ver); + dev_err(dev, "unexpected version: %u\n", ver); return -ENXIO; } ret = regmap_write(priv->regmap, SX1301_PAGE, 0); if (ret) { - dev_err(&spi->dev, "page/reset write failed\n"); + dev_err(dev, "page/reset write failed (%d)\n", ret); return ret; } ret = sx130x_field_write(priv, F_SOFT_RESET, 1); if (ret) { - dev_err(&spi->dev, "soft reset failed\n"); + dev_err(dev, "soft reset failed (%d)\n", ret); return ret; } ret = sx130x_field_write(priv, F_GLOBAL_EN, 0); if (ret) { - dev_err(&spi->dev, "gate global clocks failed\n"); + dev_err(dev, "gate global clocks failed (%d)\n", ret); return ret; } ret = sx130x_field_write(priv, F_CLK32M_EN, 0); if (ret) { - dev_err(&spi->dev, "gate 32M clock failed\n"); + dev_err(dev, "gate 32M clock failed (%d)\n", ret); return ret; } ret = sx130x_field_write(priv, F_RADIO_A_EN, 1); if (ret) { - dev_err(&spi->dev, "radio a enable failed\n"); + dev_err(dev, "radio A enable failed (%d)\n", ret); return ret; } ret = sx130x_field_write(priv, F_RADIO_B_EN, 1); if (ret) { - dev_err(&spi->dev, "radio b enable failed\n"); + dev_err(dev, "radio B enable failed (%d)\n", ret); return ret; } @@ -648,7 +637,7 @@ static int sx130x_probe(struct spi_device *spi) ret = sx130x_field_write(priv, F_RADIO_RST, 1); if (ret) { - dev_err(&spi->dev, "radio asert reset failed\n"); + dev_err(dev, "radio assert reset failed (%d)\n", ret); return ret; } @@ -656,13 +645,13 @@ static int sx130x_probe(struct spi_device *spi) ret = sx130x_field_write(priv, F_RADIO_RST, 0); if (ret) { - dev_err(&spi->dev, "radio deasert reset failed\n"); + dev_err(dev, "radio deassert reset failed (%d)\n", ret); return ret; } /* radio */ - ret = devm_sx130x_register_radio_devices(&spi->dev); + ret = devm_sx130x_register_radio_devices(dev); if (ret) return ret; @@ -672,7 +661,7 @@ static int sx130x_probe(struct spi_device *spi) ret = regmap_read(priv->regmap, SX1301_GPMODE, &val); if (ret) { - dev_err(&spi->dev, "GPIO mode read failed\n"); + dev_err(dev, "GPIO mode read failed (%d)\n", ret); goto out; } @@ -680,13 +669,13 @@ static int sx130x_probe(struct spi_device *spi) ret = regmap_write(priv->regmap, SX1301_GPMODE, val); if (ret) { - dev_err(&spi->dev, "GPIO mode write failed\n"); + dev_err(dev, "GPIO mode write failed (%d)\n", ret); goto out; } ret = regmap_read(priv->regmap, SX1301_GPSO, &val); if (ret) { - dev_err(&spi->dev, "GPIO select output read failed\n"); + dev_err(dev, "GPIO select output read failed (%d)\n", ret); goto out; } @@ -695,7 +684,7 @@ static int sx130x_probe(struct spi_device *spi) ret = regmap_write(priv->regmap, SX1301_GPSO, val); if (ret) { - dev_err(&spi->dev, "GPIO select output write failed\n"); + dev_err(dev, "GPIO select output write failed (%d)\n", ret); goto out; } @@ -705,24 +694,66 @@ static int sx130x_probe(struct spi_device *spi) if (ret) goto out; - dev_info(&spi->dev, "SX1301 module probed\n"); + dev_info(dev, "SX1301 module probed\n"); out: mutex_unlock(&priv->io_lock); return ret; } +EXPORT_SYMBOL_GPL(sx130x_probe); -static int sx130x_remove(struct spi_device *spi) +int sx130x_remove(struct device *dev) { - struct net_device *netdev = spi_get_drvdata(spi); + struct net_device *netdev = dev_get_drvdata(dev); unregister_loradev(netdev); - dev_info(&spi->dev, "SX1301 module removed\n"); + dev_info(dev, "SX1301 module removed\n"); return 0; } +EXPORT_SYMBOL_GPL(sx130x_remove); + +static int sx130x_spi_probe(struct spi_device *spi) +{ + struct gpio_desc *rst; + struct regmap *regmap; + int ret; + + rst = devm_gpiod_get_optional(&spi->dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(rst)) { + if (PTR_ERR(rst) != -EPROBE_DEFER) + dev_err(&spi->dev, "Failed to obtain reset GPIO\n"); + return PTR_ERR(rst); + } + + gpiod_set_value_cansleep(rst, 1); + msleep(100); + gpiod_set_value_cansleep(rst, 0); + msleep(100); + + spi->bits_per_word = 8; + spi_setup(spi); + + regmap = devm_regmap_init_spi(spi, &sx130x_regmap_config); + if (IS_ERR(regmap)) { + ret = PTR_ERR(regmap); + dev_err(&spi->dev, "Regmap allocation failed: %d\n", ret); + return ret; + } + + ret = sx130x_early_probe(regmap, rst); + if (ret) + return ret; + + return sx130x_probe(&spi->dev); +} + +static int sx130x_spi_remove(struct spi_device *spi) +{ + return sx130x_remove(&spi->dev);; +} #ifdef CONFIG_OF static const struct of_device_id sx130x_dt_ids[] = { @@ -737,8 +768,8 @@ static struct spi_driver sx130x_spi_driver = { .name = "sx130x", .of_match_table = of_match_ptr(sx130x_dt_ids), }, - .probe = sx130x_probe, - .remove = sx130x_remove, + .probe = sx130x_spi_probe, + .remove = sx130x_spi_remove, }; static int __init sx130x_init(void) diff --git a/include/linux/lora/sx130x.h b/include/linux/lora/sx130x.h index ac4e2e7ae18a..d6f027ef283f 100644 --- a/include/linux/lora/sx130x.h +++ b/include/linux/lora/sx130x.h @@ -9,9 +9,16 @@ #define LORA_SX130X_H #include +#include #include #include +extern const struct regmap_config sx130x_regmap_config; +int sx130x_early_probe(struct regmap *regmap, struct gpio_desc *rst); +int sx130x_probe(struct device *dev); +int sx130x_remove(struct device *dev); + + struct sx130x_radio_device { struct device dev; struct device *concentrator; From patchwork Fri Jan 4 11:21:28 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Andreas_F=C3=A4rber?= X-Patchwork-Id: 1020698 X-Patchwork-Delegate: davem@davemloft.net Return-Path: X-Original-To: patchwork-incoming-netdev@ozlabs.org Delivered-To: patchwork-incoming-netdev@ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netdev-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=suse.de Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 43WMp06HtCz9s9h for ; Fri, 4 Jan 2019 22:22:32 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1727977AbfADLVq (ORCPT ); Fri, 4 Jan 2019 06:21:46 -0500 Received: from mx2.suse.de ([195.135.220.15]:52408 "EHLO mx1.suse.de" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1727625AbfADLVq (ORCPT ); Fri, 4 Jan 2019 06:21:46 -0500 X-Virus-Scanned: by amavisd-new at test-mx.suse.de Received: from relay1.suse.de (unknown [195.135.220.254]) by mx1.suse.de (Postfix) with ESMTP id 1F256AE65; Fri, 4 Jan 2019 11:21:44 +0000 (UTC) From: =?utf-8?q?Andreas_F=C3=A4rber?= To: linux-lpwan@lists.infradead.org, linux-serial@vger.kernel.org Cc: linux-usb@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Johan Hovold , Rob Herring , =?utf-8?q?Andreas_F=C3=A4rber?= , "David S. Miller" , netdev@vger.kernel.org Subject: [PATCH lora-next 2/5] net: lora: sx130x: Prepare storing driver-specific data Date: Fri, 4 Jan 2019 12:21:28 +0100 Message-Id: <20190104112131.14451-3-afaerber@suse.de> X-Mailer: git-send-email 2.16.4 In-Reply-To: <20190104112131.14451-1-afaerber@suse.de> References: <20190104112131.14451-1-afaerber@suse.de> MIME-Version: 1.0 Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org Some drivers (e.g., serdev) may need to access private data not part of the core sx130x_priv, which is inaccessible to other source files. As the sx130x core expects to obtain the net_device from the dev's drvdata, we can't reuse that in derived drivers and need a new field plus helpers. Signed-off-by: Andreas Färber --- drivers/net/lora/sx130x.c | 19 +++++++++++++++++++ include/linux/lora/sx130x.h | 2 ++ 2 files changed, 21 insertions(+) diff --git a/drivers/net/lora/sx130x.c b/drivers/net/lora/sx130x.c index 840052955874..978c921ca5ec 100644 --- a/drivers/net/lora/sx130x.c +++ b/drivers/net/lora/sx130x.c @@ -58,6 +58,7 @@ struct sx130x_priv { struct regmap *regmap; struct regmap_field *regmap_fields[ARRAY_SIZE(sx130x_regmap_fields)]; struct mutex io_lock; + void *drvdata; }; struct regmap *sx130x_get_regmap(struct device *dev) @@ -68,6 +69,24 @@ struct regmap *sx130x_get_regmap(struct device *dev) return priv->regmap; } +void sx130x_set_drvdata(struct device *dev, void *drvdata) +{ + struct net_device *netdev = dev_get_drvdata(dev); + struct sx130x_priv *priv = netdev_priv(netdev); + + priv->drvdata = drvdata; +} +EXPORT_SYMBOL_GPL(sx130x_set_drvdata); + +void *sx130x_get_drvdata(struct device *dev) +{ + struct net_device *netdev = dev_get_drvdata(dev); + struct sx130x_priv *priv = netdev_priv(netdev); + + return priv->drvdata; +} +EXPORT_SYMBOL_GPL(sx130x_get_drvdata); + void sx130x_io_lock(struct device *dev) { struct net_device *netdev = dev_get_drvdata(dev); diff --git a/include/linux/lora/sx130x.h b/include/linux/lora/sx130x.h index d6f027ef283f..85b088ec77b8 100644 --- a/include/linux/lora/sx130x.h +++ b/include/linux/lora/sx130x.h @@ -14,6 +14,8 @@ #include extern const struct regmap_config sx130x_regmap_config; +void sx130x_set_drvdata(struct device *dev, void *drvdata); +void *sx130x_get_drvdata(struct device *dev); int sx130x_early_probe(struct regmap *regmap, struct gpio_desc *rst); int sx130x_probe(struct device *dev); int sx130x_remove(struct device *dev); From patchwork Fri Jan 4 11:21:29 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Andreas_F=C3=A4rber?= X-Patchwork-Id: 1020695 X-Patchwork-Delegate: davem@davemloft.net Return-Path: X-Original-To: patchwork-incoming-netdev@ozlabs.org Delivered-To: patchwork-incoming-netdev@ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netdev-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=suse.de Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 43WMnf4LKhz9s3l for ; Fri, 4 Jan 2019 22:22:14 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1728213AbfADLWK (ORCPT ); Fri, 4 Jan 2019 06:22:10 -0500 Received: from mx2.suse.de ([195.135.220.15]:52448 "EHLO mx1.suse.de" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1727690AbfADLVr (ORCPT ); Fri, 4 Jan 2019 06:21:47 -0500 X-Virus-Scanned: by amavisd-new at test-mx.suse.de Received: from relay2.suse.de (unknown [195.135.220.254]) by mx1.suse.de (Postfix) with ESMTP id C51C1AEB0; Fri, 4 Jan 2019 11:21:44 +0000 (UTC) From: =?utf-8?q?Andreas_F=C3=A4rber?= To: linux-lpwan@lists.infradead.org, linux-serial@vger.kernel.org Cc: linux-usb@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Johan Hovold , Rob Herring , =?utf-8?q?Andreas_F=C3=A4rber?= , "David S. Miller" , netdev@vger.kernel.org Subject: [PATCH lora-next 3/5] net: lora: sx130x: Add PicoCell serdev driver Date: Fri, 4 Jan 2019 12:21:29 +0100 Message-Id: <20190104112131.14451-4-afaerber@suse.de> X-Mailer: git-send-email 2.16.4 In-Reply-To: <20190104112131.14451-1-afaerber@suse.de> References: <20190104112131.14451-1-afaerber@suse.de> MIME-Version: 1.0 Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org The picoGW reference MCU firmware implements a USB CDC or UART interface with a set of serial commands. It can be found on multiple mPCIe cards as well as USB adapters. https://github.com/Lora-net/picoGW_mcu That MCU design superseded earlier attempts of using FTDI chips (MPSSE) for controlling the SPI based chip directly from the host. This commit adds a serdev driver implementing another regmap_bus to abstract the register access and reuses our existing regmap driver on top. Thereby we have an early proof of concept that we can drive both types of modules/cards with a single driver core! It assumes there is only one SX130x (with its radios) accessible, whereas some new cards reportedly also have an SX127x on-board. So the DT/driver design may need to be reconsidered once such a card or documentation becomes available. It also assumes SX1255/1258 are fully compatible with "semtech,sx1257". Currently there's still some bugs to be investigated, with communication stalling on one device and another reading the radio versions wrong (07 / 1f instead of 21, also observed on spi once). Signed-off-by: Andreas Färber --- drivers/net/lora/Makefile | 2 + drivers/net/lora/sx130x_picogw.c | 466 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 468 insertions(+) create mode 100644 drivers/net/lora/sx130x_picogw.c diff --git a/drivers/net/lora/Makefile b/drivers/net/lora/Makefile index c6a0410f80ce..5fef38abf5ed 100644 --- a/drivers/net/lora/Makefile +++ b/drivers/net/lora/Makefile @@ -25,6 +25,8 @@ lora-sx127x-y := sx127x.o obj-$(CONFIG_LORA_SX130X) += lora-sx130x.o lora-sx130x-y := sx130x.o lora-sx130x-y += sx130x_radio.o +obj-$(CONFIG_LORA_SX130X) += lora-sx130x-picogw.o +lora-sx130x-picogw-y := sx130x_picogw.o obj-$(CONFIG_LORA_USI) += lora-usi.o lora-usi-y := usi.o diff --git a/drivers/net/lora/sx130x_picogw.c b/drivers/net/lora/sx130x_picogw.c new file mode 100644 index 000000000000..577f9fb71d46 --- /dev/null +++ b/drivers/net/lora/sx130x_picogw.c @@ -0,0 +1,466 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Semtech SX1301/1308 PicoCell gateway serial MCU interface + * + * Copyright (c) 2018-2019 Andreas Färber + */ +#include +#include +#include +#include +#include +#include +#include +#include + +struct picogw_device { + struct serdev_device *serdev; + + u8 rx_buf[1024]; + int rx_len; + + struct completion answer_comp; + struct completion answer_read_comp; +}; + +static inline struct picogw_device *picogw_get_drvdata(struct serdev_device *sdev) +{ + return sx130x_get_drvdata(&sdev->dev); +} + +static bool picogw_valid_cmd(char ch) +{ + switch (ch) { + case 'k': /* invalid command error */ + case 'r': + case 'w': + case 'l': + return true; + default: + return false; + } +} + +static int picogw_send_cmd(struct picogw_device *picodev, char cmd, + u8 addr, const void *data, u16 data_len) +{ + struct serdev_device *sdev = picodev->serdev; + u8 buf[4]; + int i, ret; + + buf[0] = cmd; + buf[1] = (data_len >> 8) & 0xff; + buf[2] = (data_len >> 0) & 0xff; + buf[3] = addr; + + dev_dbg(&sdev->dev, "%s: %c %02x %02x %02x\n", __func__, buf[0], + (unsigned int)buf[1], (unsigned int)buf[2], (unsigned int)buf[3]); + for (i = 0; i < data_len; i++) { + dev_dbg(&sdev->dev, "%s: data %02x\n", __func__, (unsigned int)((const u8*)data)[i]); + } + + ret = serdev_device_write_buf(sdev, buf, 4); + if (ret < 0) + return ret; + if (ret != 4) + return -EIO; + + if (data_len) { + ret = serdev_device_write_buf(sdev, data, data_len); + if (ret < 0) + return ret; + if (ret != data_len) + return -EIO; + } + + return 0; +} + +static int picogw_recv_answer(struct picogw_device *picodev, + char *cmd, bool *ack, void *buf, int buf_len, + unsigned long timeout) +{ + int len; + + timeout = wait_for_completion_timeout(&picodev->answer_comp, timeout); + if (!timeout) + return -ETIMEDOUT; + + if (cmd) + *cmd = picodev->rx_buf[0]; + + if (ack) + *ack = (picodev->rx_buf[3] == 1); + + len = min(picodev->rx_len - 4, buf_len); + if (buf) + memcpy(buf, picodev->rx_buf + 4, len); + + reinit_completion(&picodev->answer_comp); + complete(&picodev->answer_read_comp); + + return len; +} + +static int picogw_reg_read(struct picogw_device *picodev, u8 addr, u8 *val, unsigned long timeout) +{ + const u8 dummy = 0; + char cmd; + bool ack; + int ret; + + ret = picogw_send_cmd(picodev, 'r', addr, &dummy, false ? 1 : 0); + if (ret) + return ret; + + ret = picogw_recv_answer(picodev, &cmd, &ack, val, 1, timeout); + if (ret < 0) + return ret; + if (cmd != 'r') + return -EIO; + if (!ack || ret != 1) + return -EIO; + + return 0; +} + +static int picogw_reg_write(struct picogw_device *picodev, u8 addr, u8 val, unsigned long timeout) +{ + char cmd; + bool ack; + int ret; + + ret = picogw_send_cmd(picodev, 'w', addr, &val, 1); + if (ret) + return ret; + + ret = picogw_recv_answer(picodev, &cmd, &ack, NULL, 0, timeout); + if (ret < 0) + return ret; + if (cmd != 'w') + return -EIO; + if (!ack || ret != 0) + return -EIO; + + return 0; +} + +static int picogw_mcu_fw_check(struct picogw_device *picodev, + u32 fw_version, u8 *id, unsigned long timeout) +{ + char cmd; + bool ack; + int ret; + + fw_version = cpu_to_be32(fw_version); + ret = picogw_send_cmd(picodev, 'l', 0, &fw_version, sizeof(fw_version)); + if (ret) + return ret; + + ret = picogw_recv_answer(picodev, &cmd, &ack, id, id ? 8 : 0, timeout); + if (ret < 0) + return ret; + if (cmd != 'l') + return -EIO; + if (id && ret != 8) + return -EIO; + + return ack ? 0 : -ENOTSUPP; +} + +static int picogw_regmap_gather_write(void *context, + const void *reg_buf, size_t reg_size, const void *val_buf, size_t val_size) +{ + struct picogw_device *picodev = context; + const u8 *addr_buf = reg_buf; + const u8 *val = val_buf; + u8 addr; + int ret; + + //dev_dbg(&picodev->serdev->dev, "%s: 0x%x (reg_size %zu) 0x%x (val_size %zu)\n", + // __func__, (unsigned int)addr_buf[0], reg_size, (unsigned int)val[0], val_size); + + if (reg_size != 1 || val_size > 0xffff) + return -EINVAL; + + addr = addr_buf[0] & ~BIT(7); + if (val_size == 1) { + ret = picogw_reg_write(picodev, addr, val[0], HZ); + if (ret) + return ret; + return 0; + } else { + /* TODO burst mode */ + dev_err(&picodev->serdev->dev, "burst mode write not yet implemented\n"); + return -ENOTSUPP; + } +} + +static int picogw_regmap_write(void *context, + const void *data_buf, size_t count) +{ + const u8 *data = data_buf; + if (count < 1) + return -EINVAL; + + return picogw_regmap_gather_write(context, data, 1, data + 1, count - 1); +} + +static int picogw_regmap_read(void *context, + const void *reg_buf, size_t reg_size, void *val_buf, size_t val_size) +{ + struct picogw_device *picodev = context; + const u8 *addr_buf = reg_buf; + u8 addr; + int ret; + + //dev_dbg(&picodev->serdev->dev, "%s: 0x%x (reg_size %zu) (val_size %zu)\n", __func__, (unsigned int)addr_buf[0], reg_size, val_size); + + if (reg_size != 1 || val_size > 0xffff) + return -EINVAL; + + addr = addr_buf[0] & ~BIT(7); + if (val_size == 1) { + ret = picogw_reg_read(picodev, addr, val_buf, HZ); + if (ret) + return ret; + return 0; + } else { + /* TODO burst mode */ + dev_err(&picodev->serdev->dev, "burst mode read not yet implemented\n"); + return -ENOTSUPP; + } +} + +static const struct regmap_bus picogw_regmap_bus = { + .write = picogw_regmap_write, + .gather_write = picogw_regmap_gather_write, + .read = picogw_regmap_read, + + .max_raw_write = 0xffff, + .max_raw_read = 0xffff, +}; + +static int picogw_handle_answer(struct picogw_device *picodev) +{ + struct device *dev = &picodev->serdev->dev; + unsigned int data_len = ((u16)picodev->rx_buf[1] << 8) | picodev->rx_buf[2]; + int cmd_len = 4 + data_len; + int i, ret; + + if (picodev->rx_len < 4) + return 0; + + if (cmd_len > sizeof(picodev->rx_buf)) { + dev_warn(dev, "answer too long (%u)\n", data_len); + return 0; + } + + if (picodev->rx_len < cmd_len) { + dev_dbg(dev, "got %u, need %u bytes\n", picodev->rx_len, cmd_len); + return 0; + } + + dev_dbg(dev, "Answer %c =%u %s (%u)\n", picodev->rx_buf[0], + (unsigned int)picodev->rx_buf[3], + (picodev->rx_buf[3] == 1) ? "OK" : "K0", + data_len); + for (i = 0; i < data_len; i++) { + //dev_dbg(dev, "%s: %02x\n", __func__, (unsigned int)picodev->rx_buf[4 + i]); + } + + complete(&picodev->answer_comp); + ret = wait_for_completion_interruptible_timeout(&picodev->answer_read_comp, HZ / 2); + if (ret < 0) + return 0; + + reinit_completion(&picodev->answer_read_comp); + + return cmd_len; +} + +static int picogw_receive_buf(struct serdev_device *sdev, const u8 *data, size_t count) +{ + struct picogw_device *picodev = picogw_get_drvdata(sdev); + size_t i; + int len = 0; + + dev_dbg(&sdev->dev, "Receive (%zu)\n", count); + + if (completion_done(&picodev->answer_comp)) { + dev_info(&sdev->dev, "RX waiting on completion\n"); + return 0; + } + if (picodev->rx_len == sizeof(picodev->rx_buf)) { + dev_warn(&sdev->dev, "RX buffer full\n"); + return 0; + } + + i = min(count, sizeof(picodev->rx_buf) - picodev->rx_len); + if (i > 0) { + memcpy(&picodev->rx_buf[picodev->rx_len], data, i); + picodev->rx_len += i; + len += i; + } + + while (picodev->rx_len > 0) { + /* search for valid beginning */ + for (i = 0; i < picodev->rx_len; i++) { + if (picogw_valid_cmd(picodev->rx_buf[i])) + break; + } + if (i > 0) { + dev_dbg(&sdev->dev, "skipping %zu bytes of garbage\n", i); + if (i < picodev->rx_len) { + memmove(picodev->rx_buf, &picodev->rx_buf[i], picodev->rx_len - i); + picodev->rx_len -= i; + } else + picodev->rx_len = 0; + } + + i = picogw_handle_answer(picodev); + if (i == 0) + break; + + if (i % 64 == 0) { + dev_info(&sdev->dev, "skipping padding byte\n"); + i++; + } + if (picodev->rx_len > i) + memmove(picodev->rx_buf, &picodev->rx_buf[i], picodev->rx_len - i); + if (picodev->rx_len >= i) + picodev->rx_len -= i; + } + + return len; +} + +static const struct serdev_device_ops picogw_serdev_client_ops = { + .receive_buf = picogw_receive_buf, + .write_wakeup = serdev_device_write_wakeup, +}; + +static int picogw_serdev_probe(struct serdev_device *sdev) +{ + struct picogw_device *picodev; + struct regmap *regmap; + u32 fw_version = 0x010a0006; + u8 mac[8]; + int ret; + + //dev_info(&sdev->dev, "Probing\n"); + + picodev = devm_kzalloc(&sdev->dev, sizeof(*picodev), GFP_KERNEL); + if (!picodev) + return -ENOMEM; + + picodev->serdev = sdev; + init_completion(&picodev->answer_comp); + init_completion(&picodev->answer_read_comp); + + ret = serdev_device_open(sdev); + if (ret) { + dev_err(&sdev->dev, "Failed to open (%d)\n", ret); + return ret; + } + + serdev_device_set_baudrate(sdev, 115200); + serdev_device_set_parity(sdev, SERDEV_PARITY_NONE); + serdev_device_set_flow_control(sdev, true); + + regmap = devm_regmap_init(&sdev->dev, &picogw_regmap_bus, picodev, &sx130x_regmap_config); + if (IS_ERR(regmap)) { + ret = PTR_ERR(regmap); + dev_err(&sdev->dev, "error initializing regmap (%d)\n", ret); + serdev_device_close(sdev); + return ret; + } + + ret = sx130x_early_probe(regmap, NULL); + if (ret) { + serdev_device_close(sdev); + return ret; + } + + sx130x_set_drvdata(&sdev->dev, picodev); + serdev_device_set_client_ops(sdev, &picogw_serdev_client_ops); + + //msleep(1000); + ret = picogw_mcu_fw_check(picodev, fw_version, mac, HZ); + if (!ret || ret == -ENOTSUPP) + dev_info(&sdev->dev, "ID = %02x%02x%02x%02x%02x%02x%02x%02x\n", + (unsigned int)mac[0], + (unsigned int)mac[1], + (unsigned int)mac[2], + (unsigned int)mac[3], + (unsigned int)mac[4], + (unsigned int)mac[5], + (unsigned int)mac[6], + (unsigned int)mac[7]); + while (ret == -ENOTSUPP && ((fw_version & 0xff) > 4)) { + ret = picogw_mcu_fw_check(picodev, --fw_version, NULL, HZ); + } + if (ret == -ENOTSUPP) { + dev_warn(&sdev->dev, "firmware check failed (%08x)\n", fw_version); + } else { + dev_err(&sdev->dev, "ID retrieval failed (%d)\n", ret); + serdev_device_close(sdev); + return ret; + } + + ret = sx130x_probe(&sdev->dev); + if (ret) { + serdev_device_close(sdev); + return ret; + } + + //dev_info(&sdev->dev, "Done.\n"); + + return 0; +} + +static void picogw_serdev_remove(struct serdev_device *sdev) +{ + sx130x_remove(&sdev->dev); + + serdev_device_close(sdev); + + //dev_info(&sdev->dev, "Removed\n"); +} + +static const struct of_device_id picogw_serdev_of_match[] = { + { .compatible = "semtech,lora-picocell" }, + {} +}; +MODULE_DEVICE_TABLE(of, picogw_serdev_of_match); + +static struct serdev_device_driver picogw_serdev_driver = { + .probe = picogw_serdev_probe, + .remove = picogw_serdev_remove, + .driver = { + .name = "lora-picogw", + .of_match_table = picogw_serdev_of_match, + }, +}; + +static int __init picogw_serdev_init(void) +{ + int ret; + + ret = serdev_device_driver_register(&picogw_serdev_driver); + if (ret) { + pr_err("serdev_device_driver_register failed (%d)", ret); + return ret; + } + + return 0; +} +module_init(picogw_serdev_init); + +static void __exit picogw_serdev_exit(void) +{ + serdev_device_driver_unregister(&picogw_serdev_driver); +} +module_exit(picogw_serdev_exit); + +MODULE_LICENSE("GPL"); From patchwork Fri Jan 4 11:21:31 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Andreas_F=C3=A4rber?= X-Patchwork-Id: 1020693 X-Patchwork-Delegate: davem@davemloft.net Return-Path: X-Original-To: patchwork-incoming-netdev@ozlabs.org Delivered-To: patchwork-incoming-netdev@ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netdev-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=suse.de Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 43WMnH2m7Nz9s9G for ; Fri, 4 Jan 2019 22:21:55 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726404AbfADLVt (ORCPT ); Fri, 4 Jan 2019 06:21:49 -0500 Received: from mx2.suse.de ([195.135.220.15]:52510 "EHLO mx1.suse.de" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1727625AbfADLVs (ORCPT ); Fri, 4 Jan 2019 06:21:48 -0500 X-Virus-Scanned: by amavisd-new at test-mx.suse.de Received: from relay2.suse.de (unknown [195.135.220.254]) by mx1.suse.de (Postfix) with ESMTP id 125B9AEEB; Fri, 4 Jan 2019 11:21:46 +0000 (UTC) From: =?utf-8?q?Andreas_F=C3=A4rber?= To: linux-lpwan@lists.infradead.org, linux-serial@vger.kernel.org Cc: linux-usb@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Johan Hovold , Rob Herring , =?utf-8?q?Andreas_F=C3=A4rber?= , "David S. Miller" , Oliver Neukum , Greg Kroah-Hartman , netdev@vger.kernel.org Subject: [RFC lora-next 5/5] HACK: net: lora: sx130x: Add PicoCell gateway shim for cdc-acm Date: Fri, 4 Jan 2019 12:21:31 +0100 Message-Id: <20190104112131.14451-6-afaerber@suse.de> X-Mailer: git-send-email 2.16.4 In-Reply-To: <20190104112131.14451-1-afaerber@suse.de> References: <20190104112131.14451-1-afaerber@suse.de> MIME-Version: 1.0 Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org Ignore our device in cdc-acm probing and add a new driver for it, dispatching to cdc-acm for the actual implementation. WARNING: It is likely that this VID/PID is in use for unrelated devices. Only the Product string hints what this really is in current v0.2.1. Previous code v0.2.0 was using a Semtech VID/PID, but no card shipping with such firmware is known to me. While this may seem unorthodox, no internals of the driver are accessed, just the set of driver callbacks is duplicated as shim. Use this shim construct to fake DT nodes for serdev on probe. For testing purposes these nodes do not have a parent yet. This results in two "Error -2 creating of_node link" warnings on probe. Cc: Johan Hovold Cc: Rob Herring Signed-off-by: Andreas Färber --- drivers/net/lora/Makefile | 2 + drivers/net/lora/picogw.c | 337 ++++++++++++++++++++++++++++++++++++++++++++ drivers/usb/class/cdc-acm.c | 4 + 3 files changed, 343 insertions(+) create mode 100644 drivers/net/lora/picogw.c diff --git a/drivers/net/lora/Makefile b/drivers/net/lora/Makefile index 5fef38abf5ed..bdcf7560dd65 100644 --- a/drivers/net/lora/Makefile +++ b/drivers/net/lora/Makefile @@ -27,6 +27,8 @@ lora-sx130x-y := sx130x.o lora-sx130x-y += sx130x_radio.o obj-$(CONFIG_LORA_SX130X) += lora-sx130x-picogw.o lora-sx130x-picogw-y := sx130x_picogw.o +obj-$(CONFIG_LORA_SX130X) += lora-picogw.o +lora-picogw-y := picogw.o obj-$(CONFIG_LORA_USI) += lora-usi.o lora-usi-y := usi.o diff --git a/drivers/net/lora/picogw.c b/drivers/net/lora/picogw.c new file mode 100644 index 000000000000..aa5b83d21bb3 --- /dev/null +++ b/drivers/net/lora/picogw.c @@ -0,0 +1,337 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Semtech PicoCell gateway USB interface + * + * Copyright (c) 2018-2019 Andreas Färber + */ + +#define pr_fmt(fmt) "picocell: " fmt + +#include +#include +#include +#include +#include +#include +#include + +#define PICO_VID 0x0483 +#define PICO_PID 0x5740 + +static struct usb_driver *picogw_get_acm_driver(struct usb_interface *iface) +{ + struct device_driver *drv; + + drv = driver_find("cdc_acm", iface->dev.bus); + if (!drv) + return NULL; + + return to_usb_driver(drv); +} + +static void picogw_kobj_release(struct kobject *kobj) +{ + struct device_node *node = container_of(kobj, struct device_node, kobj); + struct property *prop; + + prop = node->properties; + while (prop) { + struct property *next = prop->next; + kfree(prop); + prop = next; + } + + kfree(node); +} + +static struct kobj_type picogw_kobj_type = { + .release = picogw_kobj_release, +}; + +static u32 picogw_radio_a_reg = cpu_to_be32(0); +static u32 picogw_radio_b_reg = cpu_to_be32(1); + +static int picogw_fake_of_nodes(struct device *dev) +{ + struct device_node *node = NULL; + struct device_node *child, *spi, *radio_a, *radio_b; + struct property *prop; + + node = kzalloc(sizeof(*node), GFP_KERNEL); + if (!node) + return -ENOMEM; + node->name = ""; + node->full_name = "usb0483,5740"; + node->type = ""; + kobject_init(&node->kobj, &picogw_kobj_type); + node->fwnode.ops = &of_fwnode_ops; + + child = kzalloc(sizeof(*child), GFP_KERNEL); + if (!child) { + of_node_put(node); + return -ENOMEM; + } + child->name = "lora"; + child->full_name = "lora"; + child->type = ""; + child->parent = node; + kobject_init(&child->kobj, &picogw_kobj_type); + child->fwnode.ops = &of_fwnode_ops; + node->child = child; + + prop = kzalloc(sizeof(*prop), GFP_KERNEL); + if (!prop) { + of_node_put(child); + of_node_put(node); + return -ENOMEM; + } + prop->name = "compatible"; + prop->value = "semtech,lora-picocell"; + prop->length = 22; + child->properties = prop; + + spi = kzalloc(sizeof(*spi), GFP_KERNEL); + if (!spi) { + of_node_put(child); + of_node_put(node); + return -ENOMEM; + } + spi->name = "radio-spi"; + spi->full_name = "radio-spi"; + spi->type = ""; + spi->parent = child; + kobject_init(&spi->kobj, &picogw_kobj_type); + spi->fwnode.ops = &of_fwnode_ops; + child->child = spi; + + radio_a = kzalloc(sizeof(*radio_a), GFP_KERNEL); + if (!radio_a) { + of_node_put(spi); + of_node_put(child); + of_node_put(node); + return -ENOMEM; + } + radio_a->name = "lora@0"; + radio_a->full_name = "lora@0"; + radio_a->type = ""; + radio_a->parent = spi; + kobject_init(&radio_a->kobj, &picogw_kobj_type); + radio_a->fwnode.ops = &of_fwnode_ops; + spi->child = radio_a; + + prop = kzalloc(sizeof(*prop), GFP_KERNEL); + if (!prop) { + of_node_put(radio_a); + of_node_put(spi); + of_node_put(child); + of_node_put(node); + return -ENOMEM; + } + prop->name = "compatible"; + prop->value = "semtech,sx1257"; + prop->length = 15; + radio_a->properties = prop; + + prop = kzalloc(sizeof(*prop), GFP_KERNEL); + if (!prop) { + of_node_put(radio_a); + of_node_put(spi); + of_node_put(child); + of_node_put(node); + return -ENOMEM; + } + prop->name = "reg"; + prop->value = &picogw_radio_a_reg; + prop->length = 4; + radio_a->properties->next = prop; + + radio_b = kzalloc(sizeof(*radio_b), GFP_KERNEL); + if (!radio_b) { + of_node_put(radio_a); + of_node_put(spi); + of_node_put(child); + of_node_put(node); + return -ENOMEM; + } + radio_b->name = "lora@1"; + radio_b->full_name = "Lora@1"; + radio_b->type = ""; + radio_b->parent = spi; + kobject_init(&radio_b->kobj, &picogw_kobj_type); + radio_b->fwnode.ops = &of_fwnode_ops; + radio_a->sibling = radio_b; + + prop = kzalloc(sizeof(*prop), GFP_KERNEL); + if (!prop) { + of_node_put(radio_b); + of_node_put(radio_a); + of_node_put(spi); + of_node_put(child); + of_node_put(node); + return -ENOMEM; + } + prop->name = "compatible"; + prop->value = "semtech,sx1257"; + prop->length = 15; + radio_b->properties = prop; + + prop = kzalloc(sizeof(*prop), GFP_KERNEL); + if (!prop) { + of_node_put(radio_a); + of_node_put(spi); + of_node_put(child); + of_node_put(node); + return -ENOMEM; + } + prop->name = "reg"; + prop->value = &picogw_radio_b_reg; + prop->length = 4; + radio_b->properties->next = prop; + + dev->of_node = node; + + return 0; +} + +static void picogw_cleanup_of_nodes(struct device *dev) +{ + if (dev->of_node->parent) + return; + + of_node_put(dev->of_node->child->child->child->sibling); /* lora@1 */ + of_node_put(dev->of_node->child->child->child); /* lora@0 */ + of_node_put(dev->of_node->child->child); /* radio-spi*/ + of_node_put(dev->of_node->child); /* serdev */ + of_node_put(dev->of_node); /* usb */ + dev->of_node = NULL; +} + +static int picogw_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_driver *drv; + int ret; + + drv = picogw_get_acm_driver(interface); + if (!drv) { + dev_err(&interface->dev, "driver_find failed\n"); + return -EPROBE_DEFER; + } + + if (!interface->dev.of_node) { + dev_dbg(&interface->dev, "no of_node\n"); + ret = picogw_fake_of_nodes(&interface->dev); + if (ret) + return ret; + } + + ret = drv->probe(interface, id); + if (ret) { + picogw_cleanup_of_nodes(&interface->dev); + return ret; + } + + return 0; +} + +static void picogw_disconnect(struct usb_interface *intf) +{ + struct usb_driver *drv = picogw_get_acm_driver(intf); + + if (drv) + drv->disconnect(intf); + else + dev_warn(&intf->dev, "%s: failed to get cdc_acm driver\n", __func__); + + picogw_cleanup_of_nodes(&intf->dev); +} + +static int picogw_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct usb_driver *drv = picogw_get_acm_driver(intf); + + if (!drv) { + dev_err(&intf->dev, "%s: failed to get cdc_acm driver\n", __func__); + return -ENODEV; + } + + return drv->suspend(intf, message); +} + +static int picogw_resume(struct usb_interface *intf) +{ + struct usb_driver *drv = picogw_get_acm_driver(intf); + + if (!drv) { + dev_err(&intf->dev, "%s: failed to get cdc_acm driver\n", __func__); + return -ENODEV; + } + + return drv->resume(intf); +} + +static int picogw_reset_resume(struct usb_interface *intf) +{ + struct usb_driver *drv = picogw_get_acm_driver(intf); + + if (!drv) { + dev_err(&intf->dev, "%s: failed to get cdc_acm driver\n", __func__); + return -ENODEV; + } + + return drv->reset_resume(intf); +} + +static int picogw_pre_reset(struct usb_interface *intf) +{ + struct usb_driver *drv = picogw_get_acm_driver(intf); + + if (!drv) { + dev_err(&intf->dev, "%s: failed to get cdc_acm driver\n", __func__); + return -ENODEV; + } + + return drv->pre_reset(intf); +} + +static const struct usb_device_id picogw_usb_id_table[] = { + { USB_DEVICE_AND_INTERFACE_INFO(PICO_VID, PICO_PID, + USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM, USB_CDC_ACM_PROTO_AT_V25TER) }, + {} +}; +MODULE_DEVICE_TABLE(usb, picogw_usb_id_table); + +static struct usb_driver picogw_usb_driver = { + .name = "lora-picogw-usb", + .probe = picogw_probe, + .disconnect = picogw_disconnect, + .suspend = picogw_suspend, + .resume = picogw_resume, + .reset_resume = picogw_reset_resume, + .pre_reset = picogw_pre_reset, + .id_table = picogw_usb_id_table, + .supports_autosuspend = 1, + .disable_hub_initiated_lpm = 1, +}; + +static int __init picogw_init(void) +{ + int ret; + + ret = usb_register(&picogw_usb_driver); + if (ret < 0){ + pr_err("usb_register failed (%d)\n", ret); + return ret; + } + + return 0; +} +module_init(picogw_init); + +static void __exit picogw_exit(void) +{ + usb_deregister(&picogw_usb_driver); +} +module_exit(picogw_exit); + +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index c225a586c524..541c23b4fbfe 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -1865,6 +1865,10 @@ static const struct usb_device_id acm_ids[] = { .driver_info = IGNORE_DEVICE, }, + { USB_DEVICE_AND_INTERFACE_INFO(0x0483, 0x5740, + USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM, USB_CDC_ACM_PROTO_AT_V25TER), + .driver_info = IGNORE_DEVICE }, + /* control interfaces without any protocol set */ { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM, USB_CDC_PROTO_NONE) },