diff options
Diffstat (limited to 'drivers/misc/keba/cp500.c')
-rw-r--r-- | drivers/misc/keba/cp500.c | 558 |
1 files changed, 537 insertions, 21 deletions
diff --git a/drivers/misc/keba/cp500.c b/drivers/misc/keba/cp500.c index ae0922817881..255d3022dae8 100644 --- a/drivers/misc/keba/cp500.c +++ b/drivers/misc/keba/cp500.c @@ -12,7 +12,12 @@ #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" @@ -27,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 */ @@ -35,14 +41,35 @@ /* 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_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) + +/* 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) @@ -51,29 +78,56 @@ 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 }, - .i2c = { 0x5000, SZ_4K }, + .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 *nvmem; + unsigned int offset; }; struct cp500 { @@ -85,18 +139,31 @@ 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; + 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), @@ -107,30 +174,66 @@ static struct i2c_board_info cp500_i2c_info[] = { * CP505 family: bridge board * CP520 family: carrier board */ - I2C_BOARD_INFO("24c32", 0x50), - .dev_name = CP500_HW_CPU_EEPROM_NAME, + I2C_BOARD_INFO("24c32", CP500_EEPROM_ADDR), }, { /* 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 +332,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 +354,412 @@ 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 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->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; + 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 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) +{ + 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 = 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); + 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"); + 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) @@ -285,11 +770,34 @@ 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; } + 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) @@ -396,15 +904,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 +936,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); |