From nobody Wed Nov 27 08:37:43 2024 Received: from mx01lb.world4you.com (mx01lb.world4you.com [81.19.149.111]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9D47C1D0DE2 for ; Fri, 11 Oct 2024 19:56:12 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=81.19.149.111 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1728676574; cv=none; b=GCTxC2+n+IqjrQW3cSWHkBOJLzfjPuaVhCcyXG/qiJccbDC5BRZqNBAkRA2qFxfQa/Ioelln1nDY+3ZoKqKkjIJOM3yiZ/Pw3AI7r3rfleBKotGe0eWOiu2nWNWUgGI5PdjHlw6iFGgi/il2m99xLWyFNUEMcYoIiNl3jzoxCSA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1728676574; c=relaxed/simple; bh=HZGxsWYk/iPyLqh2EsxbJE55SAofMP7wFZ35hrMHnaM=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=ldPddxzu/TW+YMWaOUuUZ022/cr5Tv6PVerK6+v0eFbYBd0cxskWggxthips3VwNdgL7ZIMl8uOYRwRVFMe6tnWldtHEqJvWnotVo1IY0IUCxnikmcmuqDkIJyjNc4V5FmyvkJQx6tMqbssD7H2pKz4qEQgFMxEzFdsGkmG4XhQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=engleder-embedded.com; spf=pass smtp.mailfrom=engleder-embedded.com; dkim=pass (1024-bit key) header.d=engleder-embedded.com header.i=@engleder-embedded.com header.b=RF+pU5th; arc=none smtp.client-ip=81.19.149.111 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=engleder-embedded.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=engleder-embedded.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=engleder-embedded.com header.i=@engleder-embedded.com header.b="RF+pU5th" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=engleder-embedded.com; s=dkim11; h=Content-Transfer-Encoding:MIME-Version: References:In-Reply-To:Message-Id:Date:Subject:Cc:To:From:Sender:Reply-To: Content-Type:Content-ID:Content-Description:Resent-Date:Resent-From: Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help: List-Unsubscribe:List-Subscribe:List-Post:List-Owner:List-Archive; bh=V5Mf4Iwz4W9Y/g8xKrGIhiDltK7NvtlbbAUT15IbKPA=; b=RF+pU5thspb5MP357BaNgUN+Fk bFUGCwVPzQj2bfVQZDYSyqDeYBZtkJHAff8crJnTTSZeV287R8q87MU2qxmtQbsJD3H1l4dvb+liC b+YkZLfhP5Ro7EOCu9hvzTkQCqn1G4rFPPV2giKO3r66g2cUm8xWzkIO5ZGGliPaRgU0=; Received: from 88-117-56-173.adsl.highway.telekom.at ([88.117.56.173] helo=hornet.engleder.at) by mx01lb.world4you.com with esmtpsa (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.97.1) (envelope-from ) id 1szL4T-0000000039T-04QC; Fri, 11 Oct 2024 21:13:05 +0200 From: Gerhard Engleder To: linux-kernel@vger.kernel.org Cc: arnd@arndb.de, gregkh@linuxfoundation.org, Gerhard Engleder , Gerhard Engleder Subject: [PATCH v2 4/8] misc: keba: Add LAN9252 driver Date: Fri, 11 Oct 2024 21:12:53 +0200 Message-Id: <20241011191257.19702-5-gerhard@engleder-embedded.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20241011191257.19702-1-gerhard@engleder-embedded.com> References: <20241011191257.19702-1-gerhard@engleder-embedded.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-AV-Do-Run: Yes X-ACL-Warn: X-W4Y-Internal Content-Type: text/plain; charset="utf-8" From: Gerhard Engleder KEBA CP500 devices use the LAN9252 controller for EtherCAT communication. For a stable Ethernet link the PHY registers of the controller need to be configured correctly. This driver configures these PHY registers as required. Signed-off-by: Gerhard Engleder --- drivers/misc/keba/Kconfig | 11 ++ drivers/misc/keba/Makefile | 1 + drivers/misc/keba/lan9252.c | 359 ++++++++++++++++++++++++++++++++++++ 3 files changed, 371 insertions(+) create mode 100644 drivers/misc/keba/lan9252.c diff --git a/drivers/misc/keba/Kconfig b/drivers/misc/keba/Kconfig index 5fbcbc2252ac..dc27b902f34e 100644 --- a/drivers/misc/keba/Kconfig +++ b/drivers/misc/keba/Kconfig @@ -11,3 +11,14 @@ config KEBA_CP500 =20 This driver can also be built as a module. If so, the module will be called cp500. + +config KEBA_LAN9252 + tristate "KEBA CP500 LAN9252 configuration" + depends on SPI + depends on KEBA_CP500 || COMPILE_TEST + help + This driver is used for updating the configuration of the LAN9252 + controller on KEBA CP500 devices. + + This driver can also be built as a module. If so, the module will be + called lan9252. diff --git a/drivers/misc/keba/Makefile b/drivers/misc/keba/Makefile index 0a8b846cda7d..05e9efcad54f 100644 --- a/drivers/misc/keba/Makefile +++ b/drivers/misc/keba/Makefile @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0 =20 obj-$(CONFIG_KEBA_CP500) +=3D cp500.o +obj-$(CONFIG_KEBA_LAN9252) +=3D lan9252.o diff --git a/drivers/misc/keba/lan9252.c b/drivers/misc/keba/lan9252.c new file mode 100644 index 000000000000..fc54afd1d05b --- /dev/null +++ b/drivers/misc/keba/lan9252.c @@ -0,0 +1,359 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) KEBA Industrial Automation Gmbh 2024 + * + * Driver for LAN9252 on KEBA CP500 devices + * + * This driver is used for updating the configuration of the LAN9252 contr= oller + * on KEBA CP500 devices. The LAN9252 is connected over SPI, which is also= named + * PDI. + */ + +#include +#include + +/* SPI commands */ +#define LAN9252_SPI_READ 0x3 +#define LAN9252_SPI_WRITE 0x2 + +struct lan9252_read_cmd { + u8 cmd; + u8 addr_0; + u8 addr_1; +} __packed; + +struct lan9252_write_cmd { + u8 cmd; + u8 addr_0; + u8 addr_1; + u32 data; +} __packed; + +/* byte test register */ +#define LAN9252_BYTE_TEST 0x64 +#define LAN9252_BYTE_TEST_VALUE 0x87654321 + +/* hardware configuration register */ +#define LAN9252_HW_CFG 0x74 +#define LAN9252_HW_CFG_READY 0x08000000 + +/* EtherCAT CSR interface data register */ +#define LAN9252_ECAT_CSR_DATA 0x300 + +/* EtherCAT CSR interface command register */ +#define LAN9252_ECAT_CSR_CMD 0x304 +#define LAN9252_ECAT_CSR_BUSY 0x80000000 +#define LAN9252_ECAT_CSR_READ 0x40000000 + +/* EtherCAT slave controller MII register */ +#define LAN9252_ESC_MII 0x510 +#define LAN9252_ESC_MII_BUSY 0x8000 +#define LAN9252_ESC_MII_CMD_ERR 0x4000 +#define LAN9252_ESC_MII_READ_ERR 0x2000 +#define LAN9252_ESC_MII_ERR_MASK (LAN9252_ESC_MII_CMD_ERR | \ + LAN9252_ESC_MII_READ_ERR) +#define LAN9252_ESC_MII_WRITE 0x0200 +#define LAN9252_ESC_MII_READ 0x0100 + +/* EtherCAT slave controller PHY address register */ +#define LAN9252_ESC_PHY_ADDR 0x512 + +/* EtherCAT slave controller PHY register address register */ +#define LAN9252_ESC_PHY_REG_ADDR 0x513 + +/* EtherCAT slave controller PHY data register */ +#define LAN9252_ESC_PHY_DATA 0x514 + +/* EtherCAT slave controller PDI access state register */ +#define LAN9252_ESC_MII_PDI 0x517 +#define LAN9252_ESC_MII_ACCESS_PDI 0x01 +#define LAN9252_ESC_MII_ACCESS_ECAT 0x00 + +/* PHY address */ +#define PHY_ADDRESS 2 + +#define SPI_RETRY_COUNT 10 +#define SPI_WAIT_US 100 +#define SPI_CSR_WAIT_US 500 + +static int lan9252_spi_read(struct spi_device *spi, u16 addr, u32 *data) +{ + struct lan9252_read_cmd cmd; + + cmd.cmd =3D LAN9252_SPI_READ; + cmd.addr_0 =3D (addr >> 8) & 0xFF; + cmd.addr_1 =3D addr & 0xFF; + + return spi_write_then_read(spi, (u8 *)&cmd, + sizeof(struct lan9252_read_cmd), + (u8 *)data, sizeof(u32)); +} + +static int lan9252_spi_write(struct spi_device *spi, u16 addr, u32 data) +{ + struct lan9252_write_cmd cmd; + + cmd.cmd =3D LAN9252_SPI_WRITE; + cmd.addr_0 =3D (addr >> 8) & 0xFF; + cmd.addr_1 =3D addr & 0xFF; + cmd.data =3D data; + + return spi_write(spi, (u8 *)&cmd, sizeof(struct lan9252_write_cmd)); +} + +static bool lan9252_init(struct spi_device *spi) +{ + u32 data; + int ret; + + ret =3D lan9252_spi_read(spi, LAN9252_BYTE_TEST, &data); + if (ret || data !=3D LAN9252_BYTE_TEST_VALUE) + return false; + + ret =3D lan9252_spi_read(spi, LAN9252_HW_CFG, &data); + if (ret || !(data & LAN9252_HW_CFG_READY)) + return false; + + return true; +} + +static u8 lan9252_esc_get_size(u16 addr) +{ + if (addr =3D=3D LAN9252_ESC_MII || addr =3D=3D LAN9252_ESC_PHY_DATA) + return 2; + + return 1; +} + +static int lan9252_esc_wait(struct spi_device *spi) +{ + ktime_t timeout =3D ktime_add_us(ktime_get(), SPI_WAIT_US); + u32 data; + int ret; + + /* wait while CSR command is busy */ + for (;;) { + ret =3D lan9252_spi_read(spi, LAN9252_ECAT_CSR_CMD, &data); + if (ret) + return ret; + if (!(data & LAN9252_ECAT_CSR_BUSY)) + return 0; + + if (ktime_compare(ktime_get(), timeout) > 0) { + ret =3D lan9252_spi_read(spi, LAN9252_ECAT_CSR_CMD, &data); + if (ret) + return ret; + break; + } + } + + return (!(data & LAN9252_ECAT_CSR_BUSY)) ? 0 : -ETIMEDOUT; +} + +static int lan9252_esc_read(struct spi_device *spi, u16 addr, u32 *data) +{ + u32 csr_cmd; + u8 size; + int ret; + + size =3D lan9252_esc_get_size(addr); + csr_cmd =3D LAN9252_ECAT_CSR_BUSY | LAN9252_ECAT_CSR_READ; + csr_cmd |=3D (size << 16) | addr; + ret =3D lan9252_spi_write(spi, LAN9252_ECAT_CSR_CMD, csr_cmd); + if (ret) + return ret; + + ret =3D lan9252_esc_wait(spi); + if (ret) + return ret; + + ret =3D lan9252_spi_read(spi, LAN9252_ECAT_CSR_DATA, data); + if (ret) + return ret; + + return 0; +} + +static int lan9252_esc_write(struct spi_device *spi, u16 addr, u32 data) +{ + u32 csr_cmd; + u8 size; + int ret; + + ret =3D lan9252_spi_write(spi, LAN9252_ECAT_CSR_DATA, data); + if (ret) + return ret; + + size =3D lan9252_esc_get_size(addr); + csr_cmd =3D LAN9252_ECAT_CSR_BUSY; + csr_cmd |=3D (size << 16) | addr; + ret =3D lan9252_spi_write(spi, LAN9252_ECAT_CSR_CMD, csr_cmd); + if (ret) + return ret; + + ret =3D lan9252_esc_wait(spi); + if (ret) + return ret; + + return 0; +} + +static int lan9252_access_mii(struct spi_device *spi, bool access) +{ + u32 data; + + if (access) + data =3D LAN9252_ESC_MII_ACCESS_PDI; + else + data =3D LAN9252_ESC_MII_ACCESS_ECAT; + + return lan9252_esc_write(spi, LAN9252_ESC_MII_PDI, data); +} + +static int lan9252_mii_wait(struct spi_device *spi) +{ + ktime_t timeout =3D ktime_add_us(ktime_get(), SPI_CSR_WAIT_US); + u32 data; + int ret; + + /* wait while MII control state machine is busy */ + for (;;) { + ret =3D lan9252_esc_read(spi, LAN9252_ESC_MII, &data); + if (ret) + return ret; + if (data & LAN9252_ESC_MII_ERR_MASK) + return -EIO; + if (!(data & LAN9252_ESC_MII_BUSY)) + return 0; + + if (ktime_compare(ktime_get(), timeout) > 0) { + ret =3D lan9252_esc_read(spi, LAN9252_ESC_MII, &data); + if (ret) + return ret; + if (data & LAN9252_ESC_MII_ERR_MASK) + return -EIO; + break; + } + } + + return (!(data & LAN9252_ESC_MII_BUSY)) ? 0 : -ETIMEDOUT; +} + +static int lan9252_mii_read(struct spi_device *spi, u8 phy_addr, u8 reg_ad= dr, + u32 *data) +{ + int ret; + + ret =3D lan9252_esc_write(spi, LAN9252_ESC_PHY_ADDR, phy_addr); + if (ret) + return ret; + ret =3D lan9252_esc_write(spi, LAN9252_ESC_PHY_REG_ADDR, reg_addr); + if (ret) + return ret; + + ret =3D lan9252_esc_write(spi, LAN9252_ESC_MII, LAN9252_ESC_MII_READ); + if (ret) + return ret; + + ret =3D lan9252_mii_wait(spi); + if (ret) + return ret; + + return lan9252_esc_read(spi, LAN9252_ESC_PHY_DATA, data); +} + +static int lan9252_mii_write(struct spi_device *spi, u8 phy_addr, u8 reg_a= ddr, + u32 data) +{ + int ret; + + ret =3D lan9252_esc_write(spi, LAN9252_ESC_PHY_ADDR, phy_addr); + if (ret) + return ret; + ret =3D lan9252_esc_write(spi, LAN9252_ESC_PHY_REG_ADDR, reg_addr); + if (ret) + return ret; + ret =3D lan9252_esc_write(spi, LAN9252_ESC_PHY_DATA, data); + if (ret) + return ret; + + ret =3D lan9252_esc_write(spi, LAN9252_ESC_MII, LAN9252_ESC_MII_WRITE); + if (ret) + return ret; + + return lan9252_mii_wait(spi); +} + +static int lan9252_probe(struct spi_device *spi) +{ + u32 data; + int retry =3D SPI_RETRY_COUNT; + int ret; + + /* execute specified initialization sequence */ + while (retry && !lan9252_init(spi)) + retry--; + if (retry =3D=3D 0) { + dev_err(&spi->dev, + "Can't initialize LAN9252 SPI communication!"); + return -EIO; + } + + /* enable access to MII management for PDI */ + ret =3D lan9252_access_mii(spi, true); + if (ret) { + dev_err(&spi->dev, "Can't enable access to MII management!"); + return ret; + } + + /* + * check PHY configuration and configure if necessary + * - full duplex + * - auto negotiation disabled + * - 100 Mbps + */ + ret =3D lan9252_mii_read(spi, PHY_ADDRESS, MII_BMCR, &data); + if (ret) { + dev_err(&spi->dev, "Can't read LAN9252 configuration!"); + goto out; + } + if (!(data & BMCR_FULLDPLX) || (data & BMCR_ANENABLE) || + !(data & BMCR_SPEED100)) { + /* + */ + data &=3D ~(BMCR_ANENABLE); + data |=3D (BMCR_FULLDPLX | BMCR_SPEED100); + ret =3D lan9252_mii_write(spi, PHY_ADDRESS, MII_BMCR, data); + if (ret) + dev_err(&spi->dev, + "Can't write LAN9252 configuration!"); + } + + dev_info(&spi->dev, "LAN9252 PHY configuration"); + +out: + /* disable access to MII management for PDI */ + lan9252_access_mii(spi, false); + + return ret; +} + +static const struct spi_device_id lan9252_id[] =3D { + {"lan9252"}, + {} +}; +MODULE_DEVICE_TABLE(spi, lan9252_id); + +static struct spi_driver lan9252_driver =3D { + .driver =3D { + .name =3D "lan9252", + }, + .probe =3D lan9252_probe, + .id_table =3D lan9252_id, +}; +module_spi_driver(lan9252_driver); + +MODULE_AUTHOR("Petar Bojanic "); +MODULE_AUTHOR("Gerhard Engleder "); +MODULE_DESCRIPTION("KEBA LAN9252 driver"); +MODULE_LICENSE("GPL"); --=20 2.39.2