diff options
Diffstat (limited to 'drivers/misc/keba/cp500.c')
-rw-r--r-- | drivers/misc/keba/cp500.c | 989 |
1 files changed, 989 insertions, 0 deletions
diff --git a/drivers/misc/keba/cp500.c b/drivers/misc/keba/cp500.c new file mode 100644 index 000000000000..d0c6113dcff3 --- /dev/null +++ b/drivers/misc/keba/cp500.c @@ -0,0 +1,989 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) KEBA Industrial Automation Gmbh 2024 + * + * Driver for KEBA system FPGA + * + * The KEBA system FPGA implements various devices. This driver registers + * auxiliary devices for every device within the FPGA. + */ + +#include <linux/device.h> +#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/nvmem-provider.h> +#include <linux/pci.h> +#include <linux/spi/flash.h> +#include <linux/spi/spi.h> + +#define CP500 "cp500" + +#define PCI_VENDOR_ID_KEBA 0xCEBA +#define PCI_DEVICE_ID_KEBA_CP035 0x2706 +#define PCI_DEVICE_ID_KEBA_CP505 0x2703 +#define PCI_DEVICE_ID_KEBA_CP520 0x2696 + +#define CP500_SYS_BAR 0 +#define CP500_ECM_BAR 1 + +/* 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 */ +#define CP500_BUILD_TEST 0x8000 /* FPGA test version */ + +/* 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_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 + +/* 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) + +/* 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) +#define CP500_IS_CP520(dev) ((dev)->pci_dev->device == PCI_DEVICE_ID_KEBA_CP520) + +struct cp500_dev_info { + off_t offset; + size_t size; + unsigned int msix; +}; + +struct cp500_devs { + struct cp500_dev_info startup; + struct cp500_dev_info spi; + 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) */ +static struct cp500_devs cp035_devices = { + .startup = { 0x0000, SZ_4K }, + .spi = { 0x1000, SZ_4K }, + .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) */ +static struct cp500_devs cp505_devices = { + .startup = { 0x0000, SZ_4K }, + .spi = { 0x4000, SZ_4K }, + .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) */ +static struct cp500_devs cp520_devices = { + .startup = { 0x0000, SZ_4K }, + .spi = { 0x4000, SZ_4K }, + .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 { + struct nvmem_device *base_nvmem; + unsigned int offset; + struct nvmem_device *nvmem; +}; + +struct cp500 { + struct pci_dev *pci_dev; + struct cp500_devs *devs; + int msix_num; + struct { + int major; + 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; + 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; + + /* NVMEM devices */ + struct cp500_nvmem nvmem_cpu; + struct cp500_nvmem nvmem_user; + + void __iomem *system_startup_addr; +}; + +/* I2C devices */ +#define CP500_EEPROM_ADDR 0x50 +static struct i2c_board_info cp500_i2c_info[] = { + { /* temperature sensor */ + I2C_BOARD_INFO("emc1403", 0x4c), + }, + { /* + * CPU EEPROM + * CP035 family: CPU board + * CP505 family: bridge board + * CP520 family: carrier board + */ + I2C_BOARD_INFO("24c32", CP500_EEPROM_ADDR), + }, + { /* interface board EEPROM */ + I2C_BOARD_INFO("24c32", CP500_EEPROM_ADDR + 1), + }, + { /* + * EEPROM (optional) + * CP505 family: CPU board + * CP520 family: MMI board + */ + I2C_BOARD_INFO("24c32", CP500_EEPROM_ADDR + 2), + }, + { /* extension module 0 EEPROM (optional) */ + I2C_BOARD_INFO("24c32", CP500_EEPROM_ADDR + 3), + }, + { /* extension module 1 EEPROM (optional) */ + I2C_BOARD_INFO("24c32", CP500_EEPROM_ADDR + 4), + }, + { /* extension module 2 EEPROM (optional) */ + I2C_BOARD_INFO("24c32", CP500_EEPROM_ADDR + 5), + }, + { /* extension module 3 EEPROM (optional) */ + 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, + } +}; + +static ssize_t cp500_get_fpga_version(struct cp500 *cp500, char *buf, + size_t max_len) +{ + int n; + + if (CP500_IS_CP035(cp500)) + n = scnprintf(buf, max_len, "CP035"); + else if (CP500_IS_CP505(cp500)) + n = scnprintf(buf, max_len, "CP505"); + else + n = scnprintf(buf, max_len, "CP500"); + + n += scnprintf(buf + n, max_len - n, "_FPGA_%d.%02d", + cp500->version.major, cp500->version.minor); + + /* test versions have test bit set */ + if (cp500->version.build & CP500_BUILD_TEST) + n += scnprintf(buf + n, max_len - n, "Test%d", + cp500->version.build & ~CP500_BUILD_TEST); + + n += scnprintf(buf + n, max_len - n, "\n"); + + return n; +} + +static ssize_t version_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct cp500 *cp500 = dev_get_drvdata(dev); + + return cp500_get_fpga_version(cp500, buf, PAGE_SIZE); +} +static DEVICE_ATTR_RO(version); + +static ssize_t keep_cfg_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct cp500 *cp500 = dev_get_drvdata(dev); + unsigned long keep_cfg = 1; + + /* + * FPGA configuration stream is kept during reset when RECONFIG bit is + * zero + */ + if (ioread8(cp500->system_startup_addr + CP500_RECONFIG_REG) & + CP500_RECFG_REQ) + keep_cfg = 0; + + return sysfs_emit(buf, "%lu\n", keep_cfg); +} + +static ssize_t keep_cfg_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct cp500 *cp500 = dev_get_drvdata(dev); + unsigned long keep_cfg; + + if (kstrtoul(buf, 10, &keep_cfg) < 0) + return -EINVAL; + + /* + * In normal operation "keep_cfg" is "1". This means that the FPGA keeps + * its configuration stream during a reset. + * In case of a firmware update of the FPGA, the configuration stream + * needs to be reloaded. This can be done without a powercycle by + * writing a "0" into the "keep_cfg" attribute. After a reset/reboot th + * new configuration stream will be loaded. + */ + if (keep_cfg) + iowrite8(0, cp500->system_startup_addr + CP500_RECONFIG_REG); + else + iowrite8(CP500_RECFG_REQ, + cp500->system_startup_addr + CP500_RECONFIG_REG); + + return count; +} +static DEVICE_ATTR_RW(keep_cfg); + +static struct attribute *cp500_attrs[] = { + &dev_attr_version.attr, + &dev_attr_keep_cfg.attr, + NULL +}; +ATTRIBUTE_GROUPS(cp500); + +static void cp500_i2c_release(struct device *dev) +{ + struct keba_i2c_auxdev *i2c = + container_of(dev, struct keba_i2c_auxdev, auxdev.dev); + + kfree(i2c); +} + +static int cp500_register_i2c(struct cp500 *cp500) +{ + int ret; + + cp500->i2c = kzalloc(sizeof(*cp500->i2c), GFP_KERNEL); + if (!cp500->i2c) + return -ENOMEM; + + cp500->i2c->auxdev.name = "i2c"; + cp500->i2c->auxdev.id = 0; + cp500->i2c->auxdev.dev.release = cp500_i2c_release; + cp500->i2c->auxdev.dev.parent = &cp500->pci_dev->dev; + cp500->i2c->io = (struct resource) { + /* I2C register area */ + .start = (resource_size_t) cp500->sys_hwbase + + cp500->devs->i2c.offset, + .end = (resource_size_t) cp500->sys_hwbase + + cp500->devs->i2c.offset + + cp500->devs->i2c.size - 1, + .flags = IORESOURCE_MEM, + }; + cp500->i2c->info_size = ARRAY_SIZE(cp500_i2c_info); + cp500->i2c->info = cp500_i2c_info; + + ret = auxiliary_device_init(&cp500->i2c->auxdev); + if (ret) { + kfree(cp500->i2c); + cp500->i2c = NULL; + + return ret; + } + ret = __auxiliary_device_add(&cp500->i2c->auxdev, "keba"); + if (ret) { + auxiliary_device_uninit(&cp500->i2c->auxdev); + cp500->i2c = NULL; + + 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 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 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 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) +{ + struct cp500_nvmem *nvmem = priv; + int ret; + + ret = nvmem_device_read(nvmem->base_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->base_nvmem, nvmem->offset + offset, + bytes, val); + if (ret != bytes) + return ret; + + return 0; +} + +static int cp500_nvmem_register(struct cp500 *cp500, + struct nvmem_device *base_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.base_nvmem = base_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 = nvmem_register(&nvmem_config); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + cp500->nvmem_cpu.nvmem = tmp; + + cp500->nvmem_user.base_nvmem = base_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 = nvmem_register(&nvmem_config); + if (IS_ERR(tmp)) { + nvmem_unregister(cp500->nvmem_cpu.nvmem); + cp500->nvmem_cpu.nvmem = NULL; + + return PTR_ERR(tmp); + } + cp500->nvmem_user.nvmem = tmp; + + return 0; +} + +static void cp500_nvmem_unregister(struct cp500 *cp500) +{ + int notified; + + if (cp500->nvmem_user.nvmem) { + nvmem_unregister(cp500->nvmem_user.nvmem); + cp500->nvmem_user.nvmem = NULL; + } + if (cp500->nvmem_cpu.nvmem) { + nvmem_unregister(cp500->nvmem_cpu.nvmem); + cp500->nvmem_cpu.nvmem = NULL; + } + + /* CPU and user nvmem use the same base_nvmem, put only once */ + notified = atomic_read(&cp500->nvmem_notified); + if (notified) + nvmem_device_put(cp500->nvmem_cpu.base_nvmem); +} + +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 = cp500_nvmem_register(cp500, nvmem); + if (ret) + return ret; + + ret = nvmem_device_read(nvmem, CP500_EEPROM_DA_OFFSET, sizeof(esc_type), + (void *)&esc_type); + 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; + 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"); + 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) +{ + auxiliary_device_delete(auxdev); + auxiliary_device_uninit(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; + } + if (cp500->fan) { + cp500_unregister_dev(&cp500->fan->auxdev); + cp500->fan = NULL; + } + if (cp500->batt) { + 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) +{ + struct cp500 *cp500 = dev; + u32 axi_address = ioread32(cp500->system_startup_addr + CP500_AXI_REG); + + /* + * FPGA signals AXI response error, print AXI address to indicate which + * IP core was affected + */ + dev_err(&cp500->pci_dev->dev, "AXI response error at 0x%08x\n", + axi_address); + + return IRQ_HANDLED; +} + +static int cp500_enable(struct cp500 *cp500) +{ + int axi_irq = -1; + int ret; + + if (cp500->msix_num > CP500_NUM_MSIX_NO_AXI) { + axi_irq = pci_irq_vector(cp500->pci_dev, CP500_AXI_MSIX); + ret = request_irq(axi_irq, cp500_axi_handler, 0, + CP500, cp500); + if (ret != 0) { + dev_err(&cp500->pci_dev->dev, + "Failed to register AXI response error!\n"); + return ret; + } + } + + return 0; +} + +static void cp500_disable(struct cp500 *cp500) +{ + int axi_irq; + + if (cp500->msix_num > CP500_NUM_MSIX_NO_AXI) { + axi_irq = pci_irq_vector(cp500->pci_dev, CP500_AXI_MSIX); + free_irq(axi_irq, cp500); + } +} + +static int cp500_probe(struct pci_dev *pci_dev, const struct pci_device_id *id) +{ + struct device *dev = &pci_dev->dev; + struct resource startup; + struct cp500 *cp500; + u32 cp500_vers; + char buf[64]; + int ret; + + cp500 = devm_kzalloc(dev, sizeof(*cp500), GFP_KERNEL); + if (!cp500) + return -ENOMEM; + cp500->pci_dev = pci_dev; + cp500->sys_hwbase = pci_resource_start(pci_dev, CP500_SYS_BAR); + cp500->ecm_hwbase = pci_resource_start(pci_dev, CP500_ECM_BAR); + if (!cp500->sys_hwbase || !cp500->ecm_hwbase) + return -ENODEV; + + if (CP500_IS_CP035(cp500)) + cp500->devs = &cp035_devices; + else if (CP500_IS_CP505(cp500)) + cp500->devs = &cp505_devices; + else if (CP500_IS_CP520(cp500)) + cp500->devs = &cp520_devices; + else + return -ENODEV; + + ret = pci_enable_device(pci_dev); + if (ret) + return ret; + pci_set_master(pci_dev); + + startup = *pci_resource_n(pci_dev, CP500_SYS_BAR); + startup.end = startup.start + cp500->devs->startup.size - 1; + cp500->system_startup_addr = devm_ioremap_resource(&pci_dev->dev, + &startup); + if (IS_ERR(cp500->system_startup_addr)) { + ret = PTR_ERR(cp500->system_startup_addr); + goto out_disable; + } + + cp500->msix_num = pci_alloc_irq_vectors(pci_dev, CP500_NUM_MSIX_NO_MMI, + CP500_NUM_MSIX, PCI_IRQ_MSIX); + if (cp500->msix_num < CP500_NUM_MSIX_NO_MMI) { + dev_err(&pci_dev->dev, + "Hardware does not support enough MSI-X interrupts\n"); + ret = -ENODEV; + goto out_disable; + } + + cp500_vers = ioread32(cp500->system_startup_addr + CP500_VERSION_REG); + cp500->version.major = (cp500_vers & 0xff); + cp500->version.minor = (cp500_vers >> 8) & 0xff; + cp500->version.build = (cp500_vers >> 16) & 0xffff; + cp500_get_fpga_version(cp500, buf, sizeof(buf)); + + dev_info(&pci_dev->dev, "FPGA version %s", buf); + + 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_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: + pci_clear_master(pci_dev); + pci_disable_device(pci_dev); + + return ret; +} + +static void cp500_remove(struct pci_dev *pci_dev) +{ + struct cp500 *cp500 = pci_get_drvdata(pci_dev); + + /* + * unregister CPU and user nvmem and put base_nvmem before parent + * auxiliary device of base_nvmem is unregistered + */ + nvmem_unregister_notifier(&cp500->nvmem_notifier); + cp500_nvmem_unregister(cp500); + + cp500_unregister_auxiliary_devs(cp500); + + cp500_disable(cp500); + + pci_set_drvdata(pci_dev, 0); + + pci_free_irq_vectors(pci_dev); + + pci_clear_master(pci_dev); + pci_disable_device(pci_dev); +} + +static struct pci_device_id cp500_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_KEBA, PCI_DEVICE_ID_KEBA_CP035) }, + { PCI_DEVICE(PCI_VENDOR_ID_KEBA, PCI_DEVICE_ID_KEBA_CP505) }, + { PCI_DEVICE(PCI_VENDOR_ID_KEBA, PCI_DEVICE_ID_KEBA_CP520) }, + { } +}; +MODULE_DEVICE_TABLE(pci, cp500_ids); + +static struct pci_driver cp500_driver = { + .name = CP500, + .id_table = cp500_ids, + .probe = cp500_probe, + .remove = cp500_remove, + .dev_groups = cp500_groups, +}; +module_pci_driver(cp500_driver); + +MODULE_AUTHOR("Gerhard Engleder <eg@keba.com>"); +MODULE_DESCRIPTION("KEBA CP500 system FPGA driver"); +MODULE_LICENSE("GPL"); |