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: 154766 Delivered-To: patch@linaro.org Received: by 2002:a2e:299d:0:0:0:0:0 with SMTP id p29-v6csp511895ljp; Fri, 4 Jan 2019 03:21:57 -0800 (PST) X-Google-Smtp-Source: AFSGD/XgJ4X0A5DGkMNxfI8A68kws4mEt5YDT3JBP4ulLO3B2a8cEgS/phEcYtPXwet6xtS5+DzZ X-Received: by 2002:a62:9683:: with SMTP id s3mr51648664pfk.60.1546600917469; Fri, 04 Jan 2019 03:21:57 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1546600917; cv=none; d=google.com; s=arc-20160816; b=hLz4xxsBcgjGkRZg9LLVtt8MAdfoQnk+y7fPYvPi7/p37rSPP1XI7xULpWWNAThizm roMnc7YB7GdTqnKW8qneyMqCsWt4SuLzHNfBEY7gHou7/BkgkaNxQyi2YCXkfIJvG6zH BPYFav8AU2OVDEfi77UcLUM7n03kw1zllnxTeaE5xYbDFYtTg7JB0f9QStSjRManA3jL Meo5IYqWUb7Xjf/PBezRp9bd5E9exR9pcsL9nHBgNxA6zhHGEB+WhNkV36Antd/9HJYo If46VoHKWbj7Rl+IvrAnHnBNCnI4Ju/3PQWy184Rt/wZad/MFnB4j2iOugZ4eFneYK+Z O5lQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from; bh=rIIveaTb2zTWkGSyrh9Dmw4C0svCazM6ppcCm5BmRlo=; b=tCHhTJj4WMBG51xwt29jmGosgzw68uIDhcr55qakC+U6sMg4cv8y3uEmjB8MC/M4n3 JJBLcLiCE34NwqpwyxbguYXfWGV7jja6fIxUHFVbA12i0Ecd57SircEvMGaTS8o8Lm1G ZTCjN7u4mo/DjP5hwrKCD1uJnOEWUu2y5MSSz7arNTKZScRonMqW3FJIHRlQyCfw1GVs euGQzTW5i4bpAQ9DvgYybTgl0SFNxlLDjOQABRHbEG+9tP/KYaYglhOSHjtfzJOO88Qd xGmYPMEgi7Cf00rZrqnxcE+JGIcJhdccFi/X0EwWOGC5IHRoDxw8UNM1/3AZtzgZYm3L MVtA== ARC-Authentication-Results: i=1; mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id e11si9684376pls.71.2019.01.04.03.21.56; Fri, 04 Jan 2019 03:21:57 -0800 (PST) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1728130AbfADLVt (ORCPT + 31 others); Fri, 4 Jan 2019 06:21:49 -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: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@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(-) -- 2.16.4 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: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: 154768 Delivered-To: patch@linaro.org Received: by 2002:a2e:299d:0:0:0:0:0 with SMTP id p29-v6csp512122ljp; Fri, 4 Jan 2019 03:22:13 -0800 (PST) X-Google-Smtp-Source: AFSGD/Xn8J5dD69kGW8G+CIRKsMrrtp62BFj0ljJHwzbKrnoWqz1QjxEZxsp15i5sZA/U3hk9Tpj X-Received: by 2002:a62:8949:: with SMTP id v70mr50800836pfd.85.1546600933378; Fri, 04 Jan 2019 03:22:13 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1546600933; cv=none; d=google.com; s=arc-20160816; b=GEkGf0WMjitt9qK0izZLNm8TKxRqaLo7DjDitGI1dZT2VYvJxl9sMhy1naQMgEr/eX zYmRC8rh9I3dbW0CTBhBy6fuYEm42hKoYsRN3PvoPX+IjwzXpZ6mDdiILTfLL1GP/P+z I1hjKr3pO1VGbWHhjfvryiQ1VCQU0LMZMotFGar2NbHgs/F1oOY6HYhV8mTSZRsVhqTP fC1M7A0YBTxm3GFF8lnHwh/iaF08uySZOGuQjvnmlRqkEhh3Kc0/jOWY1/y7MaIxglKD YBDnwIzFSvRCI24kU4usHumbwyO+mxvssNYfWFr8wflrboyu52pvc029psCJWrpUq8Fr m3JQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from; bh=SHa8c9dGIIyD/bim6z04UohqFeM6Wc/vfAQrNiqPfjA=; b=py6zVrXpiiCaWqTeg9G32seJJm/TdA4z4tJuaQ5UW0ZJ9MHDJ9R4a/iOzHeH8twsoW Q9h1E+H6DBQSm2l0JSGeHkEp7Oc7LUUlTdUhH5VoQSMJaxT+k+XM6pdJwnJHh+t011yj riRAwjSu/G5pSysHR9weJMlf0DcSiXMcV0CnfmOFElewds4zywvBSLhQv1XymbIIHf+1 /8U5759M4vLDKmMttxXdW0Cz1J9FiG7CZRtLQnmxcOSAT7g02An9zSPu5pRtFLrdOgFb FqI3mMxSzw87s27rdvFBATtTMDzlrxJ0Cs9KsjU+ODi22wfiXk6TuElIbZV0w/prkNT/ gUkA== ARC-Authentication-Results: i=1; mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id d34si2947075pla.80.2019.01.04.03.22.13; Fri, 04 Jan 2019 03:22:13 -0800 (PST) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1728230AbfADLWL (ORCPT + 31 others); Fri, 4 Jan 2019 06:22:11 -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: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@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 -- 2.16.4 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: 154765 Delivered-To: patch@linaro.org Received: by 2002:a2e:299d:0:0:0:0:0 with SMTP id p29-v6csp511835ljp; Fri, 4 Jan 2019 03:21:52 -0800 (PST) X-Google-Smtp-Source: ALg8bN4Xhs0+vd9M5gJL6kNagV9aoLgftsFa6rhcK9MS5L45Z4WEGubeCnF6w0puGPj39h84YBS+ X-Received: by 2002:a17:902:82c2:: with SMTP id u2mr50458316plz.110.1546600912191; Fri, 04 Jan 2019 03:21:52 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1546600912; cv=none; d=google.com; s=arc-20160816; b=HsFnFhJJGsBHAR1W/4liYzoVyONOVwU7kSxoyCWTgr3SA+/ZVGJbRlmRaHa6fAWqJL 01ccS42sOEvWCou8kZvBj8h7xel8NDXg+65kl+Q2D9OrqCb0HtNHkG5d1aoHIwCi4u19 0+idrqzLakCxzapLMAAoLgUwH5sSo613gp//IKtC2hFRDdrZoVDbZs2IfQa21ml1lpNy EiVhVQdbkdL68Qu/wHddrrYisrZGrnt7SEX2KRh/9lpCFxk+RCwDwmoR82einYFcutAO GXzodR3k37plmVjBDDf2TXQRXcBgpY5WuNvK4d5fyZ746prsJb9sZ5+icPb1+23rDRDG ISwg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from; bh=LYDa74T3lwAJBZCRXW5E0G49Jow25lyt7+0JmEOggNk=; b=XMRE9y5jrjmSni5hns8e52V2a8x/FpHc0SooHZhkFtMrGyBZpdO5WGULGwL+Oad/WL kJovWT7T183bql+eJkCXhaSRPu5FNI+XXj7B11FedPRfRxLvFBMtYXGv/MDPCEaEM0zo ExHTHmEvGd2fPAxfmT2wXLVrFkoMpUXSZIZ5lczf8IqYRHu/WJ1I/9Wpx8J1/S97Fz9M P3KVJkPIdkNiAwduk9D8xbD7e+diAFXMaVwE9HqFg6SpF3fgUpVt7tHhGraDQ/ULwM32 wXxdh8OnzD2CFL2cLi3MCetsfHs4cfNHMiPm9MZtSOv4tyT2qYQ8i6//MA4UjoEapdIf lA/A== ARC-Authentication-Results: i=1; mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id e11si9684376pls.71.2019.01.04.03.21.51; Fri, 04 Jan 2019 03:21:52 -0800 (PST) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1728174AbfADLVu (ORCPT + 31 others); Fri, 4 Jan 2019 06:21:50 -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: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@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 -- 2.16.4 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) },