* [PATCH 1/6] misc: keba: Add SPI controller device
2024-10-09 20:29 [PATCH 0/6] misc: keba: Add support for additional devices Gerhard Engleder
@ 2024-10-09 20:29 ` Gerhard Engleder
2024-10-10 7:12 ` Greg KH
2024-10-13 1:03 ` kernel test robot
2024-10-09 20:29 ` [PATCH 2/6] misc: keba: Add LAN9252 driver Gerhard Engleder
` (4 subsequent siblings)
5 siblings, 2 replies; 12+ messages in thread
From: Gerhard Engleder @ 2024-10-09 20:29 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 | 235 ++++++++++++++++++++++++++++++++++----
include/linux/misc/keba.h | 15 +++
2 files changed, 227 insertions(+), 23 deletions(-)
diff --git a/drivers/misc/keba/cp500.c b/drivers/misc/keba/cp500.c
index ae0922817881..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,
}
};
@@ -229,7 +288,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,30 +310,149 @@ 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;
}
+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;
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)
@@ -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 related [flat|nested] 12+ messages in thread* Re: [PATCH 1/6] misc: keba: Add SPI controller device
2024-10-09 20:29 ` [PATCH 1/6] misc: keba: Add SPI controller device Gerhard Engleder
@ 2024-10-10 7:12 ` Greg KH
2024-10-10 19:53 ` Gerhard Engleder
2024-10-13 1:03 ` kernel test robot
1 sibling, 1 reply; 12+ messages in thread
From: Greg KH @ 2024-10-10 7:12 UTC (permalink / raw)
To: Gerhard Engleder; +Cc: linux-kernel, arnd, Gerhard Engleder
On Wed, Oct 09, 2024 at 10:29:44PM +0200, Gerhard Engleder wrote:
> 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");
Nit, this doesn't have anything to do with the original commit message,
please be more careful when splitting patches up into a series.
thanks,
greg k-h
^ permalink raw reply [flat|nested] 12+ messages in thread* Re: [PATCH 1/6] misc: keba: Add SPI controller device
2024-10-10 7:12 ` Greg KH
@ 2024-10-10 19:53 ` Gerhard Engleder
0 siblings, 0 replies; 12+ messages in thread
From: Gerhard Engleder @ 2024-10-10 19:53 UTC (permalink / raw)
To: Greg KH; +Cc: linux-kernel, arnd, Gerhard Engleder
On 10.10.24 09:12, Greg KH wrote:
> On Wed, Oct 09, 2024 at 10:29:44PM +0200, Gerhard Engleder wrote:
>> 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");
>
> Nit, this doesn't have anything to do with the original commit message,
> please be more careful when splitting patches up into a series.
Yes, I also did some variable renaming, which should be moved to a
separate commit. I will be more strict in the future.
Thanks!
Gerhard
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH 1/6] misc: keba: Add SPI controller device
2024-10-09 20:29 ` [PATCH 1/6] misc: keba: Add SPI controller device Gerhard Engleder
2024-10-10 7:12 ` Greg KH
@ 2024-10-13 1:03 ` kernel test robot
1 sibling, 0 replies; 12+ messages in thread
From: kernel test robot @ 2024-10-13 1:03 UTC (permalink / raw)
To: Gerhard Engleder, linux-kernel
Cc: oe-kbuild-all, arnd, gregkh, Gerhard Engleder
Hi Gerhard,
kernel test robot noticed the following build errors:
[auto build test ERROR on char-misc/char-misc-testing]
[also build test ERROR on char-misc/char-misc-next char-misc/char-misc-linus soc/for-next linus/master v6.12-rc2 next-20241011]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Gerhard-Engleder/misc-keba-Add-SPI-controller-device/20241010-051016
base: char-misc/char-misc-testing
patch link: https://lore.kernel.org/r/20241009202949.20164-2-gerhard%40engleder-embedded.com
patch subject: [PATCH 1/6] misc: keba: Add SPI controller device
config: csky-randconfig-r123-20241012 (https://download.01.org/0day-ci/archive/20241013/202410130817.NXBCxx4q-lkp@intel.com/config)
compiler: csky-linux-gcc (GCC) 14.1.0
reproduce: (https://download.01.org/0day-ci/archive/20241013/202410130817.NXBCxx4q-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202410130817.NXBCxx4q-lkp@intel.com/
All errors (new ones prefixed by >>):
csky-linux-ld: drivers/misc/keba/cp500.o: in function `cp500_nvmem_match':
>> drivers/misc/keba/cp500.c:396:(.text+0x15e): undefined reference to `i2c_verify_client'
csky-linux-ld: drivers/misc/keba/cp500.o: in function `cp500_i2c_release':
drivers/misc/keba/cp500.c:287:(.text+0x1c0): undefined reference to `i2c_verify_client'
vim +396 drivers/misc/keba/cp500.c
388
389 static int cp500_nvmem_match(struct device *dev, const void *data)
390 {
391 const struct cp500 *cp500 = data;
392 struct i2c_client *client;
393
394 /* match only CPU EEPROM below the cp500 device */
395 dev = dev->parent;
> 396 client = i2c_verify_client(dev);
397 if (!client || client->addr != CP500_EEPROM_ADDR)
398 return 0;
399 while ((dev = dev->parent))
400 if (dev == &cp500->pci_dev->dev)
401 return 1;
402
403 return 0;
404 }
405
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 2/6] misc: keba: Add LAN9252 driver
2024-10-09 20:29 [PATCH 0/6] misc: keba: Add support for additional devices Gerhard Engleder
2024-10-09 20:29 ` [PATCH 1/6] misc: keba: Add SPI controller device Gerhard Engleder
@ 2024-10-09 20:29 ` Gerhard Engleder
2024-10-11 8:28 ` kernel test robot
2024-10-09 20:29 ` [PATCH 3/6] misc: keba: Support EEPROM sections as separate devices Gerhard Engleder
` (3 subsequent siblings)
5 siblings, 1 reply; 12+ messages in thread
From: Gerhard Engleder @ 2024-10-09 20:29 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..d2e734239b70
--- /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 ret;
+ }
+
+ /* 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 related [flat|nested] 12+ messages in thread* Re: [PATCH 2/6] misc: keba: Add LAN9252 driver
2024-10-09 20:29 ` [PATCH 2/6] misc: keba: Add LAN9252 driver Gerhard Engleder
@ 2024-10-11 8:28 ` kernel test robot
0 siblings, 0 replies; 12+ messages in thread
From: kernel test robot @ 2024-10-11 8:28 UTC (permalink / raw)
To: Gerhard Engleder, linux-kernel
Cc: llvm, oe-kbuild-all, arnd, gregkh, Gerhard Engleder
Hi Gerhard,
kernel test robot noticed the following build warnings:
[auto build test WARNING on char-misc/char-misc-testing]
[also build test WARNING on char-misc/char-misc-next char-misc/char-misc-linus linus/master v6.12-rc2 next-20241010]
[cannot apply to soc/for-next]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Gerhard-Engleder/misc-keba-Add-SPI-controller-device/20241010-051016
base: char-misc/char-misc-testing
patch link: https://lore.kernel.org/r/20241009202949.20164-3-gerhard%40engleder-embedded.com
patch subject: [PATCH 2/6] misc: keba: Add LAN9252 driver
config: x86_64-allyesconfig (https://download.01.org/0day-ci/archive/20241011/202410111611.EMSfFrYK-lkp@intel.com/config)
compiler: clang version 18.1.8 (https://github.com/llvm/llvm-project 3b5b5c1ec4a3095ab096dd780e84d7ab81f3d7ff)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20241011/202410111611.EMSfFrYK-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202410111611.EMSfFrYK-lkp@intel.com/
All warnings (new ones prefixed by >>):
>> drivers/misc/keba/lan9252.c:299:10: warning: variable 'ret' is uninitialized when used here [-Wuninitialized]
299 | return ret;
| ^~~
drivers/misc/keba/lan9252.c:291:9: note: initialize the variable 'ret' to silence this warning
291 | int ret;
| ^
| = 0
1 warning generated.
vim +/ret +299 drivers/misc/keba/lan9252.c
286
287 static int lan9252_probe(struct spi_device *spi)
288 {
289 u32 data;
290 int retry = SPI_RETRY_COUNT;
291 int ret;
292
293 /* execute specified initialization sequence */
294 while (retry && !lan9252_init(spi))
295 retry--;
296 if (retry == 0) {
297 dev_err(&spi->dev,
298 "Can't initialize LAN9252 SPI communication!");
> 299 return ret;
300 }
301
302 /* enable access to MII management for PDI */
303 ret = lan9252_access_mii(spi, true);
304 if (ret) {
305 dev_err(&spi->dev, "Can't enable access to MII management!");
306 return ret;
307 }
308
309 /*
310 * check PHY configuration and configure if necessary
311 * - full duplex
312 * - auto negotiation disabled
313 * - 100 Mbps
314 */
315 ret = lan9252_mii_read(spi, PHY_ADDRESS, MII_BMCR, &data);
316 if (ret) {
317 dev_err(&spi->dev, "Can't read LAN9252 configuration!");
318 goto out;
319 }
320 if (!(data & BMCR_FULLDPLX) || (data & BMCR_ANENABLE) ||
321 !(data & BMCR_SPEED100)) {
322 /*
323 */
324 data &= ~(BMCR_ANENABLE);
325 data |= (BMCR_FULLDPLX | BMCR_SPEED100);
326 ret = lan9252_mii_write(spi, PHY_ADDRESS, MII_BMCR, data);
327 if (ret)
328 dev_err(&spi->dev,
329 "Can't write LAN9252 configuration!");
330 }
331
332 dev_info(&spi->dev, "LAN9252 PHY configuration");
333
334 out:
335 /* disable access to MII management for PDI */
336 lan9252_access_mii(spi, false);
337
338 return ret;
339 }
340
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 3/6] misc: keba: Support EEPROM sections as separate devices
2024-10-09 20:29 [PATCH 0/6] misc: keba: Add support for additional devices Gerhard Engleder
2024-10-09 20:29 ` [PATCH 1/6] misc: keba: Add SPI controller device Gerhard Engleder
2024-10-09 20:29 ` [PATCH 2/6] misc: keba: Add LAN9252 driver Gerhard Engleder
@ 2024-10-09 20:29 ` Gerhard Engleder
2024-10-13 8:52 ` kernel test robot
2024-10-09 20:29 ` [PATCH 4/6] misc: keba: Add fan device Gerhard Engleder
` (2 subsequent siblings)
5 siblings, 1 reply; 12+ messages in thread
From: Gerhard Engleder @ 2024-10-09 20:29 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 related [flat|nested] 12+ messages in thread* [PATCH 4/6] misc: keba: Add fan device
2024-10-09 20:29 [PATCH 0/6] misc: keba: Add support for additional devices Gerhard Engleder
` (2 preceding siblings ...)
2024-10-09 20:29 ` [PATCH 3/6] misc: keba: Support EEPROM sections as separate devices Gerhard Engleder
@ 2024-10-09 20:29 ` Gerhard Engleder
2024-10-09 20:29 ` [PATCH 5/6] misc: keba: Add battery device Gerhard Engleder
2024-10-09 20:29 ` [PATCH 6/6] misc: keba: Add UART devices Gerhard Engleder
5 siblings, 0 replies; 12+ messages in thread
From: Gerhard Engleder @ 2024-10-09 20:29 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 related [flat|nested] 12+ messages in thread* [PATCH 5/6] misc: keba: Add battery device
2024-10-09 20:29 [PATCH 0/6] misc: keba: Add support for additional devices Gerhard Engleder
` (3 preceding siblings ...)
2024-10-09 20:29 ` [PATCH 4/6] misc: keba: Add fan device Gerhard Engleder
@ 2024-10-09 20:29 ` Gerhard Engleder
2024-10-09 20:29 ` [PATCH 6/6] misc: keba: Add UART devices Gerhard Engleder
5 siblings, 0 replies; 12+ messages in thread
From: Gerhard Engleder @ 2024-10-09 20:29 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 related [flat|nested] 12+ messages in thread* [PATCH 6/6] misc: keba: Add UART devices
2024-10-09 20:29 [PATCH 0/6] misc: keba: Add support for additional devices Gerhard Engleder
` (4 preceding siblings ...)
2024-10-09 20:29 ` [PATCH 5/6] misc: keba: Add battery device Gerhard Engleder
@ 2024-10-09 20:29 ` Gerhard Engleder
5 siblings, 0 replies; 12+ messages in thread
From: Gerhard Engleder @ 2024-10-09 20:29 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 related [flat|nested] 12+ messages in thread