From patchwork Thu Sep 19 10:52:35 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?b?SsOpcsO0bWUgUG91aWxsZXI=?= X-Patchwork-Id: 1164540 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=silabs.com Authentication-Results: ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=silabs.onmicrosoft.com header.i=@silabs.onmicrosoft.com header.b="O3ybS/a8"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 46Ytz55nqRz9s7T for ; Thu, 19 Sep 2019 20:54:57 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2389466AbfISKwu (ORCPT ); Thu, 19 Sep 2019 06:52:50 -0400 Received: from mail-eopbgr700070.outbound.protection.outlook.com ([40.107.70.70]:25057 "EHLO NAM04-SN1-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S2387501AbfISKwp (ORCPT ); Thu, 19 Sep 2019 06:52:45 -0400 ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none; b=J34JZfVvhXzatv5bX8sspTzjrtRiBEUvcUvOjohSBVKyBdbZk5SvTbObqC96Pm78hveIwCZdjj6w0LGKJ1tUzBdYNlOszfqH2HfCFTsRYStnoiTLlGz2Kx5R/u36BOOhH9KFJ99u9nKAUki9MYxBjbeiXIXIPieW+yaFQVotU7iEXRPBKmqJk4A5Qh/cNkIIZSFPtaneCufsdX1OZF5mVlZYTLAC9WvtiNILC6dbcCwAK/ERpD0auumo+PztjYpKgwk6Y1qWzq+1u4xw8Da2vmQqiwy/Qc8+p7Tcz7aPp9I+lvR1izifA1DK/oNXnDCPQx7WVKP/A5YPF6qwv5t26Q== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector9901; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=GzkOoL63Vex8BJgy6pIDrlevRSfDGd5ehTa+0O7ZxUg=; b=FGFeQ+9CzDDVTtnT44m26LYNFhXy+a6Pmw2xS8skQWumdND6+WRO4lt1dQeYNpt1OmzApnL4Nr5ZxjUe45OFeX/nn73nP2o73Ubq5/712TnHH5i775g7B/B28+LtPrwRWW4UnzTddPuQqEUbAt60Wew8cqitXE+Mbs6vjrDx+tpbQxW7moQ33jrCn7yxgu3McIVmEfX6uR6mfIhw0Ri0AMN4E+Z3Zx1YB6v3hsGGZzhCk8jjpB+Or0KRl1avM1BpI1jILz+OMjjkBQMpTpa3wEf3lLkhfsuL7Dmlvvt8eKc3z12mwQ47wxPXqYGam4buGwI7tEyiILf9UxZkAJDk3w== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=silabs.com; dmarc=pass action=none header.from=silabs.com; dkim=pass header.d=silabs.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=silabs.onmicrosoft.com; s=selector2-silabs-onmicrosoft-com; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=GzkOoL63Vex8BJgy6pIDrlevRSfDGd5ehTa+0O7ZxUg=; b=O3ybS/a8o2yNt+e3E0MmWoJF77y7SnYbiZF5BVVRGI7eTKSdBanmx9ZJPDAF4F1hu+1shD8dHlmymPCNyi/qA0rrXIjzzxOFuAtZgOD+WFqo1pVva631bbFfEFN6UE1qs8prSbkcGZdgAutZmy3c5wOOob+4jj6Fd2M96aLJ5lE= Received: from MN2PR11MB4063.namprd11.prod.outlook.com (20.179.149.217) by MN2PR11MB3775.namprd11.prod.outlook.com (20.178.253.202) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.2263.17; Thu, 19 Sep 2019 10:52:35 +0000 Received: from MN2PR11MB4063.namprd11.prod.outlook.com ([fe80::45dc:e073:4446:4bf8]) by MN2PR11MB4063.namprd11.prod.outlook.com ([fe80::45dc:e073:4446:4bf8%3]) with mapi id 15.20.2263.023; Thu, 19 Sep 2019 10:52:35 +0000 From: Jerome Pouiller To: "devel@driverdev.osuosl.org" , "linux-wireless@vger.kernel.org" CC: "netdev@vger.kernel.org" , "linux-kernel@vger.kernel.org" , Greg Kroah-Hartman , Kalle Valo , "David S . Miller" , David Le Goff , Jerome Pouiller Subject: [PATCH 02/20] staging: wfx: add support for I/O access Thread-Topic: [PATCH 02/20] staging: wfx: add support for I/O access Thread-Index: AQHVbthYAz5BV9jhoUCLDh42xNi6RQ== Date: Thu, 19 Sep 2019 10:52:35 +0000 Message-ID: <20190919105153.15285-3-Jerome.Pouiller@silabs.com> References: <20190919105153.15285-1-Jerome.Pouiller@silabs.com> In-Reply-To: <20190919105153.15285-1-Jerome.Pouiller@silabs.com> Accept-Language: en-US Content-Language: en-US X-MS-Has-Attach: X-MS-TNEF-Correlator: authentication-results: spf=none (sender IP is ) smtp.mailfrom=Jerome.Pouiller@silabs.com; x-originating-ip: [37.71.187.125] x-ms-publictraffictype: Email x-ms-office365-filtering-correlation-id: 7f17e5d0-eea6-463e-98a5-08d73cef7b1c x-microsoft-antispam: BCL:0; PCL:0; RULEID:(2390118)(7020095)(4652040)(8989299)(5600167)(711020)(4605104)(1401327)(4534185)(4627221)(201703031133081)(201702281549075)(8990200)(2017052603328)(7193020); SRVR:MN2PR11MB3775; x-ms-traffictypediagnostic: MN2PR11MB3775: x-ms-exchange-purlcount: 1 x-ms-exchange-transport-forked: True x-microsoft-antispam-prvs: x-ms-oob-tlc-oobclassifiers: OLM:873; x-forefront-prvs: 016572D96D x-forefront-antispam-report: SFV:NSPM; SFS:(10009020)(366004)(376002)(346002)(39850400004)(136003)(396003)(199004)(189003)(86362001)(256004)(14444005)(8936002)(4326008)(66066001)(36756003)(14454004)(305945005)(64756008)(5660300002)(66476007)(66556008)(66446008)(316002)(476003)(446003)(11346002)(966005)(110136005)(2616005)(66946007)(478600001)(91956017)(25786009)(76116006)(7736002)(71190400001)(81166006)(1076003)(30864003)(2501003)(6506007)(186003)(6436002)(486006)(81156014)(3846002)(6116002)(8676002)(102836004)(107886003)(54906003)(6306002)(76176011)(2906002)(26005)(66574012)(99286004)(6486002)(71200400001)(6512007)(579004); DIR:OUT; SFP:1101; SCL:1; SRVR:MN2PR11MB3775; H:MN2PR11MB4063.namprd11.prod.outlook.com; FPR:; SPF:None; LANG:en; PTR:InfoNoRecords; A:1; MX:1; received-spf: None (protection.outlook.com: silabs.com does not designate permitted sender hosts) x-ms-exchange-senderadcheck: 1 x-microsoft-antispam-message-info: 18DDf3g1l5OJr/aBDThYY0oe7hJEwpxe4jFbQfxm7TMVjJu6i3PJRIk6jJlTKdBi93Slhklrhl6RV/PL/p3s9NBisJ7Zl1CPwk0MXyh0hdQPgPcarFxnjXefkJm8wOAoyKh4DBOxP2LUDwQ6hh9m9z3iSl+j/Wy5GcMqvu4npVmHOJt0aO9XmSGHxlnme8PZvzc4sVhj3mHGP89oPziv7QOJA4f3WwvTqbOKXI3urBlsmj3jDVAdaimitJb+0YF92/Or6ZAz41zOZ/XkzLuEtByH8/iJHGxQOC0lUw7M7Kj4tFgNP7TZcKl5yJJZm99mBOZd+qk65gXnV8WQUVZPif9ZHMlPcNYu+LZj+8hBcxFjzJZ+Ai0xvg5U7B3Ve9E7OIF4zormoaBWCuJr98CXfWVEhNUgX5GnP8bCeVYxfCg= Content-ID: MIME-Version: 1.0 X-OriginatorOrg: silabs.com X-MS-Exchange-CrossTenant-Network-Message-Id: 7f17e5d0-eea6-463e-98a5-08d73cef7b1c X-MS-Exchange-CrossTenant-originalarrivaltime: 19 Sep 2019 10:52:35.0509 (UTC) X-MS-Exchange-CrossTenant-fromentityheader: Hosted X-MS-Exchange-CrossTenant-id: 54dbd822-5231-4b20-944d-6f4abcd541fb X-MS-Exchange-CrossTenant-mailboxtype: HOSTED X-MS-Exchange-CrossTenant-userprincipalname: WJ+pEHfdNgh6et5H7TvuFyONq9UMpqT94JaBeDg7i1/nFnGZr3Ger7mSDhD2rXGEsWgIYKU2eDwyGotybkm3gQ== X-MS-Exchange-Transport-CrossTenantHeadersStamped: MN2PR11MB3775 Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org From: Jérôme Pouiller Introduce bus level communication layer. At this level, 7 registers can be addressed. Notice that SPI driver is able to manage chip reset. SDIO mode relies on an external driver (`mmc-pwrseq`) to reset chip. Signed-off-by: Jérôme Pouiller --- drivers/staging/wfx/bus.h | 17 +++ drivers/staging/wfx/bus_sdio.c | 189 ++++++++++++++++++++++- drivers/staging/wfx/bus_spi.c | 271 ++++++++++++++++++++++++++++++++- drivers/staging/wfx/hwio.h | 48 ++++++ drivers/staging/wfx/main.c | 53 +++++++ drivers/staging/wfx/main.h | 32 ++++ drivers/staging/wfx/wfx.h | 24 +++ 7 files changed, 632 insertions(+), 2 deletions(-) create mode 100644 drivers/staging/wfx/hwio.h create mode 100644 drivers/staging/wfx/main.h create mode 100644 drivers/staging/wfx/wfx.h diff --git a/drivers/staging/wfx/bus.h b/drivers/staging/wfx/bus.h index 8ce871a8a9ff..eb77abc09ec2 100644 --- a/drivers/staging/wfx/bus.h +++ b/drivers/staging/wfx/bus.h @@ -11,6 +11,23 @@ #include #include +#define WFX_REG_CONFIG 0x0 +#define WFX_REG_CONTROL 0x1 +#define WFX_REG_IN_OUT_QUEUE 0x2 +#define WFX_REG_AHB_DPORT 0x3 +#define WFX_REG_BASE_ADDR 0x4 +#define WFX_REG_SRAM_DPORT 0x5 +#define WFX_REG_SET_GEN_R_W 0x6 +#define WFX_REG_FRAME_OUT 0x7 + +struct hwbus_ops { + int (*copy_from_io)(void *bus_priv, unsigned int addr, void *dst, size_t count); + int (*copy_to_io)(void *bus_priv, unsigned int addr, const void *src, size_t count); + void (*lock)(void *bus_priv); + void (*unlock)(void *bus_priv); + size_t (*align_size)(void *bus_priv, size_t size); +}; + extern struct sdio_driver wfx_sdio_driver; extern struct spi_driver wfx_spi_driver; diff --git a/drivers/staging/wfx/bus_sdio.c b/drivers/staging/wfx/bus_sdio.c index 4b26c994f43c..35bcca7ec5dc 100644 --- a/drivers/staging/wfx/bus_sdio.c +++ b/drivers/staging/wfx/bus_sdio.c @@ -8,36 +8,223 @@ #include #include #include +#include #include #include "bus.h" +#include "wfx.h" +#include "hwio.h" +#include "main.h" + +static const struct wfx_platform_data wfx_sdio_pdata = { +}; + +struct wfx_sdio_priv { + struct sdio_func *func; + struct wfx_dev *core; + u8 buf_id_tx; + u8 buf_id_rx; + int of_irq; +}; + +static int wfx_sdio_copy_from_io(void *priv, unsigned int reg_id, + void *dst, size_t count) +{ + struct wfx_sdio_priv *bus = priv; + unsigned int sdio_addr = reg_id << 2; + int ret; + + BUG_ON(reg_id > 7); + WARN(((uintptr_t) dst) & 3, "unaligned buffer size"); + WARN(count & 3, "unaligned buffer address"); + + /* Use queue mode buffers */ + if (reg_id == WFX_REG_IN_OUT_QUEUE) + sdio_addr |= (bus->buf_id_rx + 1) << 7; + ret = sdio_memcpy_fromio(bus->func, dst, sdio_addr, count); + if (!ret && reg_id == WFX_REG_IN_OUT_QUEUE) + bus->buf_id_rx = (bus->buf_id_rx + 1) % 4; + + return ret; +} + +static int wfx_sdio_copy_to_io(void *priv, unsigned int reg_id, + const void *src, size_t count) +{ + struct wfx_sdio_priv *bus = priv; + unsigned int sdio_addr = reg_id << 2; + int ret; + + BUG_ON(reg_id > 7); + WARN(((uintptr_t) src) & 3, "unaligned buffer size"); + WARN(count & 3, "unaligned buffer address"); + + /* Use queue mode buffers */ + if (reg_id == WFX_REG_IN_OUT_QUEUE) + sdio_addr |= bus->buf_id_tx << 7; + // FIXME: discards 'const' qualifier for src + ret = sdio_memcpy_toio(bus->func, sdio_addr, (void *) src, count); + if (!ret && reg_id == WFX_REG_IN_OUT_QUEUE) + bus->buf_id_tx = (bus->buf_id_tx + 1) % 32; + + return ret; +} + +static void wfx_sdio_lock(void *priv) +{ + struct wfx_sdio_priv *bus = priv; + + sdio_claim_host(bus->func); +} + +static void wfx_sdio_unlock(void *priv) +{ + struct wfx_sdio_priv *bus = priv; + + sdio_release_host(bus->func); +} + +static void wfx_sdio_irq_handler(struct sdio_func *func) +{ + struct wfx_sdio_priv *bus = sdio_get_drvdata(func); + + if (bus->core) + /* empty */; + else + WARN(!bus->core, "race condition in driver init/deinit"); +} + +static irqreturn_t wfx_sdio_irq_handler_ext(int irq, void *priv) +{ + struct wfx_sdio_priv *bus = priv; + + if (!bus->core) { + WARN(!bus->core, "race condition in driver init/deinit"); + return IRQ_NONE; + } + sdio_claim_host(bus->func); + sdio_release_host(bus->func); + return IRQ_HANDLED; +} + +static int wfx_sdio_irq_subscribe(struct wfx_sdio_priv *bus) +{ + int ret; + + if (bus->of_irq) { + ret = request_irq(bus->of_irq, wfx_sdio_irq_handler_ext, + IRQF_TRIGGER_RISING, "wfx", bus); + } else { + sdio_claim_host(bus->func); + ret = sdio_claim_irq(bus->func, wfx_sdio_irq_handler); + sdio_release_host(bus->func); + } + return ret; +} + +static int wfx_sdio_irq_unsubscribe(struct wfx_sdio_priv *bus) +{ + int ret; + + if (bus->of_irq) { + free_irq(bus->of_irq, bus); + ret = 0; + } else { + sdio_claim_host(bus->func); + ret = sdio_release_irq(bus->func); + sdio_release_host(bus->func); + } + return ret; +} + +static size_t wfx_sdio_align_size(void *priv, size_t size) +{ + struct wfx_sdio_priv *bus = priv; + + return sdio_align_size(bus->func, size); +} + +static const struct hwbus_ops wfx_sdio_hwbus_ops = { + .copy_from_io = wfx_sdio_copy_from_io, + .copy_to_io = wfx_sdio_copy_to_io, + .lock = wfx_sdio_lock, + .unlock = wfx_sdio_unlock, + .align_size = wfx_sdio_align_size, +}; static const struct of_device_id wfx_sdio_of_match[]; static int wfx_sdio_probe(struct sdio_func *func, const struct sdio_device_id *id) { struct device_node *np = func->dev.of_node; + struct wfx_sdio_priv *bus; + int ret; if (func->num != 1) { dev_err(&func->dev, "SDIO function number is %d while it should always be 1 (unsupported chip?)\n", func->num); return -ENODEV; } + bus = devm_kzalloc(&func->dev, sizeof(*bus), GFP_KERNEL); + if (!bus) + return -ENOMEM; + if (np) { if (!of_match_node(wfx_sdio_of_match, np)) { dev_warn(&func->dev, "no compatible device found in DT\n"); return -ENODEV; } + bus->of_irq = irq_of_parse_and_map(np, 0); } else { dev_warn(&func->dev, "device is not declared in DT, features will be limited\n"); // FIXME: ignore VID/PID and only rely on device tree // return -ENODEV; } - return -EIO; // FIXME: not yet supported + + bus->func = func; + sdio_set_drvdata(func, bus); + func->card->quirks |= MMC_QUIRK_LENIENT_FN0 | MMC_QUIRK_BLKSZ_FOR_BYTE_MODE | MMC_QUIRK_BROKEN_BYTE_MODE_512; + + sdio_claim_host(func); + ret = sdio_enable_func(func); + // Block of 64 bytes is more efficient than 512B for frame sizes < 4k + sdio_set_block_size(func, 64); + sdio_release_host(func); + if (ret) + goto err0; + + ret = wfx_sdio_irq_subscribe(bus); + if (ret) + goto err1; + + bus->core = wfx_init_common(&func->dev, &wfx_sdio_pdata, + &wfx_sdio_hwbus_ops, bus); + if (!bus->core) { + ret = -EIO; + goto err2; + } + + return 0; + +err2: + wfx_sdio_irq_unsubscribe(bus); +err1: + sdio_claim_host(func); + sdio_disable_func(func); + sdio_release_host(func); +err0: + return ret; } static void wfx_sdio_remove(struct sdio_func *func) { + struct wfx_sdio_priv *bus = sdio_get_drvdata(func); + + wfx_free_common(bus->core); + wfx_sdio_irq_unsubscribe(bus); + sdio_claim_host(func); + sdio_disable_func(func); + sdio_release_host(func); } #define SDIO_VENDOR_ID_SILABS 0x0000 diff --git a/drivers/staging/wfx/bus_spi.c b/drivers/staging/wfx/bus_spi.c index 574b60f513e9..b311ff72cf80 100644 --- a/drivers/staging/wfx/bus_spi.c +++ b/drivers/staging/wfx/bus_spi.c @@ -7,19 +7,288 @@ * Copyright (c) 2010, ST-Ericsson */ #include +#include +#include +#include +#include #include +#include #include #include "bus.h" +#include "wfx.h" +#include "hwio.h" +#include "main.h" + +#define DETECT_INVALID_CTRL_ACCESS + +static int gpio_reset = -2; +module_param(gpio_reset, int, 0644); +MODULE_PARM_DESC(gpio_reset, "gpio number for reset. -1 for none."); + +#define SET_WRITE 0x7FFF /* usage: and operation */ +#define SET_READ 0x8000 /* usage: or operation */ + +static const struct wfx_platform_data wfx_spi_pdata = { +}; + +struct wfx_spi_priv { + struct spi_device *func; + struct wfx_dev *core; + struct gpio_desc *gpio_reset; + struct work_struct request_rx; + bool need_swab; +}; + +/* + * Read of control register need a particular attention because it should be + * done only after an IRQ raise. We can detect if this event happens by reading + * control register twice (it is safe to read twice since we can garantee that + * no data acess was done since IRQ raising). In add, this function optimize it + * by doing only one SPI request. + */ +#if (KERNEL_VERSION(4, 19, 14) > LINUX_VERSION_CODE) +static int wfx_spi_read_ctrl_reg(struct wfx_spi_priv *bus, u16 *dst) +{ + int i, ret = 0; + u16 tx_buf[4] = { }; + u16 rx_buf[4] = { }; + u16 tmp[2] = { }; + struct spi_message m; + struct spi_transfer t = { + .rx_buf = rx_buf, + .tx_buf = tx_buf, + .len = sizeof(tx_buf), + }; + u16 regaddr = (WFX_REG_CONTROL << 12) | (sizeof(u16) / 2) | SET_READ; + + cpu_to_le16s(regaddr); + if (bus->need_swab) + swab16s(®addr); + + tx_buf[0] = tx_buf[2] = regaddr; + spi_message_init(&m); + spi_message_add_tail(&t, &m); + for (i = 0, tmp[0] = tmp[1] + 1; tmp[0] != tmp[1] && i < 3; i++) { + ret = spi_sync(bus->func, &m); + // Changes of gpio-wakeup can occur during control register + // access. In this case, CTRL_WLAN_READY may differs. + tmp[0] = rx_buf[1] & cpu_to_le16(~CTRL_WLAN_READY); + tmp[1] = rx_buf[3] & cpu_to_le16(~CTRL_WLAN_READY); + } + if (tmp[0] != tmp[1]) + ret = -ETIMEDOUT; + else if (i > 1) + dev_info(bus->core->dev, "success read after %d failures\n", i - 1); + + *dst = rx_buf[1]; + return ret; +} +#endif + +/* + * WFx chip read data 16bits at time and place them directly into (little + * endian) CPU register. So, chip expect byte order like "B1 B0 B3 B2" (while + * LE is "B0 B1 B2 B3" and BE is "B3 B2 B1 B0") + * + * A little endian host with bits_per_word == 16 should do the right job + * natively. The code below to support big endian host and commonly used SPI + * 8bits. + */ +static int wfx_spi_copy_from_io(void *priv, unsigned int addr, + void *dst, size_t count) +{ + struct wfx_spi_priv *bus = priv; + u16 regaddr = (addr << 12) | (count / 2) | SET_READ; + struct spi_message m; + struct spi_transfer t_addr = { + .tx_buf = ®addr, + .len = sizeof(regaddr), + }; + struct spi_transfer t_msg = { + .rx_buf = dst, + .len = count, + }; + u16 *dst16 = dst; +#if (KERNEL_VERSION(4, 19, 14) > LINUX_VERSION_CODE) + u8 *dst8 = dst; +#endif + int ret, i; + + WARN(count % 2, "buffer size must be a multiple of 2"); + +#if (KERNEL_VERSION(4, 19, 14) > LINUX_VERSION_CODE) + /* Some SPI driver (and especially Raspberry one) have race conditions + * during SPI transfers. It impact last byte of transfer. Work around + * bellow try to detect and solve them. + * See https://github.com/raspberrypi/linux/issues/2200 + */ + if (addr == WFX_REG_IN_OUT_QUEUE) + dst8[count - 1] = 0xFF; +#endif + + cpu_to_le16s(®addr); + if (bus->need_swab) + swab16s(®addr); + + spi_message_init(&m); + spi_message_add_tail(&t_addr, &m); + spi_message_add_tail(&t_msg, &m); + ret = spi_sync(bus->func, &m); + +#if (KERNEL_VERSION(4, 19, 14) > LINUX_VERSION_CODE) + /* If last byte has not been overwritten, read ctrl_reg manually + */ + if (addr == WFX_REG_IN_OUT_QUEUE && !ret && dst8[count - 1] == 0xFF) { + dev_warn(bus->core->dev, "SPI DMA error detected (and resolved)\n"); + ret = wfx_spi_read_ctrl_reg(bus, (u16 *) (dst8 + count - 2)); + } +#endif + + if (bus->need_swab && addr == WFX_REG_CONFIG) + for (i = 0; i < count / 2; i++) + swab16s(&dst16[i]); + return ret; +} + +static int wfx_spi_copy_to_io(void *priv, unsigned int addr, + const void *src, size_t count) +{ + struct wfx_spi_priv *bus = priv; + u16 regaddr = (addr << 12) | (count / 2); + // FIXME: use a bounce buffer + u16 *src16 = (void *) src; + int ret, i; + struct spi_message m; + struct spi_transfer t_addr = { + .tx_buf = ®addr, + .len = sizeof(regaddr), + }; + struct spi_transfer t_msg = { + .tx_buf = src, + .len = count, + }; + + WARN(count % 2, "buffer size must be a multiple of 2"); + WARN(regaddr & SET_READ, "bad addr or size overflow"); + + cpu_to_le16s(®addr); + + if (bus->need_swab) + swab16s(®addr); + if (bus->need_swab && addr == WFX_REG_CONFIG) + for (i = 0; i < count / 2; i++) + swab16s(&src16[i]); + + spi_message_init(&m); + spi_message_add_tail(&t_addr, &m); + spi_message_add_tail(&t_msg, &m); + ret = spi_sync(bus->func, &m); + + if (bus->need_swab && addr == WFX_REG_CONFIG) + for (i = 0; i < count / 2; i++) + swab16s(&src16[i]); + return ret; +} + +static void wfx_spi_lock(void *priv) +{ +} + +static void wfx_spi_unlock(void *priv) +{ +} + +static irqreturn_t wfx_spi_irq_handler(int irq, void *priv) +{ + struct wfx_spi_priv *bus = priv; + + if (!bus->core) { + WARN(!bus->core, "race condition in driver init/deinit"); + return IRQ_NONE; + } + queue_work(system_highpri_wq, &bus->request_rx); + return IRQ_HANDLED; +} + +static void wfx_spi_request_rx(struct work_struct *work) +{ +} + +static size_t wfx_spi_align_size(void *priv, size_t size) +{ + // Most of SPI controllers avoid DMA if buffer size is not 32bit aligned + return ALIGN(size, 4); +} + +static const struct hwbus_ops wfx_spi_hwbus_ops = { + .copy_from_io = wfx_spi_copy_from_io, + .copy_to_io = wfx_spi_copy_to_io, + .lock = wfx_spi_lock, + .unlock = wfx_spi_unlock, + .align_size = wfx_spi_align_size, +}; static int wfx_spi_probe(struct spi_device *func) { - return -EIO; + struct wfx_spi_priv *bus; + int ret; + + if (!func->bits_per_word) + func->bits_per_word = 16; + ret = spi_setup(func); + if (ret) + return ret; + // Trace below is also displayed by spi_setup() if compiled with DEBUG + dev_dbg(&func->dev, "SPI params: CS=%d, mode=%d bits/word=%d speed=%d\n", + func->chip_select, func->mode, func->bits_per_word, func->max_speed_hz); + if (func->bits_per_word != 16 && func->bits_per_word != 8) + dev_warn(&func->dev, "unusual bits/word value: %d\n", func->bits_per_word); + if (func->max_speed_hz > 49000000) + dev_warn(&func->dev, "%dHz is a very high speed\n", func->max_speed_hz); + + bus = devm_kzalloc(&func->dev, sizeof(*bus), GFP_KERNEL); + if (!bus) + return -ENOMEM; + bus->func = func; + if (func->bits_per_word == 8 || IS_ENABLED(CONFIG_CPU_BIG_ENDIAN)) + bus->need_swab = true; + spi_set_drvdata(func, bus); + + bus->gpio_reset = wfx_get_gpio(&func->dev, gpio_reset, "reset"); + if (!bus->gpio_reset) { + dev_warn(&func->dev, "try to load firmware anyway\n"); + } else { + gpiod_set_value(bus->gpio_reset, 0); + udelay(100); + gpiod_set_value(bus->gpio_reset, 1); + udelay(2000); + } + + ret = devm_request_irq(&func->dev, func->irq, wfx_spi_irq_handler, + IRQF_TRIGGER_RISING, "wfx", bus); + if (ret) + return ret; + + INIT_WORK(&bus->request_rx, wfx_spi_request_rx); + bus->core = wfx_init_common(&func->dev, &wfx_spi_pdata, + &wfx_spi_hwbus_ops, bus); + if (!bus->core) + return -EIO; + + return ret; } /* Disconnect Function to be called by SPI stack when device is disconnected */ static int wfx_spi_disconnect(struct spi_device *func) { + struct wfx_spi_priv *bus = spi_get_drvdata(func); + + wfx_free_common(bus->core); + // A few IRQ will be sent during device release. Hopefully, no IRQ + // should happen after wdev/wvif are released. + devm_free_irq(&func->dev, func->irq, bus); + flush_work(&bus->request_rx); return 0; } diff --git a/drivers/staging/wfx/hwio.h b/drivers/staging/wfx/hwio.h new file mode 100644 index 000000000000..c490014c1df8 --- /dev/null +++ b/drivers/staging/wfx/hwio.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Low-level API. + * + * Copyright (c) 2017-2018, Silicon Laboratories, Inc. + * Copyright (c) 2010, ST-Ericsson + */ +#ifndef WFX_HWIO_H +#define WFX_HWIO_H + +#define CFG_ERR_SPI_FRAME 0x00000001 // only with SPI +#define CFG_ERR_SDIO_BUF_MISMATCH 0x00000001 // only with SDIO +#define CFG_ERR_BUF_UNDERRUN 0x00000002 +#define CFG_ERR_DATA_IN_TOO_LARGE 0x00000004 +#define CFG_ERR_HOST_NO_OUT_QUEUE 0x00000008 +#define CFG_ERR_BUF_OVERRUN 0x00000010 +#define CFG_ERR_DATA_OUT_TOO_LARGE 0x00000020 +#define CFG_ERR_HOST_NO_IN_QUEUE 0x00000040 +#define CFG_ERR_HOST_CRC_MISS 0x00000080 // only with SDIO +#define CFG_SPI_IGNORE_CS 0x00000080 // only with SPI +#define CFG_WORD_MODE_MASK 0x00000300 // Bytes ordering (only writable in SPI): +#define CFG_WORD_MODE0 0x00000000 // B1,B0,B3,B2 (In SPI, register address and CONFIG data always use this mode) +#define CFG_WORD_MODE1 0x00000100 // B3,B2,B1,B0 +#define CFG_WORD_MODE2 0x00000200 // B0,B1,B2,B3 (SDIO) +#define CFG_DIRECT_ACCESS_MODE 0x00000400 // Direct or queue access mode +#define CFG_PREFETCH_AHB 0x00000800 +#define CFG_DISABLE_CPU_CLK 0x00001000 +#define CFG_PREFETCH_SRAM 0x00002000 +#define CFG_CPU_RESET 0x00004000 +#define CFG_SDIO_DISABLE_IRQ 0x00008000 // only with SDIO +#define CFG_IRQ_ENABLE_DATA 0x00010000 +#define CFG_IRQ_ENABLE_WRDY 0x00020000 +#define CFG_CLK_RISE_EDGE 0x00040000 +#define CFG_SDIO_DISABLE_CRC_CHK 0x00080000 // only with SDIO +#define CFG_RESERVED 0x00F00000 +#define CFG_DEVICE_ID_MAJOR 0x07000000 +#define CFG_DEVICE_ID_RESERVED 0x78000000 +#define CFG_DEVICE_ID_TYPE 0x80000000 + +#define CTRL_NEXT_LEN_MASK 0x00000FFF +#define CTRL_WLAN_WAKEUP 0x00001000 +#define CTRL_WLAN_READY 0x00002000 + +#define IGPR_RW 0x80000000 +#define IGPR_INDEX 0x7F000000 +#define IGPR_VALUE 0x00FFFFFF + +#endif /* WFX_HWIO_H */ diff --git a/drivers/staging/wfx/main.c b/drivers/staging/wfx/main.c index cd69f955f531..744445ef597c 100644 --- a/drivers/staging/wfx/main.c +++ b/drivers/staging/wfx/main.c @@ -11,10 +11,15 @@ * Copyright (c) 2004-2006 Jean-Baptiste Note , et al. */ #include +#include +#include +#include #include #include #include +#include "main.h" +#include "wfx.h" #include "bus.h" #include "wfx_version.h" @@ -23,6 +28,54 @@ MODULE_AUTHOR("Jérôme Pouiller "); MODULE_LICENSE("GPL"); MODULE_VERSION(WFX_LABEL); +struct gpio_desc *wfx_get_gpio(struct device *dev, int override, const char *label) +{ + struct gpio_desc *ret; + char label_buf[256]; + + if (override >= 0) { + snprintf(label_buf, sizeof(label_buf), "wfx_%s", label); + ret = ERR_PTR(devm_gpio_request_one(dev, override, GPIOF_OUT_INIT_LOW, label_buf)); + if (!ret) + ret = gpio_to_desc(override); + } else if (override == -1) { + ret = NULL; + } else { + ret = devm_gpiod_get(dev, label, GPIOD_OUT_LOW); + } + if (IS_ERR(ret) || !ret) { + if (!ret || PTR_ERR(ret) == -ENOENT) + dev_warn(dev, "gpio %s is not defined\n", label); + else + dev_warn(dev, "error while requesting gpio %s\n", label); + ret = NULL; + } else { + dev_dbg(dev, "using gpio %d for %s\n", desc_to_gpio(ret), label); + } + return ret; +} + +struct wfx_dev *wfx_init_common(struct device *dev, + const struct wfx_platform_data *pdata, + const struct hwbus_ops *hwbus_ops, + void *hwbus_priv) +{ + struct wfx_dev *wdev; + + wdev = devm_kmalloc(dev, sizeof(*wdev), GFP_KERNEL); + if (!wdev) + return NULL; + wdev->dev = dev; + wdev->hwbus_ops = hwbus_ops; + wdev->hwbus_priv = hwbus_priv; + memcpy(&wdev->pdata, pdata, sizeof(*pdata)); + return wdev; +} + +void wfx_free_common(struct wfx_dev *wdev) +{ +} + static int __init wfx_core_init(void) { int ret = 0; diff --git a/drivers/staging/wfx/main.h b/drivers/staging/wfx/main.h new file mode 100644 index 000000000000..82222edf998b --- /dev/null +++ b/drivers/staging/wfx/main.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Device probe and register. + * + * Copyright (c) 2017-2019, Silicon Laboratories, Inc. + * Copyright (c) 2010, ST-Ericsson + * Copyright (c) 2006, Michael Wu + * Copyright 2004-2006 Jean-Baptiste Note , et al. + */ +#ifndef WFX_MAIN_H +#define WFX_MAIN_H + +#include +#include + +#include "bus.h" + +struct wfx_dev; + +struct wfx_platform_data { +}; + +struct wfx_dev *wfx_init_common(struct device *dev, + const struct wfx_platform_data *pdata, + const struct hwbus_ops *hwbus_ops, + void *hwbus_priv); +void wfx_free_common(struct wfx_dev *wdev); + +struct gpio_desc *wfx_get_gpio(struct device *dev, int override, + const char *label); + +#endif diff --git a/drivers/staging/wfx/wfx.h b/drivers/staging/wfx/wfx.h new file mode 100644 index 000000000000..9716acc981df --- /dev/null +++ b/drivers/staging/wfx/wfx.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Common private data for Silicon Labs WFx chips. + * + * Copyright (c) 2017-2019, Silicon Laboratories, Inc. + * Copyright (c) 2010, ST-Ericsson + * Copyright (c) 2006, Michael Wu + * Copyright 2004-2006 Jean-Baptiste Note , et al. + */ +#ifndef WFX_H +#define WFX_H + +#include "main.h" + +struct hwbus_ops; + +struct wfx_dev { + struct wfx_platform_data pdata; + struct device *dev; + const struct hwbus_ops *hwbus_ops; + void *hwbus_priv; +}; + +#endif /* WFX_H */