| // 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_obj(*cp500->i2c); | |
| 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_obj(*cp500->spi); | |
| 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_obj(*cp500->fan); | |
| 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_obj(*cp500->batt); | |
| 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_obj(**uart); | |
| 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 = pcim_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)) | |
| return PTR_ERR(cp500->system_startup_addr); | |
| 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"); | |
| return -ENODEV; | |
| } | |
| 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); | |
| 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); | |
| } | |
| 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"); |