* [PATCH v2 1/8] misc: keba: Use variable ret for return values
2024-10-11 19:12 [PATCH v2 0/8] misc: keba: Add support for additional devices Gerhard Engleder
@ 2024-10-11 19:12 ` Gerhard Engleder
2024-10-11 19:12 ` [PATCH v2 2/8] misc: keba: Use capital letters for I2C error message Gerhard Engleder
` (7 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Gerhard Engleder @ 2024-10-11 19:12 UTC (permalink / raw)
To: linux-kernel; +Cc: arnd, gregkh, Gerhard Engleder, Gerhard Engleder
From: Gerhard Engleder <eg@keba.com>
One function of the cp500 driver uses the variable name retval for
return values but all others use the variable name ret. Use ret for
return values in all functions.
Signed-off-by: Gerhard Engleder <eg@keba.com>
---
drivers/misc/keba/cp500.c | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/drivers/misc/keba/cp500.c b/drivers/misc/keba/cp500.c
index ae0922817881..b5327feb2847 100644
--- a/drivers/misc/keba/cp500.c
+++ b/drivers/misc/keba/cp500.c
@@ -229,7 +229,7 @@ static void cp500_i2c_release(struct device *dev)
static int cp500_register_i2c(struct cp500 *cp500)
{
- int retval;
+ int ret;
cp500->i2c = kzalloc(sizeof(*cp500->i2c), GFP_KERNEL);
if (!cp500->i2c)
@@ -251,19 +251,19 @@ static int cp500_register_i2c(struct cp500 *cp500)
cp500->i2c->info_size = ARRAY_SIZE(cp500_i2c_info);
cp500->i2c->info = cp500_i2c_info;
- retval = auxiliary_device_init(&cp500->i2c->auxdev);
- if (retval) {
+ ret = auxiliary_device_init(&cp500->i2c->auxdev);
+ if (ret) {
kfree(cp500->i2c);
cp500->i2c = NULL;
- return retval;
+ return ret;
}
- retval = __auxiliary_device_add(&cp500->i2c->auxdev, "keba");
- if (retval) {
+ ret = __auxiliary_device_add(&cp500->i2c->auxdev, "keba");
+ if (ret) {
auxiliary_device_uninit(&cp500->i2c->auxdev);
cp500->i2c = NULL;
- return retval;
+ return ret;
}
return 0;
--
2.39.2
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH v2 2/8] misc: keba: Use capital letters for I2C error message
2024-10-11 19:12 [PATCH v2 0/8] misc: keba: Add support for additional devices Gerhard Engleder
2024-10-11 19:12 ` [PATCH v2 1/8] misc: keba: Use variable ret for return values Gerhard Engleder
@ 2024-10-11 19:12 ` Gerhard Engleder
2024-10-11 19:12 ` [PATCH v2 3/8] misc: keba: Add SPI controller device Gerhard Engleder
` (6 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Gerhard Engleder @ 2024-10-11 19:12 UTC (permalink / raw)
To: linux-kernel; +Cc: arnd, gregkh, Gerhard Engleder, Gerhard Engleder
From: Gerhard Engleder <eg@keba.com>
Print "I2C" instead of "i2c" in error message as "I2C" is the official
name for the bus.
Signed-off-by: Gerhard Engleder <eg@keba.com>
---
drivers/misc/keba/cp500.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/misc/keba/cp500.c b/drivers/misc/keba/cp500.c
index b5327feb2847..1eee130c3a7f 100644
--- a/drivers/misc/keba/cp500.c
+++ b/drivers/misc/keba/cp500.c
@@ -274,7 +274,7 @@ static void cp500_register_auxiliary_devs(struct cp500 *cp500)
struct device *dev = &cp500->pci_dev->dev;
if (cp500_register_i2c(cp500))
- dev_warn(dev, "Failed to register i2c!\n");
+ dev_warn(dev, "Failed to register I2C!\n");
}
static void cp500_unregister_dev(struct auxiliary_device *auxdev)
--
2.39.2
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH v2 3/8] misc: keba: Add SPI controller device
2024-10-11 19:12 [PATCH v2 0/8] misc: keba: Add support for additional devices Gerhard Engleder
2024-10-11 19:12 ` [PATCH v2 1/8] misc: keba: Use variable ret for return values Gerhard Engleder
2024-10-11 19:12 ` [PATCH v2 2/8] misc: keba: Use capital letters for I2C error message Gerhard Engleder
@ 2024-10-11 19:12 ` Gerhard Engleder
2024-10-11 19:12 ` [PATCH v2 4/8] misc: keba: Add LAN9252 driver Gerhard Engleder
` (5 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Gerhard Engleder @ 2024-10-11 19:12 UTC (permalink / raw)
To: linux-kernel; +Cc: arnd, gregkh, Gerhard Engleder, Gerhard Engleder
From: Gerhard Engleder <eg@keba.com>
Add support for the SPI controller auxiliary device. This enables access
to the SPI flash of the FPGA and some other SPI devices.
The actual list of SPI devices is detected by reading some bits out of
the previously registered I2C EEPROM.
Signed-off-by: Gerhard Engleder <eg@keba.com>
---
drivers/misc/keba/cp500.c | 219 +++++++++++++++++++++++++++++++++++---
include/linux/misc/keba.h | 15 +++
2 files changed, 219 insertions(+), 15 deletions(-)
diff --git a/drivers/misc/keba/cp500.c b/drivers/misc/keba/cp500.c
index 1eee130c3a7f..7cebf2929390 100644
--- a/drivers/misc/keba/cp500.c
+++ b/drivers/misc/keba/cp500.c
@@ -12,7 +12,11 @@
#include <linux/i2c.h>
#include <linux/misc/keba.h>
#include <linux/module.h>
+#include <linux/mtd/partitions.h>
+#include <linux/nvmem-consumer.h>
#include <linux/pci.h>
+#include <linux/spi/flash.h>
+#include <linux/spi/spi.h>
#define CP500 "cp500"
@@ -43,6 +47,16 @@
/* EEPROM */
#define CP500_HW_CPU_EEPROM_NAME "cp500_cpu_eeprom"
+#define CP500_EEPROM_DA_OFFSET 0x016F
+#define CP500_EEPROM_DA_ESC_TYPE_MASK 0x01
+#define CP500_EEPROM_ESC_LAN9252 0x00
+#define CP500_EEPROM_ESC_ET1100 0x01
+
+/* SPI flash running at full speed */
+#define CP500_FLASH_HZ (33 * 1000 * 1000)
+
+/* LAN9252 */
+#define CP500_LAN9252_HZ (10 * 1000 * 1000)
#define CP500_IS_CP035(dev) ((dev)->pci_dev->device == PCI_DEVICE_ID_KEBA_CP035)
#define CP500_IS_CP505(dev) ((dev)->pci_dev->device == PCI_DEVICE_ID_KEBA_CP505)
@@ -55,25 +69,29 @@ struct cp500_dev_info {
struct cp500_devs {
struct cp500_dev_info startup;
+ struct cp500_dev_info spi;
struct cp500_dev_info i2c;
};
/* list of devices within FPGA of CP035 family (CP035, CP056, CP057) */
static struct cp500_devs cp035_devices = {
- .startup = { 0x0000, SZ_4K },
- .i2c = { 0x4000, SZ_4K },
+ .startup = { 0x0000, SZ_4K },
+ .spi = { 0x1000, SZ_4K },
+ .i2c = { 0x4000, SZ_4K },
};
/* list of devices within FPGA of CP505 family (CP503, CP505, CP507) */
static struct cp500_devs cp505_devices = {
- .startup = { 0x0000, SZ_4K },
- .i2c = { 0x5000, SZ_4K },
+ .startup = { 0x0000, SZ_4K },
+ .spi = { 0x4000, SZ_4K },
+ .i2c = { 0x5000, SZ_4K },
};
/* list of devices within FPGA of CP520 family (CP520, CP530) */
static struct cp500_devs cp520_devices = {
- .startup = { 0x0000, SZ_4K },
- .i2c = { 0x5000, SZ_4K },
+ .startup = { 0x0000, SZ_4K },
+ .spi = { 0x4000, SZ_4K },
+ .i2c = { 0x5000, SZ_4K },
};
struct cp500 {
@@ -85,9 +103,12 @@ struct cp500 {
int minor;
int build;
} version;
+ struct notifier_block nvmem_notifier;
+ atomic_t nvmem_notified;
/* system FPGA BAR */
resource_size_t sys_hwbase;
+ struct keba_spi_auxdev *spi;
struct keba_i2c_auxdev *i2c;
/* ECM EtherCAT BAR */
@@ -97,6 +118,7 @@ struct cp500 {
};
/* I2C devices */
+#define CP500_EEPROM_ADDR 0x50
static struct i2c_board_info cp500_i2c_info[] = {
{ /* temperature sensor */
I2C_BOARD_INFO("emc1403", 0x4c),
@@ -107,30 +129,67 @@ static struct i2c_board_info cp500_i2c_info[] = {
* CP505 family: bridge board
* CP520 family: carrier board
*/
- I2C_BOARD_INFO("24c32", 0x50),
+ I2C_BOARD_INFO("24c32", CP500_EEPROM_ADDR),
.dev_name = CP500_HW_CPU_EEPROM_NAME,
},
{ /* interface board EEPROM */
- I2C_BOARD_INFO("24c32", 0x51),
+ I2C_BOARD_INFO("24c32", CP500_EEPROM_ADDR + 1),
},
{ /*
* EEPROM (optional)
* CP505 family: CPU board
* CP520 family: MMI board
*/
- I2C_BOARD_INFO("24c32", 0x52),
+ I2C_BOARD_INFO("24c32", CP500_EEPROM_ADDR + 2),
},
{ /* extension module 0 EEPROM (optional) */
- I2C_BOARD_INFO("24c32", 0x53),
+ I2C_BOARD_INFO("24c32", CP500_EEPROM_ADDR + 3),
},
{ /* extension module 1 EEPROM (optional) */
- I2C_BOARD_INFO("24c32", 0x54),
+ I2C_BOARD_INFO("24c32", CP500_EEPROM_ADDR + 4),
},
{ /* extension module 2 EEPROM (optional) */
- I2C_BOARD_INFO("24c32", 0x55),
+ I2C_BOARD_INFO("24c32", CP500_EEPROM_ADDR + 5),
},
{ /* extension module 3 EEPROM (optional) */
- I2C_BOARD_INFO("24c32", 0x56),
+ I2C_BOARD_INFO("24c32", CP500_EEPROM_ADDR + 6),
+ }
+};
+
+/* SPI devices */
+static struct mtd_partition cp500_partitions[] = {
+ {
+ .name = "system-flash-parts",
+ .size = MTDPART_SIZ_FULL,
+ .offset = 0,
+ .mask_flags = 0
+ }
+};
+static const struct flash_platform_data cp500_w25q32 = {
+ .type = "w25q32",
+ .name = "system-flash",
+ .parts = cp500_partitions,
+ .nr_parts = ARRAY_SIZE(cp500_partitions),
+};
+static const struct flash_platform_data cp500_m25p16 = {
+ .type = "m25p16",
+ .name = "system-flash",
+ .parts = cp500_partitions,
+ .nr_parts = ARRAY_SIZE(cp500_partitions),
+};
+static struct spi_board_info cp500_spi_info[] = {
+ { /* system FPGA configuration bitstream flash */
+ .modalias = "m25p80",
+ .platform_data = &cp500_m25p16,
+ .max_speed_hz = CP500_FLASH_HZ,
+ .chip_select = 0,
+ .mode = SPI_MODE_3,
+ }, { /* LAN9252 EtherCAT slave controller */
+ .modalias = "lan9252",
+ .platform_data = NULL,
+ .max_speed_hz = CP500_LAN9252_HZ,
+ .chip_select = 1,
+ .mode = SPI_MODE_3,
}
};
@@ -269,6 +328,125 @@ static int cp500_register_i2c(struct cp500 *cp500)
return 0;
}
+static void cp500_spi_release(struct device *dev)
+{
+ struct keba_spi_auxdev *spi =
+ container_of(dev, struct keba_spi_auxdev, auxdev.dev);
+
+ kfree(spi);
+}
+
+static int cp500_register_spi(struct cp500 *cp500, u8 esc_type)
+{
+ int info_size;
+ int ret;
+
+ cp500->spi = kzalloc(sizeof(*cp500->spi), GFP_KERNEL);
+ if (!cp500->spi)
+ return -ENOMEM;
+
+ if (CP500_IS_CP035(cp500))
+ cp500_spi_info[0].platform_data = &cp500_w25q32;
+ if (esc_type == CP500_EEPROM_ESC_LAN9252)
+ info_size = ARRAY_SIZE(cp500_spi_info);
+ else
+ info_size = ARRAY_SIZE(cp500_spi_info) - 1;
+
+ cp500->spi->auxdev.name = "spi";
+ cp500->spi->auxdev.id = 0;
+ cp500->spi->auxdev.dev.release = cp500_spi_release;
+ cp500->spi->auxdev.dev.parent = &cp500->pci_dev->dev;
+ cp500->spi->io = (struct resource) {
+ /* SPI register area */
+ .start = (resource_size_t) cp500->sys_hwbase +
+ cp500->devs->spi.offset,
+ .end = (resource_size_t) cp500->sys_hwbase +
+ cp500->devs->spi.offset +
+ cp500->devs->spi.size - 1,
+ .flags = IORESOURCE_MEM,
+ };
+ cp500->spi->info_size = info_size;
+ cp500->spi->info = cp500_spi_info;
+
+ ret = auxiliary_device_init(&cp500->spi->auxdev);
+ if (ret) {
+ kfree(cp500->spi);
+ cp500->spi = NULL;
+
+ return ret;
+ }
+ ret = __auxiliary_device_add(&cp500->spi->auxdev, "keba");
+ if (ret) {
+ auxiliary_device_uninit(&cp500->spi->auxdev);
+ cp500->spi = NULL;
+
+ return ret;
+ }
+
+ return 0;
+}
+
+static int cp500_nvmem_match(struct device *dev, const void *data)
+{
+ const struct cp500 *cp500 = data;
+ struct i2c_client *client;
+
+ /* match only CPU EEPROM below the cp500 device */
+ dev = dev->parent;
+ client = i2c_verify_client(dev);
+ if (!client || client->addr != CP500_EEPROM_ADDR)
+ return 0;
+ while ((dev = dev->parent))
+ if (dev == &cp500->pci_dev->dev)
+ return 1;
+
+ return 0;
+}
+
+static int cp500_nvmem(struct notifier_block *nb, unsigned long action,
+ void *data)
+{
+ struct nvmem_device *nvmem;
+ struct cp500 *cp500;
+ struct device *dev;
+ int notified;
+ u8 esc_type;
+ int ret;
+
+ if (action != NVMEM_ADD)
+ return NOTIFY_DONE;
+ cp500 = container_of(nb, struct cp500, nvmem_notifier);
+ dev = &cp500->pci_dev->dev;
+
+ /* process CPU EEPROM content only once */
+ notified = atomic_read(&cp500->nvmem_notified);
+ if (notified)
+ return NOTIFY_DONE;
+ nvmem = nvmem_device_find(cp500, cp500_nvmem_match);
+ if (IS_ERR_OR_NULL(nvmem))
+ return NOTIFY_DONE;
+ if (!atomic_try_cmpxchg_relaxed(&cp500->nvmem_notified, ¬ified, 1)) {
+ nvmem_device_put(nvmem);
+
+ return NOTIFY_DONE;
+ }
+
+ ret = nvmem_device_read(nvmem, CP500_EEPROM_DA_OFFSET, sizeof(esc_type),
+ (void *)&esc_type);
+ nvmem_device_put(nvmem);
+ if (ret != sizeof(esc_type)) {
+ dev_warn(dev, "Failed to read device assembly!\n");
+
+ return NOTIFY_DONE;
+ }
+ esc_type &= CP500_EEPROM_DA_ESC_TYPE_MASK;
+
+ if (cp500_register_spi(cp500, esc_type))
+ dev_warn(dev, "Failed to register SPI!\n");
+
+ return NOTIFY_OK;
+}
+
static void cp500_register_auxiliary_devs(struct cp500 *cp500)
{
struct device *dev = &cp500->pci_dev->dev;
@@ -285,7 +463,10 @@ static void cp500_unregister_dev(struct auxiliary_device *auxdev)
static void cp500_unregister_auxiliary_devs(struct cp500 *cp500)
{
-
+ if (cp500->spi) {
+ cp500_unregister_dev(&cp500->spi->auxdev);
+ cp500->spi = NULL;
+ }
if (cp500->i2c) {
cp500_unregister_dev(&cp500->i2c->auxdev);
cp500->i2c = NULL;
@@ -396,15 +577,21 @@ static int cp500_probe(struct pci_dev *pci_dev, const struct pci_device_id *id)
pci_set_drvdata(pci_dev, cp500);
+ cp500->nvmem_notifier.notifier_call = cp500_nvmem;
+ ret = nvmem_register_notifier(&cp500->nvmem_notifier);
+ if (ret != 0)
+ goto out_free_irq;
ret = cp500_enable(cp500);
if (ret != 0)
- goto out_free_irq;
+ goto out_unregister_nvmem;
cp500_register_auxiliary_devs(cp500);
return 0;
+out_unregister_nvmem:
+ nvmem_unregister_notifier(&cp500->nvmem_notifier);
out_free_irq:
pci_free_irq_vectors(pci_dev);
out_disable:
@@ -422,6 +609,8 @@ static void cp500_remove(struct pci_dev *pci_dev)
cp500_disable(cp500);
+ nvmem_unregister_notifier(&cp500->nvmem_notifier);
+
pci_set_drvdata(pci_dev, 0);
pci_free_irq_vectors(pci_dev);
diff --git a/include/linux/misc/keba.h b/include/linux/misc/keba.h
index 323b31a847c5..1bd5409c6f6f 100644
--- a/include/linux/misc/keba.h
+++ b/include/linux/misc/keba.h
@@ -7,6 +7,7 @@
#include <linux/auxiliary_bus.h>
struct i2c_board_info;
+struct spi_board_info;
/**
* struct keba_i2c_auxdev - KEBA I2C auxiliary device
@@ -22,4 +23,18 @@ struct keba_i2c_auxdev {
struct i2c_board_info *info;
};
+/**
+ * struct keba_spi_auxdev - KEBA SPI auxiliary device
+ * @auxdev: auxiliary device object
+ * @io: address range of SPI controller IO memory
+ * @info_size: number of SPI devices to be probed
+ * @info: SPI devices to be probed
+ */
+struct keba_spi_auxdev {
+ struct auxiliary_device auxdev;
+ struct resource io;
+ int info_size;
+ struct spi_board_info *info;
+};
+
#endif /* _LINUX_MISC_KEBA_H */
--
2.39.2
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH v2 4/8] misc: keba: Add LAN9252 driver
2024-10-11 19:12 [PATCH v2 0/8] misc: keba: Add support for additional devices Gerhard Engleder
` (2 preceding siblings ...)
2024-10-11 19:12 ` [PATCH v2 3/8] misc: keba: Add SPI controller device Gerhard Engleder
@ 2024-10-11 19:12 ` Gerhard Engleder
2024-10-11 19:12 ` [PATCH v2 5/8] misc: keba: Support EEPROM sections as separate devices Gerhard Engleder
` (4 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Gerhard Engleder @ 2024-10-11 19:12 UTC (permalink / raw)
To: linux-kernel; +Cc: arnd, gregkh, Gerhard Engleder, Gerhard Engleder
From: Gerhard Engleder <eg@keba.com>
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 <eg@keba.com>
---
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
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
obj-$(CONFIG_KEBA_CP500) += cp500.o
+obj-$(CONFIG_KEBA_LAN9252) += 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 controller
+ * on KEBA CP500 devices. The LAN9252 is connected over SPI, which is also named
+ * PDI.
+ */
+
+#include <linux/spi/spi.h>
+#include <linux/mii.h>
+
+/* 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 = LAN9252_SPI_READ;
+ cmd.addr_0 = (addr >> 8) & 0xFF;
+ cmd.addr_1 = 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 = LAN9252_SPI_WRITE;
+ cmd.addr_0 = (addr >> 8) & 0xFF;
+ cmd.addr_1 = addr & 0xFF;
+ cmd.data = 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 = lan9252_spi_read(spi, LAN9252_BYTE_TEST, &data);
+ if (ret || data != LAN9252_BYTE_TEST_VALUE)
+ return false;
+
+ ret = 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 == LAN9252_ESC_MII || addr == LAN9252_ESC_PHY_DATA)
+ return 2;
+
+ return 1;
+}
+
+static int lan9252_esc_wait(struct spi_device *spi)
+{
+ ktime_t timeout = ktime_add_us(ktime_get(), SPI_WAIT_US);
+ u32 data;
+ int ret;
+
+ /* wait while CSR command is busy */
+ for (;;) {
+ ret = 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 = 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 = lan9252_esc_get_size(addr);
+ csr_cmd = LAN9252_ECAT_CSR_BUSY | LAN9252_ECAT_CSR_READ;
+ csr_cmd |= (size << 16) | addr;
+ ret = lan9252_spi_write(spi, LAN9252_ECAT_CSR_CMD, csr_cmd);
+ if (ret)
+ return ret;
+
+ ret = lan9252_esc_wait(spi);
+ if (ret)
+ return ret;
+
+ ret = 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 = lan9252_spi_write(spi, LAN9252_ECAT_CSR_DATA, data);
+ if (ret)
+ return ret;
+
+ size = lan9252_esc_get_size(addr);
+ csr_cmd = LAN9252_ECAT_CSR_BUSY;
+ csr_cmd |= (size << 16) | addr;
+ ret = lan9252_spi_write(spi, LAN9252_ECAT_CSR_CMD, csr_cmd);
+ if (ret)
+ return ret;
+
+ ret = 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 = LAN9252_ESC_MII_ACCESS_PDI;
+ else
+ data = 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 = ktime_add_us(ktime_get(), SPI_CSR_WAIT_US);
+ u32 data;
+ int ret;
+
+ /* wait while MII control state machine is busy */
+ for (;;) {
+ ret = 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 = 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_addr,
+ u32 *data)
+{
+ int ret;
+
+ ret = lan9252_esc_write(spi, LAN9252_ESC_PHY_ADDR, phy_addr);
+ if (ret)
+ return ret;
+ ret = lan9252_esc_write(spi, LAN9252_ESC_PHY_REG_ADDR, reg_addr);
+ if (ret)
+ return ret;
+
+ ret = lan9252_esc_write(spi, LAN9252_ESC_MII, LAN9252_ESC_MII_READ);
+ if (ret)
+ return ret;
+
+ ret = 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_addr,
+ u32 data)
+{
+ int ret;
+
+ ret = lan9252_esc_write(spi, LAN9252_ESC_PHY_ADDR, phy_addr);
+ if (ret)
+ return ret;
+ ret = lan9252_esc_write(spi, LAN9252_ESC_PHY_REG_ADDR, reg_addr);
+ if (ret)
+ return ret;
+ ret = lan9252_esc_write(spi, LAN9252_ESC_PHY_DATA, data);
+ if (ret)
+ return ret;
+
+ ret = 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 = SPI_RETRY_COUNT;
+ int ret;
+
+ /* execute specified initialization sequence */
+ while (retry && !lan9252_init(spi))
+ retry--;
+ if (retry == 0) {
+ dev_err(&spi->dev,
+ "Can't initialize LAN9252 SPI communication!");
+ return -EIO;
+ }
+
+ /* enable access to MII management for PDI */
+ ret = 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 = 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 &= ~(BMCR_ANENABLE);
+ data |= (BMCR_FULLDPLX | BMCR_SPEED100);
+ ret = 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[] = {
+ {"lan9252"},
+ {}
+};
+MODULE_DEVICE_TABLE(spi, lan9252_id);
+
+static struct spi_driver lan9252_driver = {
+ .driver = {
+ .name = "lan9252",
+ },
+ .probe = lan9252_probe,
+ .id_table = lan9252_id,
+};
+module_spi_driver(lan9252_driver);
+
+MODULE_AUTHOR("Petar Bojanic <boja@keba.com>");
+MODULE_AUTHOR("Gerhard Engleder <eg@keba.com>");
+MODULE_DESCRIPTION("KEBA LAN9252 driver");
+MODULE_LICENSE("GPL");
--
2.39.2
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH v2 5/8] misc: keba: Support EEPROM sections as separate devices
2024-10-11 19:12 [PATCH v2 0/8] misc: keba: Add support for additional devices Gerhard Engleder
` (3 preceding siblings ...)
2024-10-11 19:12 ` [PATCH v2 4/8] misc: keba: Add LAN9252 driver Gerhard Engleder
@ 2024-10-11 19:12 ` Gerhard Engleder
2024-10-11 19:12 ` [PATCH v2 6/8] misc: keba: Add fan device Gerhard Engleder
` (3 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Gerhard Engleder @ 2024-10-11 19:12 UTC (permalink / raw)
To: linux-kernel; +Cc: arnd, gregkh, Gerhard Engleder, Gerhard Engleder
From: Gerhard Engleder <eg@keba.com>
The main EEPROM of KEBA CP500 devices is divided into two sections. One
section for type label data like device name, order number, serial
number and so on. The second section is reserved for arbitrary data
stored by the user.
The two sections have a defined range. The first 3 kB for the type label
and the remaining 1 kB for user data. The type label is written during
production and can fill up the whole 3 kB. Thus, the range is fixed and
cannot be changed dynamically.
The two sections cannot be presented as NVMEM cells. A NVMEM cell is
always read and written at once, because the data presented to the user
can differ from the data stored in the physical NVMEM cell. Thus, NVMEM
cells would lead to reading 3 kB for every type label access, even if
only the device name is read. So performance would suffer. But it is
also an indication that NVMEM cells are designed for small data cells
within NVMEM devices.
Register separate NVMEM devices for every section. This enables safe
access to every section. Also different access rights are then possible.
Signed-off-by: Gerhard Engleder <eg@keba.com>
---
drivers/misc/keba/cp500.c | 105 ++++++++++++++++++++++++++++++++++++--
1 file changed, 102 insertions(+), 3 deletions(-)
diff --git a/drivers/misc/keba/cp500.c b/drivers/misc/keba/cp500.c
index 7cebf2929390..3cf99eaf45c4 100644
--- a/drivers/misc/keba/cp500.c
+++ b/drivers/misc/keba/cp500.c
@@ -14,6 +14,7 @@
#include <linux/module.h>
#include <linux/mtd/partitions.h>
#include <linux/nvmem-consumer.h>
+#include <linux/nvmem-provider.h>
#include <linux/pci.h>
#include <linux/spi/flash.h>
#include <linux/spi/spi.h>
@@ -46,11 +47,16 @@
#define CP500_NUM_MSIX_NO_AXI 3
/* EEPROM */
-#define CP500_HW_CPU_EEPROM_NAME "cp500_cpu_eeprom"
#define CP500_EEPROM_DA_OFFSET 0x016F
#define CP500_EEPROM_DA_ESC_TYPE_MASK 0x01
#define CP500_EEPROM_ESC_LAN9252 0x00
#define CP500_EEPROM_ESC_ET1100 0x01
+#define CP500_EEPROM_CPU_NAME "cpu_eeprom"
+#define CP500_EEPROM_CPU_OFFSET 0
+#define CP500_EEPROM_CPU_SIZE 3072
+#define CP500_EEPROM_USER_NAME "user_eeprom"
+#define CP500_EEPROM_USER_OFFSET 3072
+#define CP500_EEPROM_USER_SIZE 1024
/* SPI flash running at full speed */
#define CP500_FLASH_HZ (33 * 1000 * 1000)
@@ -94,6 +100,11 @@ static struct cp500_devs cp520_devices = {
.i2c = { 0x5000, SZ_4K },
};
+struct cp500_nvmem {
+ struct nvmem_device *nvmem;
+ unsigned int offset;
+};
+
struct cp500 {
struct pci_dev *pci_dev;
struct cp500_devs *devs;
@@ -114,6 +125,10 @@ struct cp500 {
/* ECM EtherCAT BAR */
resource_size_t ecm_hwbase;
+ /* NVMEM devices */
+ struct cp500_nvmem nvmem_cpu;
+ struct cp500_nvmem nvmem_user;
+
void __iomem *system_startup_addr;
};
@@ -130,7 +145,6 @@ static struct i2c_board_info cp500_i2c_info[] = {
* CP520 family: carrier board
*/
I2C_BOARD_INFO("24c32", CP500_EEPROM_ADDR),
- .dev_name = CP500_HW_CPU_EEPROM_NAME,
},
{ /* interface board EEPROM */
I2C_BOARD_INFO("24c32", CP500_EEPROM_ADDR + 1),
@@ -386,6 +400,77 @@ static int cp500_register_spi(struct cp500 *cp500, u8 esc_type)
return 0;
}
+static int cp500_nvmem_read(void *priv, unsigned int offset, void *val,
+ size_t bytes)
+{
+ struct cp500_nvmem *nvmem = priv;
+ int ret;
+
+ ret = nvmem_device_read(nvmem->nvmem, nvmem->offset + offset, bytes,
+ val);
+ if (ret != bytes)
+ return ret;
+
+ return 0;
+}
+
+static int cp500_nvmem_write(void *priv, unsigned int offset, void *val,
+ size_t bytes)
+{
+ struct cp500_nvmem *nvmem = priv;
+ int ret;
+
+ ret = nvmem_device_write(nvmem->nvmem, nvmem->offset + offset, bytes,
+ val);
+ if (ret != bytes)
+ return ret;
+
+ return 0;
+}
+
+static int cp500_nvmem_register(struct cp500 *cp500, struct nvmem_device *nvmem)
+{
+ struct device *dev = &cp500->pci_dev->dev;
+ struct nvmem_config nvmem_config = {};
+ struct nvmem_device *tmp;
+
+ /*
+ * The main EEPROM of CP500 devices is logically split into two EEPROMs.
+ * The first logical EEPROM with 3 kB contains the type label which is
+ * programmed during production of the device. The second logical EEPROM
+ * with 1 kB is not programmed during production and can be used for
+ * arbitrary user data.
+ */
+
+ nvmem_config.dev = dev;
+ nvmem_config.owner = THIS_MODULE;
+ nvmem_config.id = NVMEM_DEVID_NONE;
+ nvmem_config.type = NVMEM_TYPE_EEPROM;
+ nvmem_config.root_only = true;
+ nvmem_config.reg_read = cp500_nvmem_read;
+ nvmem_config.reg_write = cp500_nvmem_write;
+
+ cp500->nvmem_cpu.nvmem = nvmem;
+ cp500->nvmem_cpu.offset = CP500_EEPROM_CPU_OFFSET;
+ nvmem_config.name = CP500_EEPROM_CPU_NAME;
+ nvmem_config.size = CP500_EEPROM_CPU_SIZE;
+ nvmem_config.priv = &cp500->nvmem_cpu;
+ tmp = devm_nvmem_register(dev, &nvmem_config);
+ if (IS_ERR(tmp))
+ return PTR_ERR(tmp);
+
+ cp500->nvmem_user.nvmem = nvmem;
+ cp500->nvmem_user.offset = CP500_EEPROM_USER_OFFSET;
+ nvmem_config.name = CP500_EEPROM_USER_NAME;
+ nvmem_config.size = CP500_EEPROM_USER_SIZE;
+ nvmem_config.priv = &cp500->nvmem_user;
+ tmp = devm_nvmem_register(dev, &nvmem_config);
+ if (IS_ERR(tmp))
+ return PTR_ERR(tmp);
+
+ return 0;
+}
+
static int cp500_nvmem_match(struct device *dev, const void *data)
{
const struct cp500 *cp500 = data;
@@ -403,6 +488,13 @@ static int cp500_nvmem_match(struct device *dev, const void *data)
return 0;
}
+static void cp500_devm_nvmem_put(void *data)
+{
+ struct nvmem_device *nvmem = data;
+
+ nvmem_device_put(nvmem);
+}
+
static int cp500_nvmem(struct notifier_block *nb, unsigned long action,
void *data)
{
@@ -431,9 +523,16 @@ static int cp500_nvmem(struct notifier_block *nb, unsigned long action,
return NOTIFY_DONE;
}
+ ret = devm_add_action_or_reset(dev, cp500_devm_nvmem_put, nvmem);
+ if (ret)
+ return ret;
+
+ ret = cp500_nvmem_register(cp500, nvmem);
+ if (ret)
+ return ret;
+
ret = nvmem_device_read(nvmem, CP500_EEPROM_DA_OFFSET, sizeof(esc_type),
(void *)&esc_type);
- nvmem_device_put(nvmem);
if (ret != sizeof(esc_type)) {
dev_warn(dev, "Failed to read device assembly!\n");
--
2.39.2
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH v2 6/8] misc: keba: Add fan device
2024-10-11 19:12 [PATCH v2 0/8] misc: keba: Add support for additional devices Gerhard Engleder
` (4 preceding siblings ...)
2024-10-11 19:12 ` [PATCH v2 5/8] misc: keba: Support EEPROM sections as separate devices Gerhard Engleder
@ 2024-10-11 19:12 ` Gerhard Engleder
2024-10-11 19:12 ` [PATCH v2 7/8] misc: keba: Add battery device Gerhard Engleder
` (2 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Gerhard Engleder @ 2024-10-11 19:12 UTC (permalink / raw)
To: linux-kernel; +Cc: arnd, gregkh, Gerhard Engleder, Gerhard Engleder
From: Gerhard Engleder <eg@keba.com>
Add support for the fan auxiliary device. This enables monitoring of the
fan.
Signed-off-by: Gerhard Engleder <eg@keba.com>
---
drivers/misc/keba/cp500.c | 83 ++++++++++++++++++++++++++++++++++-----
include/linux/misc/keba.h | 10 +++++
2 files changed, 84 insertions(+), 9 deletions(-)
diff --git a/drivers/misc/keba/cp500.c b/drivers/misc/keba/cp500.c
index 3cf99eaf45c4..ae3ed1cece32 100644
--- a/drivers/misc/keba/cp500.c
+++ b/drivers/misc/keba/cp500.c
@@ -32,6 +32,7 @@
/* BAR 0 registers */
#define CP500_VERSION_REG 0x00
#define CP500_RECONFIG_REG 0x11 /* upper 8-bits of STARTUP register */
+#define CP500_PRESENT_REG 0x20
#define CP500_AXI_REG 0x40
/* Bits in BUILD_REG */
@@ -40,6 +41,9 @@
/* Bits in RECONFIG_REG */
#define CP500_RECFG_REQ 0x01 /* reconfigure FPGA on next reset */
+/* Bits in PRESENT_REG */
+#define CP500_PRESENT_FAN0 0x01
+
/* MSIX */
#define CP500_AXI_MSIX 3
#define CP500_NUM_MSIX 8
@@ -77,27 +81,31 @@ struct cp500_devs {
struct cp500_dev_info startup;
struct cp500_dev_info spi;
struct cp500_dev_info i2c;
+ struct cp500_dev_info fan;
};
/* list of devices within FPGA of CP035 family (CP035, CP056, CP057) */
static struct cp500_devs cp035_devices = {
- .startup = { 0x0000, SZ_4K },
- .spi = { 0x1000, SZ_4K },
- .i2c = { 0x4000, SZ_4K },
+ .startup = { 0x0000, SZ_4K },
+ .spi = { 0x1000, SZ_4K },
+ .i2c = { 0x4000, SZ_4K },
+ .fan = { 0x9000, SZ_4K },
};
/* list of devices within FPGA of CP505 family (CP503, CP505, CP507) */
static struct cp500_devs cp505_devices = {
- .startup = { 0x0000, SZ_4K },
- .spi = { 0x4000, SZ_4K },
- .i2c = { 0x5000, SZ_4K },
+ .startup = { 0x0000, SZ_4K },
+ .spi = { 0x4000, SZ_4K },
+ .i2c = { 0x5000, SZ_4K },
+ .fan = { 0x9000, SZ_4K },
};
/* list of devices within FPGA of CP520 family (CP520, CP530) */
static struct cp500_devs cp520_devices = {
- .startup = { 0x0000, SZ_4K },
- .spi = { 0x4000, SZ_4K },
- .i2c = { 0x5000, SZ_4K },
+ .startup = { 0x0000, SZ_4K },
+ .spi = { 0x4000, SZ_4K },
+ .i2c = { 0x5000, SZ_4K },
+ .fan = { 0x8000, SZ_4K },
};
struct cp500_nvmem {
@@ -121,6 +129,7 @@ struct cp500 {
resource_size_t sys_hwbase;
struct keba_spi_auxdev *spi;
struct keba_i2c_auxdev *i2c;
+ struct keba_fan_auxdev *fan;
/* ECM EtherCAT BAR */
resource_size_t ecm_hwbase;
@@ -400,6 +409,54 @@ static int cp500_register_spi(struct cp500 *cp500, u8 esc_type)
return 0;
}
+static void cp500_fan_release(struct device *dev)
+{
+ struct keba_fan_auxdev *fan =
+ container_of(dev, struct keba_fan_auxdev, auxdev.dev);
+
+ kfree(fan);
+}
+
+static int cp500_register_fan(struct cp500 *cp500)
+{
+ int ret;
+
+ cp500->fan = kzalloc(sizeof(*cp500->fan), GFP_KERNEL);
+ if (!cp500->fan)
+ return -ENOMEM;
+
+ cp500->fan->auxdev.name = "fan";
+ cp500->fan->auxdev.id = 0;
+ cp500->fan->auxdev.dev.release = cp500_fan_release;
+ cp500->fan->auxdev.dev.parent = &cp500->pci_dev->dev;
+ cp500->fan->io = (struct resource) {
+ /* fan register area */
+ .start = (resource_size_t) cp500->sys_hwbase +
+ cp500->devs->fan.offset,
+ .end = (resource_size_t) cp500->sys_hwbase +
+ cp500->devs->fan.offset +
+ cp500->devs->fan.size - 1,
+ .flags = IORESOURCE_MEM,
+ };
+
+ ret = auxiliary_device_init(&cp500->fan->auxdev);
+ if (ret) {
+ kfree(cp500->fan);
+ cp500->fan = NULL;
+
+ return ret;
+ }
+ ret = __auxiliary_device_add(&cp500->fan->auxdev, "keba");
+ if (ret) {
+ auxiliary_device_uninit(&cp500->fan->auxdev);
+ cp500->fan = NULL;
+
+ return ret;
+ }
+
+ return 0;
+}
+
static int cp500_nvmem_read(void *priv, unsigned int offset, void *val,
size_t bytes)
{
@@ -549,9 +606,13 @@ static int cp500_nvmem(struct notifier_block *nb, unsigned long action,
static void cp500_register_auxiliary_devs(struct cp500 *cp500)
{
struct device *dev = &cp500->pci_dev->dev;
+ u8 present = ioread8(cp500->system_startup_addr + CP500_PRESENT_REG);
if (cp500_register_i2c(cp500))
dev_warn(dev, "Failed to register I2C!\n");
+ if (present & CP500_PRESENT_FAN0)
+ if (cp500_register_fan(cp500))
+ dev_warn(dev, "Failed to register fan!\n");
}
static void cp500_unregister_dev(struct auxiliary_device *auxdev)
@@ -570,6 +631,10 @@ static void cp500_unregister_auxiliary_devs(struct cp500 *cp500)
cp500_unregister_dev(&cp500->i2c->auxdev);
cp500->i2c = NULL;
}
+ if (cp500->fan) {
+ cp500_unregister_dev(&cp500->fan->auxdev);
+ cp500->fan = NULL;
+ }
}
static irqreturn_t cp500_axi_handler(int irq, void *dev)
diff --git a/include/linux/misc/keba.h b/include/linux/misc/keba.h
index 1bd5409c6f6f..451777acc262 100644
--- a/include/linux/misc/keba.h
+++ b/include/linux/misc/keba.h
@@ -37,4 +37,14 @@ struct keba_spi_auxdev {
struct spi_board_info *info;
};
+/**
+ * struct keba_fan_auxdev - KEBA fan auxiliary device
+ * @auxdev: auxiliary device object
+ * @io: address range of fan controller IO memory
+ */
+struct keba_fan_auxdev {
+ struct auxiliary_device auxdev;
+ struct resource io;
+};
+
#endif /* _LINUX_MISC_KEBA_H */
--
2.39.2
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH v2 7/8] misc: keba: Add battery device
2024-10-11 19:12 [PATCH v2 0/8] misc: keba: Add support for additional devices Gerhard Engleder
` (5 preceding siblings ...)
2024-10-11 19:12 ` [PATCH v2 6/8] misc: keba: Add fan device Gerhard Engleder
@ 2024-10-11 19:12 ` Gerhard Engleder
2024-10-11 19:12 ` [PATCH v2 8/8] misc: keba: Add UART devices Gerhard Engleder
2024-10-12 7:26 ` [PATCH v2 0/8] misc: keba: Add support for additional devices Greg KH
8 siblings, 0 replies; 10+ messages in thread
From: Gerhard Engleder @ 2024-10-11 19:12 UTC (permalink / raw)
To: linux-kernel; +Cc: arnd, gregkh, Gerhard Engleder, Gerhard Engleder
From: Gerhard Engleder <eg@keba.com>
Add support for the battery auxiliary device. This enables monitoring of
the battery.
Signed-off-by: Gerhard Engleder <eg@keba.com>
---
drivers/misc/keba/cp500.c | 59 +++++++++++++++++++++++++++++++++++++++
include/linux/misc/keba.h | 10 +++++++
2 files changed, 69 insertions(+)
diff --git a/drivers/misc/keba/cp500.c b/drivers/misc/keba/cp500.c
index ae3ed1cece32..afd4d7c06cee 100644
--- a/drivers/misc/keba/cp500.c
+++ b/drivers/misc/keba/cp500.c
@@ -82,6 +82,7 @@ struct cp500_devs {
struct cp500_dev_info spi;
struct cp500_dev_info i2c;
struct cp500_dev_info fan;
+ struct cp500_dev_info batt;
};
/* list of devices within FPGA of CP035 family (CP035, CP056, CP057) */
@@ -90,6 +91,7 @@ static struct cp500_devs cp035_devices = {
.spi = { 0x1000, SZ_4K },
.i2c = { 0x4000, SZ_4K },
.fan = { 0x9000, SZ_4K },
+ .batt = { 0xA000, SZ_4K },
};
/* list of devices within FPGA of CP505 family (CP503, CP505, CP507) */
@@ -98,6 +100,7 @@ static struct cp500_devs cp505_devices = {
.spi = { 0x4000, SZ_4K },
.i2c = { 0x5000, SZ_4K },
.fan = { 0x9000, SZ_4K },
+ .batt = { 0xA000, SZ_4K },
};
/* list of devices within FPGA of CP520 family (CP520, CP530) */
@@ -106,6 +109,7 @@ static struct cp500_devs cp520_devices = {
.spi = { 0x4000, SZ_4K },
.i2c = { 0x5000, SZ_4K },
.fan = { 0x8000, SZ_4K },
+ .batt = { 0x9000, SZ_4K },
};
struct cp500_nvmem {
@@ -130,6 +134,7 @@ struct cp500 {
struct keba_spi_auxdev *spi;
struct keba_i2c_auxdev *i2c;
struct keba_fan_auxdev *fan;
+ struct keba_batt_auxdev *batt;
/* ECM EtherCAT BAR */
resource_size_t ecm_hwbase;
@@ -457,6 +462,54 @@ static int cp500_register_fan(struct cp500 *cp500)
return 0;
}
+static void cp500_batt_release(struct device *dev)
+{
+ struct keba_batt_auxdev *fan =
+ container_of(dev, struct keba_batt_auxdev, auxdev.dev);
+
+ kfree(fan);
+}
+
+static int cp500_register_batt(struct cp500 *cp500)
+{
+ int ret;
+
+ cp500->batt = kzalloc(sizeof(*cp500->batt), GFP_KERNEL);
+ if (!cp500->batt)
+ return -ENOMEM;
+
+ cp500->batt->auxdev.name = "batt";
+ cp500->batt->auxdev.id = 0;
+ cp500->batt->auxdev.dev.release = cp500_batt_release;
+ cp500->batt->auxdev.dev.parent = &cp500->pci_dev->dev;
+ cp500->batt->io = (struct resource) {
+ /* battery register area */
+ .start = (resource_size_t) cp500->sys_hwbase +
+ cp500->devs->batt.offset,
+ .end = (resource_size_t) cp500->sys_hwbase +
+ cp500->devs->batt.offset +
+ cp500->devs->batt.size - 1,
+ .flags = IORESOURCE_MEM,
+ };
+
+ ret = auxiliary_device_init(&cp500->batt->auxdev);
+ if (ret) {
+ kfree(cp500->batt);
+ cp500->batt = NULL;
+
+ return ret;
+ }
+ ret = __auxiliary_device_add(&cp500->batt->auxdev, "keba");
+ if (ret) {
+ auxiliary_device_uninit(&cp500->batt->auxdev);
+ cp500->batt = NULL;
+
+ return ret;
+ }
+
+ return 0;
+}
+
static int cp500_nvmem_read(void *priv, unsigned int offset, void *val,
size_t bytes)
{
@@ -613,6 +666,8 @@ static void cp500_register_auxiliary_devs(struct cp500 *cp500)
if (present & CP500_PRESENT_FAN0)
if (cp500_register_fan(cp500))
dev_warn(dev, "Failed to register fan!\n");
+ if (cp500_register_batt(cp500))
+ dev_warn(dev, "Failed to register battery!\n");
}
static void cp500_unregister_dev(struct auxiliary_device *auxdev)
@@ -635,6 +690,10 @@ static void cp500_unregister_auxiliary_devs(struct cp500 *cp500)
cp500_unregister_dev(&cp500->fan->auxdev);
cp500->fan = NULL;
}
+ if (cp500->batt) {
+ cp500_unregister_dev(&cp500->batt->auxdev);
+ cp500->batt = NULL;
+ }
}
static irqreturn_t cp500_axi_handler(int irq, void *dev)
diff --git a/include/linux/misc/keba.h b/include/linux/misc/keba.h
index 451777acc262..ca52716f8437 100644
--- a/include/linux/misc/keba.h
+++ b/include/linux/misc/keba.h
@@ -47,4 +47,14 @@ struct keba_fan_auxdev {
struct resource io;
};
+/**
+ * struct keba_batt_auxdev - KEBA battery auxiliary device
+ * @auxdev: auxiliary device object
+ * @io: address range of battery controller IO memory
+ */
+struct keba_batt_auxdev {
+ struct auxiliary_device auxdev;
+ struct resource io;
+};
+
#endif /* _LINUX_MISC_KEBA_H */
--
2.39.2
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH v2 8/8] misc: keba: Add UART devices
2024-10-11 19:12 [PATCH v2 0/8] misc: keba: Add support for additional devices Gerhard Engleder
` (6 preceding siblings ...)
2024-10-11 19:12 ` [PATCH v2 7/8] misc: keba: Add battery device Gerhard Engleder
@ 2024-10-11 19:12 ` Gerhard Engleder
2024-10-12 7:26 ` [PATCH v2 0/8] misc: keba: Add support for additional devices Greg KH
8 siblings, 0 replies; 10+ messages in thread
From: Gerhard Engleder @ 2024-10-11 19:12 UTC (permalink / raw)
To: linux-kernel; +Cc: arnd, gregkh, Gerhard Engleder, Gerhard Engleder
From: Gerhard Engleder <eg@keba.com>
Add support for the UART auxiliary devices. This enables access to up to
3 different UARTs, which are implemented in the FPGA.
Signed-off-by: Gerhard Engleder <eg@keba.com>
---
drivers/misc/keba/cp500.c | 104 ++++++++++++++++++++++++++++++++++++++
include/linux/misc/keba.h | 12 +++++
2 files changed, 116 insertions(+)
diff --git a/drivers/misc/keba/cp500.c b/drivers/misc/keba/cp500.c
index afd4d7c06cee..255d3022dae8 100644
--- a/drivers/misc/keba/cp500.c
+++ b/drivers/misc/keba/cp500.c
@@ -46,6 +46,9 @@
/* MSIX */
#define CP500_AXI_MSIX 3
+#define CP500_RFB_UART_MSIX 4
+#define CP500_DEBUG_UART_MSIX 5
+#define CP500_SI1_UART_MSIX 6
#define CP500_NUM_MSIX 8
#define CP500_NUM_MSIX_NO_MMI 2
#define CP500_NUM_MSIX_NO_AXI 3
@@ -75,6 +78,7 @@
struct cp500_dev_info {
off_t offset;
size_t size;
+ unsigned int msix;
};
struct cp500_devs {
@@ -83,6 +87,9 @@ struct cp500_devs {
struct cp500_dev_info i2c;
struct cp500_dev_info fan;
struct cp500_dev_info batt;
+ struct cp500_dev_info uart0_rfb;
+ struct cp500_dev_info uart1_dbg;
+ struct cp500_dev_info uart2_si1;
};
/* list of devices within FPGA of CP035 family (CP035, CP056, CP057) */
@@ -92,6 +99,8 @@ static struct cp500_devs cp035_devices = {
.i2c = { 0x4000, SZ_4K },
.fan = { 0x9000, SZ_4K },
.batt = { 0xA000, SZ_4K },
+ .uart0_rfb = { 0xB000, SZ_4K, CP500_RFB_UART_MSIX },
+ .uart2_si1 = { 0xD000, SZ_4K, CP500_SI1_UART_MSIX },
};
/* list of devices within FPGA of CP505 family (CP503, CP505, CP507) */
@@ -101,6 +110,8 @@ static struct cp500_devs cp505_devices = {
.i2c = { 0x5000, SZ_4K },
.fan = { 0x9000, SZ_4K },
.batt = { 0xA000, SZ_4K },
+ .uart0_rfb = { 0xB000, SZ_4K, CP500_RFB_UART_MSIX },
+ .uart2_si1 = { 0xD000, SZ_4K, CP500_SI1_UART_MSIX },
};
/* list of devices within FPGA of CP520 family (CP520, CP530) */
@@ -110,6 +121,8 @@ static struct cp500_devs cp520_devices = {
.i2c = { 0x5000, SZ_4K },
.fan = { 0x8000, SZ_4K },
.batt = { 0x9000, SZ_4K },
+ .uart0_rfb = { 0xC000, SZ_4K, CP500_RFB_UART_MSIX },
+ .uart1_dbg = { 0xD000, SZ_4K, CP500_DEBUG_UART_MSIX },
};
struct cp500_nvmem {
@@ -135,6 +148,9 @@ struct cp500 {
struct keba_i2c_auxdev *i2c;
struct keba_fan_auxdev *fan;
struct keba_batt_auxdev *batt;
+ struct keba_uart_auxdev *uart0_rfb;
+ struct keba_uart_auxdev *uart1_dbg;
+ struct keba_uart_auxdev *uart2_si1;
/* ECM EtherCAT BAR */
resource_size_t ecm_hwbase;
@@ -510,6 +526,55 @@ static int cp500_register_batt(struct cp500 *cp500)
return 0;
}
+static void cp500_uart_release(struct device *dev)
+{
+ struct keba_uart_auxdev *uart =
+ container_of(dev, struct keba_uart_auxdev, auxdev.dev);
+
+ kfree(uart);
+}
+
+static int cp500_register_uart(struct cp500 *cp500,
+ struct keba_uart_auxdev **uart, const char *name,
+ struct cp500_dev_info *info, unsigned int irq)
+{
+ int ret;
+
+ *uart = kzalloc(sizeof(**uart), GFP_KERNEL);
+ if (!*uart)
+ return -ENOMEM;
+
+ (*uart)->auxdev.name = name;
+ (*uart)->auxdev.id = 0;
+ (*uart)->auxdev.dev.release = cp500_uart_release;
+ (*uart)->auxdev.dev.parent = &cp500->pci_dev->dev;
+ (*uart)->io = (struct resource) {
+ /* UART register area */
+ .start = (resource_size_t) cp500->sys_hwbase + info->offset,
+ .end = (resource_size_t) cp500->sys_hwbase + info->offset +
+ info->size - 1,
+ .flags = IORESOURCE_MEM,
+ };
+ (*uart)->irq = irq;
+
+ ret = auxiliary_device_init(&(*uart)->auxdev);
+ if (ret) {
+ kfree(*uart);
+ *uart = NULL;
+
+ return ret;
+ }
+ ret = __auxiliary_device_add(&(*uart)->auxdev, "keba");
+ if (ret) {
+ auxiliary_device_uninit(&(*uart)->auxdev);
+ *uart = NULL;
+
+ return ret;
+ }
+
+ return 0;
+}
+
static int cp500_nvmem_read(void *priv, unsigned int offset, void *val,
size_t bytes)
{
@@ -668,6 +733,33 @@ static void cp500_register_auxiliary_devs(struct cp500 *cp500)
dev_warn(dev, "Failed to register fan!\n");
if (cp500_register_batt(cp500))
dev_warn(dev, "Failed to register battery!\n");
+ if (cp500->devs->uart0_rfb.size &&
+ cp500->devs->uart0_rfb.msix < cp500->msix_num) {
+ int irq = pci_irq_vector(cp500->pci_dev,
+ cp500->devs->uart0_rfb.msix);
+
+ if (cp500_register_uart(cp500, &cp500->uart0_rfb, "rs485-uart",
+ &cp500->devs->uart0_rfb, irq))
+ dev_warn(dev, "Failed to register RFB UART!\n");
+ }
+ if (cp500->devs->uart1_dbg.size &&
+ cp500->devs->uart1_dbg.msix < cp500->msix_num) {
+ int irq = pci_irq_vector(cp500->pci_dev,
+ cp500->devs->uart1_dbg.msix);
+
+ if (cp500_register_uart(cp500, &cp500->uart1_dbg, "rs232-uart",
+ &cp500->devs->uart1_dbg, irq))
+ dev_warn(dev, "Failed to register debug UART!\n");
+ }
+ if (cp500->devs->uart2_si1.size &&
+ cp500->devs->uart2_si1.msix < cp500->msix_num) {
+ int irq = pci_irq_vector(cp500->pci_dev,
+ cp500->devs->uart2_si1.msix);
+
+ if (cp500_register_uart(cp500, &cp500->uart2_si1, "uart",
+ &cp500->devs->uart2_si1, irq))
+ dev_warn(dev, "Failed to register SI1 UART!\n");
+ }
}
static void cp500_unregister_dev(struct auxiliary_device *auxdev)
@@ -694,6 +786,18 @@ static void cp500_unregister_auxiliary_devs(struct cp500 *cp500)
cp500_unregister_dev(&cp500->batt->auxdev);
cp500->batt = NULL;
}
+ if (cp500->uart0_rfb) {
+ cp500_unregister_dev(&cp500->uart0_rfb->auxdev);
+ cp500->uart0_rfb = NULL;
+ }
+ if (cp500->uart1_dbg) {
+ cp500_unregister_dev(&cp500->uart1_dbg->auxdev);
+ cp500->uart1_dbg = NULL;
+ }
+ if (cp500->uart2_si1) {
+ cp500_unregister_dev(&cp500->uart2_si1->auxdev);
+ cp500->uart2_si1 = NULL;
+ }
}
static irqreturn_t cp500_axi_handler(int irq, void *dev)
diff --git a/include/linux/misc/keba.h b/include/linux/misc/keba.h
index ca52716f8437..a81d6fa70851 100644
--- a/include/linux/misc/keba.h
+++ b/include/linux/misc/keba.h
@@ -57,4 +57,16 @@ struct keba_batt_auxdev {
struct resource io;
};
+/**
+ * struct keba_uart_auxdev - KEBA UART auxiliary device
+ * @auxdev: auxiliary device object
+ * @io: address range of UART controller IO memory
+ * @irq: number of UART controller interrupt
+ */
+struct keba_uart_auxdev {
+ struct auxiliary_device auxdev;
+ struct resource io;
+ unsigned int irq;
+};
+
#endif /* _LINUX_MISC_KEBA_H */
--
2.39.2
^ permalink raw reply [flat|nested] 10+ messages in thread* Re: [PATCH v2 0/8] misc: keba: Add support for additional devices
2024-10-11 19:12 [PATCH v2 0/8] misc: keba: Add support for additional devices Gerhard Engleder
` (7 preceding siblings ...)
2024-10-11 19:12 ` [PATCH v2 8/8] misc: keba: Add UART devices Gerhard Engleder
@ 2024-10-12 7:26 ` Greg KH
8 siblings, 0 replies; 10+ messages in thread
From: Greg KH @ 2024-10-12 7:26 UTC (permalink / raw)
To: Gerhard Engleder; +Cc: linux-kernel, arnd
On Fri, Oct 11, 2024 at 09:12:49PM +0200, Gerhard Engleder wrote:
> Add SPI controller auxiliary device. The SPI controller driver is not
> part of this series, because it should target spi-next and not
> char-misc. Additionally a driver for the LAN9252 chip, which is
> connected to the SPI controller, is included. This driver only
> configures the LAN9252 chip in the right mode and provides no user space
> interface.
>
> The main EEPROM of the CP500 devices is divided into two sections by
> design/factory. Therefore, provide the two section as separate NVMEM
> devices.
>
> Add fan, battery and UART auxiliary devices. Similar to the SPI
> controller, this is some prepartion work, because the drivers will
> target other branches than char-misc.
>
> The series starts with two small cleanups.
>
> v2:
> - separate commits for clean up (Greg KH)
Ah, I wasn't saying "this must be done" at all, it was just a minor
comment. But thanks for doing it!
greg k-h
^ permalink raw reply [flat|nested] 10+ messages in thread