diff options
author | Thomas Bogendoerfer <tbogendoerfer@suse.de> | 2020-01-09 11:34:29 +0100 |
---|---|---|
committer | Paul Burton <paulburton@kernel.org> | 2020-01-09 15:30:59 -0800 |
commit | 0ce5ebd24d25f02c73940f047b12733d84b125e8 (patch) | |
tree | 3cbdc0a8e8baff23e120b7b8ffdf1ded8e5c8a1d /drivers | |
parent | 10cf8300ecada10f4bd6c9be78439cce98ae6d8b (diff) |
mfd: ioc3: Add driver for SGI IOC3 chip
SGI IOC3 chip has integrated ethernet, keyboard and mouse interface.
It also supports connecting a SuperIO chip for serial and parallel
interfaces. IOC3 is used inside various SGI systemboards and add-on
cards with different equipped external interfaces.
Support for ethernet and serial interfaces were implemented inside
the network driver. This patchset moves out the not network related
parts to a new MFD driver, which takes care of card detection,
setup of platform devices and interrupt distribution for the subdevices.
Serial portion: Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Acked-for-MFD-by: Lee Jones <lee.jones@linaro.org>
Network part: Reviewed-by: Jakub Kicinski <jakub.kicinski@netronome.com>
Network part: Acked-by: Jakub Kicinski <jakub.kicinski@netronome.com>
Signed-off-by: Thomas Bogendoerfer <tbogendoerfer@suse.de>
Signed-off-by: Paul Burton <paulburton@kernel.org>
Cc: James Hogan <jhogan@kernel.org>
Cc: David S. Miller <davem@davemloft.net>
Cc: Jiri Slaby <jslaby@suse.com>
Cc: linux-mips@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: netdev@vger.kernel.org
Cc: linux-serial@vger.kernel.org
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/mfd/Kconfig | 13 | ||||
-rw-r--r-- | drivers/mfd/Makefile | 1 | ||||
-rw-r--r-- | drivers/mfd/ioc3.c | 669 | ||||
-rw-r--r-- | drivers/net/ethernet/sgi/Kconfig | 5 | ||||
-rw-r--r-- | drivers/net/ethernet/sgi/ioc3-eth.c | 556 | ||||
-rw-r--r-- | drivers/tty/serial/8250/8250_ioc3.c | 98 | ||||
-rw-r--r-- | drivers/tty/serial/8250/Kconfig | 11 | ||||
-rw-r--r-- | drivers/tty/serial/8250/Makefile | 1 |
8 files changed, 893 insertions, 461 deletions
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 420900852166..7a4a41dddc97 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -2004,5 +2004,18 @@ config RAVE_SP_CORE Select this to get support for the Supervisory Processor device found on several devices in RAVE line of hardware. +config SGI_MFD_IOC3 + tristate "SGI IOC3 core driver" + depends on PCI && MIPS && 64BIT + select MFD_CORE + help + This option enables basic support for the SGI IOC3-based + controller cards. This option does not enable any specific + functions on such a card, but provides necessary infrastructure + for other drivers to utilize. + + If you have an SGI Origin, Octane, or a PCI IOC3 card, + then say Y. Otherwise say N. + endmenu endif diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index aed99f08739f..5fe930c76ade 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -255,3 +255,4 @@ obj-$(CONFIG_MFD_ROHM_BD70528) += rohm-bd70528.o obj-$(CONFIG_MFD_ROHM_BD718XX) += rohm-bd718x7.o obj-$(CONFIG_MFD_STMFX) += stmfx.o +obj-$(CONFIG_SGI_MFD_IOC3) += ioc3.o diff --git a/drivers/mfd/ioc3.c b/drivers/mfd/ioc3.c new file mode 100644 index 000000000000..02998d4eb74b --- /dev/null +++ b/drivers/mfd/ioc3.c @@ -0,0 +1,669 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * SGI IOC3 multifunction device driver + * + * Copyright (C) 2018, 2019 Thomas Bogendoerfer <tbogendoerfer@suse.de> + * + * Based on work by: + * Stanislaw Skowronek <skylark@unaligned.org> + * Joshua Kinard <kumba@gentoo.org> + * Brent Casavant <bcasavan@sgi.com> - IOC4 master driver + * Pat Gefre <pfg@sgi.com> - IOC3 serial port IRQ demuxer + */ + +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/mfd/core.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/platform_device.h> +#include <linux/platform_data/sgi-w1.h> +#include <linux/rtc/ds1685.h> + +#include <asm/pci/bridge.h> +#include <asm/sn/ioc3.h> + +#define IOC3_IRQ_SERIAL_A 6 +#define IOC3_IRQ_SERIAL_B 15 +#define IOC3_IRQ_KBD 22 + +/* Bitmask for selecting which IRQs are level triggered */ +#define IOC3_LVL_MASK (BIT(IOC3_IRQ_SERIAL_A) | BIT(IOC3_IRQ_SERIAL_B)) + +#define M48T35_REG_SIZE 32768 /* size of m48t35 registers */ + +/* 1.2 us latency timer (40 cycles at 33 MHz) */ +#define IOC3_LATENCY 40 + +struct ioc3_priv_data { + struct irq_domain *domain; + struct ioc3 __iomem *regs; + struct pci_dev *pdev; + int domain_irq; +}; + +static void ioc3_irq_ack(struct irq_data *d) +{ + struct ioc3_priv_data *ipd = irq_data_get_irq_chip_data(d); + unsigned int hwirq = irqd_to_hwirq(d); + + writel(BIT(hwirq), &ipd->regs->sio_ir); +} + +static void ioc3_irq_mask(struct irq_data *d) +{ + struct ioc3_priv_data *ipd = irq_data_get_irq_chip_data(d); + unsigned int hwirq = irqd_to_hwirq(d); + + writel(BIT(hwirq), &ipd->regs->sio_iec); +} + +static void ioc3_irq_unmask(struct irq_data *d) +{ + struct ioc3_priv_data *ipd = irq_data_get_irq_chip_data(d); + unsigned int hwirq = irqd_to_hwirq(d); + + writel(BIT(hwirq), &ipd->regs->sio_ies); +} + +static struct irq_chip ioc3_irq_chip = { + .name = "IOC3", + .irq_ack = ioc3_irq_ack, + .irq_mask = ioc3_irq_mask, + .irq_unmask = ioc3_irq_unmask, +}; + +static int ioc3_irq_domain_map(struct irq_domain *d, unsigned int irq, + irq_hw_number_t hwirq) +{ + /* Set level IRQs for every interrupt contained in IOC3_LVL_MASK */ + if (BIT(hwirq) & IOC3_LVL_MASK) + irq_set_chip_and_handler(irq, &ioc3_irq_chip, handle_level_irq); + else + irq_set_chip_and_handler(irq, &ioc3_irq_chip, handle_edge_irq); + + irq_set_chip_data(irq, d->host_data); + return 0; +} + +static void ioc3_irq_domain_unmap(struct irq_domain *d, unsigned int irq) +{ + irq_set_chip_and_handler(irq, NULL, NULL); + irq_set_chip_data(irq, NULL); +} + +static const struct irq_domain_ops ioc3_irq_domain_ops = { + .map = ioc3_irq_domain_map, + .unmap = ioc3_irq_domain_unmap, +}; + +static void ioc3_irq_handler(struct irq_desc *desc) +{ + struct irq_domain *domain = irq_desc_get_handler_data(desc); + struct ioc3_priv_data *ipd = domain->host_data; + struct ioc3 __iomem *regs = ipd->regs; + u32 pending, mask; + unsigned int irq; + + pending = readl(®s->sio_ir); + mask = readl(®s->sio_ies); + pending &= mask; /* Mask off not enabled interrupts */ + + if (pending) { + irq = irq_find_mapping(domain, __ffs(pending)); + if (irq) + generic_handle_irq(irq); + } else { + spurious_interrupt(); + } +} + +/* + * System boards/BaseIOs use more interrupt pins of the bridge ASIC + * to which the IOC3 is connected. Since the IOC3 MFD driver + * knows wiring of these extra pins, we use the map_irq function + * to get interrupts activated + */ +static int ioc3_map_irq(struct pci_dev *pdev, int slot, int pin) +{ + struct pci_host_bridge *hbrg = pci_find_host_bridge(pdev->bus); + + return hbrg->map_irq(pdev, slot, pin); +} + +static int ioc3_irq_domain_setup(struct ioc3_priv_data *ipd, int irq) +{ + struct irq_domain *domain; + struct fwnode_handle *fn; + + fn = irq_domain_alloc_named_fwnode("IOC3"); + if (!fn) + goto err; + + domain = irq_domain_create_linear(fn, 24, &ioc3_irq_domain_ops, ipd); + if (!domain) + goto err; + + irq_domain_free_fwnode(fn); + ipd->domain = domain; + + irq_set_chained_handler_and_data(irq, ioc3_irq_handler, domain); + ipd->domain_irq = irq; + return 0; + +err: + dev_err(&ipd->pdev->dev, "irq domain setup failed\n"); + return -ENOMEM; +} + +static struct resource ioc3_uarta_resources[] = { + DEFINE_RES_MEM(offsetof(struct ioc3, sregs.uarta), + sizeof_field(struct ioc3, sregs.uarta)), + DEFINE_RES_IRQ(IOC3_IRQ_SERIAL_A) +}; + +static struct resource ioc3_uartb_resources[] = { + DEFINE_RES_MEM(offsetof(struct ioc3, sregs.uartb), + sizeof_field(struct ioc3, sregs.uartb)), + DEFINE_RES_IRQ(IOC3_IRQ_SERIAL_B) +}; + +static struct mfd_cell ioc3_serial_cells[] = { + { + .name = "ioc3-serial8250", + .resources = ioc3_uarta_resources, + .num_resources = ARRAY_SIZE(ioc3_uarta_resources), + }, + { + .name = "ioc3-serial8250", + .resources = ioc3_uartb_resources, + .num_resources = ARRAY_SIZE(ioc3_uartb_resources), + } +}; + +static int ioc3_serial_setup(struct ioc3_priv_data *ipd) +{ + int ret; + + /* Set gpio pins for RS232/RS422 mode selection */ + writel(GPCR_UARTA_MODESEL | GPCR_UARTB_MODESEL, + &ipd->regs->gpcr_s); + /* Select RS232 mode for uart a */ + writel(0, &ipd->regs->gppr[6]); + /* Select RS232 mode for uart b */ + writel(0, &ipd->regs->gppr[7]); + + /* Switch both ports to 16650 mode */ + writel(readl(&ipd->regs->port_a.sscr) & ~SSCR_DMA_EN, + &ipd->regs->port_a.sscr); + writel(readl(&ipd->regs->port_b.sscr) & ~SSCR_DMA_EN, + &ipd->regs->port_b.sscr); + udelay(1000); /* Wait until mode switch is done */ + + ret = mfd_add_devices(&ipd->pdev->dev, PLATFORM_DEVID_AUTO, + ioc3_serial_cells, ARRAY_SIZE(ioc3_serial_cells), + &ipd->pdev->resource[0], 0, ipd->domain); + if (ret) { + dev_err(&ipd->pdev->dev, "Failed to add 16550 subdevs\n"); + return ret; + } + + return 0; +} + +static struct resource ioc3_kbd_resources[] = { + DEFINE_RES_MEM(offsetof(struct ioc3, serio), + sizeof_field(struct ioc3, serio)), + DEFINE_RES_IRQ(IOC3_IRQ_KBD) +}; + +static struct mfd_cell ioc3_kbd_cells[] = { + { + .name = "ioc3-kbd", + .resources = ioc3_kbd_resources, + .num_resources = ARRAY_SIZE(ioc3_kbd_resources), + } +}; + +static int ioc3_kbd_setup(struct ioc3_priv_data *ipd) +{ + int ret; + + ret = mfd_add_devices(&ipd->pdev->dev, PLATFORM_DEVID_AUTO, + ioc3_kbd_cells, ARRAY_SIZE(ioc3_kbd_cells), + &ipd->pdev->resource[0], 0, ipd->domain); + if (ret) { + dev_err(&ipd->pdev->dev, "Failed to add 16550 subdevs\n"); + return ret; + } + + return 0; +} + +static struct resource ioc3_eth_resources[] = { + DEFINE_RES_MEM(offsetof(struct ioc3, eth), + sizeof_field(struct ioc3, eth)), + DEFINE_RES_MEM(offsetof(struct ioc3, ssram), + sizeof_field(struct ioc3, ssram)), + DEFINE_RES_IRQ(0) +}; + +static struct resource ioc3_w1_resources[] = { + DEFINE_RES_MEM(offsetof(struct ioc3, mcr), + sizeof_field(struct ioc3, mcr)), +}; +static struct sgi_w1_platform_data ioc3_w1_platform_data; + +static struct mfd_cell ioc3_eth_cells[] = { + { + .name = "ioc3-eth", + .resources = ioc3_eth_resources, + .num_resources = ARRAY_SIZE(ioc3_eth_resources), + }, + { + .name = "sgi_w1", + .resources = ioc3_w1_resources, + .num_resources = ARRAY_SIZE(ioc3_w1_resources), + .platform_data = &ioc3_w1_platform_data, + .pdata_size = sizeof(ioc3_w1_platform_data), + } +}; + +static int ioc3_eth_setup(struct ioc3_priv_data *ipd) +{ + int ret; + + /* Enable One-Wire bus */ + writel(GPCR_MLAN_EN, &ipd->regs->gpcr_s); + + /* Generate unique identifier */ + snprintf(ioc3_w1_platform_data.dev_id, + sizeof(ioc3_w1_platform_data.dev_id), "ioc3-%012llx", + ipd->pdev->resource->start); + + ret = mfd_add_devices(&ipd->pdev->dev, PLATFORM_DEVID_AUTO, + ioc3_eth_cells, ARRAY_SIZE(ioc3_eth_cells), + &ipd->pdev->resource[0], ipd->pdev->irq, NULL); + if (ret) { + dev_err(&ipd->pdev->dev, "Failed to add ETH/W1 subdev\n"); + return ret; + } + + return 0; +} + +static struct resource ioc3_m48t35_resources[] = { + DEFINE_RES_MEM(IOC3_BYTEBUS_DEV0, M48T35_REG_SIZE) +}; + +static struct mfd_cell ioc3_m48t35_cells[] = { + { + .name = "rtc-m48t35", + .resources = ioc3_m48t35_resources, + .num_resources = ARRAY_SIZE(ioc3_m48t35_resources), + } +}; + +static int ioc3_m48t35_setup(struct ioc3_priv_data *ipd) +{ + int ret; + + ret = mfd_add_devices(&ipd->pdev->dev, PLATFORM_DEVID_AUTO, + ioc3_m48t35_cells, ARRAY_SIZE(ioc3_m48t35_cells), + &ipd->pdev->resource[0], 0, ipd->domain); + if (ret) + dev_err(&ipd->pdev->dev, "Failed to add M48T35 subdev\n"); + + return ret; +} + +static struct ds1685_rtc_platform_data ip30_rtc_platform_data = { + .bcd_mode = false, + .no_irq = false, + .uie_unsupported = true, + .access_type = ds1685_reg_indirect, +}; + +static struct resource ioc3_rtc_ds1685_resources[] = { + DEFINE_RES_MEM(IOC3_BYTEBUS_DEV1, 1), + DEFINE_RES_MEM(IOC3_BYTEBUS_DEV2, 1), + DEFINE_RES_IRQ(0) +}; + +static struct mfd_cell ioc3_ds1685_cells[] = { + { + .name = "rtc-ds1685", + .resources = ioc3_rtc_ds1685_resources, + .num_resources = ARRAY_SIZE(ioc3_rtc_ds1685_resources), + .platform_data = &ip30_rtc_platform_data, + .pdata_size = sizeof(ip30_rtc_platform_data), + .id = PLATFORM_DEVID_NONE, + } +}; + +static int ioc3_ds1685_setup(struct ioc3_priv_data *ipd) +{ + int ret, irq; + + irq = ioc3_map_irq(ipd->pdev, 6, 0); + + ret = mfd_add_devices(&ipd->pdev->dev, 0, ioc3_ds1685_cells, + ARRAY_SIZE(ioc3_ds1685_cells), + &ipd->pdev->resource[0], irq, NULL); + if (ret) + dev_err(&ipd->pdev->dev, "Failed to add DS1685 subdev\n"); + + return ret; +}; + + +static struct resource ioc3_leds_resources[] = { + DEFINE_RES_MEM(offsetof(struct ioc3, gppr[0]), + sizeof_field(struct ioc3, gppr[0])), + DEFINE_RES_MEM(offsetof(struct ioc3, gppr[1]), + sizeof_field(struct ioc3, gppr[1])), +}; + +static struct mfd_cell ioc3_led_cells[] = { + { + .name = "ip30-leds", + .resources = ioc3_leds_resources, + .num_resources = ARRAY_SIZE(ioc3_leds_resources), + .id = PLATFORM_DEVID_NONE, + } +}; + +static int ioc3_led_setup(struct ioc3_priv_data *ipd) +{ + int ret; + + ret = mfd_add_devices(&ipd->pdev->dev, 0, ioc3_led_cells, + ARRAY_SIZE(ioc3_led_cells), + &ipd->pdev->resource[0], 0, ipd->domain); + if (ret) + dev_err(&ipd->pdev->dev, "Failed to add LED subdev\n"); + + return ret; +} + +static int ip27_baseio_setup(struct ioc3_priv_data *ipd) +{ + int ret, io_irq; + + io_irq = ioc3_map_irq(ipd->pdev, PCI_SLOT(ipd->pdev->devfn), + PCI_INTERRUPT_INTB); + ret = ioc3_irq_domain_setup(ipd, io_irq); + if (ret) + return ret; + + ret = ioc3_eth_setup(ipd); + if (ret) + return ret; + + ret = ioc3_serial_setup(ipd); + if (ret) + return ret; + + return ioc3_m48t35_setup(ipd); +} + +static int ip27_baseio6g_setup(struct ioc3_priv_data *ipd) +{ + int ret, io_irq; + + io_irq = ioc3_map_irq(ipd->pdev, PCI_SLOT(ipd->pdev->devfn), + PCI_INTERRUPT_INTB); + ret = ioc3_irq_domain_setup(ipd, io_irq); + if (ret) + return ret; + + ret = ioc3_eth_setup(ipd); + if (ret) + return ret; + + ret = ioc3_serial_setup(ipd); + if (ret) + return ret; + + ret = ioc3_m48t35_setup(ipd); + if (ret) + return ret; + + return ioc3_kbd_setup(ipd); +} + +static int ip27_mio_setup(struct ioc3_priv_data *ipd) +{ + int ret; + + ret = ioc3_irq_domain_setup(ipd, ipd->pdev->irq); + if (ret) + return ret; + + ret = ioc3_serial_setup(ipd); + if (ret) + return ret; + + return ioc3_kbd_setup(ipd); +} + +static int ip30_sysboard_setup(struct ioc3_priv_data *ipd) +{ + int ret, io_irq; + + io_irq = ioc3_map_irq(ipd->pdev, PCI_SLOT(ipd->pdev->devfn), + PCI_INTERRUPT_INTB); + ret = ioc3_irq_domain_setup(ipd, io_irq); + if (ret) + return ret; + + ret = ioc3_eth_setup(ipd); + if (ret) + return ret; + + ret = ioc3_serial_setup(ipd); + if (ret) + return ret; + + ret = ioc3_kbd_setup(ipd); + if (ret) + return ret; + + ret = ioc3_ds1685_setup(ipd); + if (ret) + return ret; + + return ioc3_led_setup(ipd); +} + +static int ioc3_menet_setup(struct ioc3_priv_data *ipd) +{ + int ret, io_irq; + + io_irq = ioc3_map_irq(ipd->pdev, PCI_SLOT(ipd->pdev->devfn), + PCI_INTERRUPT_INTB); + ret = ioc3_irq_domain_setup(ipd, io_irq); + if (ret) + return ret; + + ret = ioc3_eth_setup(ipd); + if (ret) + return ret; + + return ioc3_serial_setup(ipd); +} + +static int ioc3_menet4_setup(struct ioc3_priv_data *ipd) +{ + return ioc3_eth_setup(ipd); +} + +static int ioc3_cad_duo_setup(struct ioc3_priv_data *ipd) +{ + int ret, io_irq; + + io_irq = ioc3_map_irq(ipd->pdev, PCI_SLOT(ipd->pdev->devfn), + PCI_INTERRUPT_INTB); + ret = ioc3_irq_domain_setup(ipd, io_irq); + if (ret) + return ret; + + ret = ioc3_eth_setup(ipd); + if (ret) + return ret; + + return ioc3_kbd_setup(ipd); +} + +/* Helper macro for filling ioc3_info array */ +#define IOC3_SID(_name, _sid, _setup) \ + { \ + .name = _name, \ + .sid = PCI_VENDOR_ID_SGI | (IOC3_SUBSYS_ ## _sid << 16), \ + .setup = _setup, \ + } + +static struct { + const char *name; + u32 sid; + int (*setup)(struct ioc3_priv_data *ipd); +} ioc3_infos[] = { + IOC3_SID("IP27 BaseIO6G", IP27_BASEIO6G, &ip27_baseio6g_setup), + IOC3_SID("IP27 MIO", IP27_MIO, &ip27_mio_setup), + IOC3_SID("IP27 BaseIO", IP27_BASEIO, &ip27_baseio_setup), + IOC3_SID("IP29 System Board", IP29_SYSBOARD, &ip27_baseio6g_setup), + IOC3_SID("IP30 System Board", IP30_SYSBOARD, &ip30_sysboard_setup), + IOC3_SID("MENET", MENET, &ioc3_menet_setup), + IOC3_SID("MENET4", MENET4, &ioc3_menet4_setup) +}; +#undef IOC3_SID + +static int ioc3_setup(struct ioc3_priv_data *ipd) +{ + u32 sid; + int i; + + /* Clear IRQs */ + writel(~0, &ipd->regs->sio_iec); + writel(~0, &ipd->regs->sio_ir); + writel(0, &ipd->regs->eth.eier); + writel(~0, &ipd->regs->eth.eisr); + + /* Read subsystem vendor id and subsystem id */ + pci_read_config_dword(ipd->pdev, PCI_SUBSYSTEM_VENDOR_ID, &sid); + + for (i = 0; i < ARRAY_SIZE(ioc3_infos); i++) + if (sid == ioc3_infos[i].sid) { + pr_info("ioc3: %s\n", ioc3_infos[i].name); + return ioc3_infos[i].setup(ipd); + } + + /* Treat everything not identified by PCI subid as CAD DUO */ + pr_info("ioc3: CAD DUO\n"); + return ioc3_cad_duo_setup(ipd); +} + +static int ioc3_mfd_probe(struct pci_dev *pdev, + const struct pci_device_id *pci_id) +{ + struct ioc3_priv_data *ipd; + struct ioc3 __iomem *regs; + int ret; + + ret = pci_enable_device(pdev); + if (ret) + return ret; + + pci_write_config_byte(pdev, PCI_LATENCY_TIMER, IOC3_LATENCY); + pci_set_master(pdev); + + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); + if (ret) { + pr_err("%s: No usable DMA configuration, aborting.\n", + pci_name(pdev)); + goto out_disable_device; + } + + /* Set up per-IOC3 data */ + ipd = devm_kzalloc(&pdev->dev, sizeof(struct ioc3_priv_data), + GFP_KERNEL); + if (!ipd) { + ret = -ENOMEM; + goto out_disable_device; + } + ipd->pdev = pdev; + + /* + * Map all IOC3 registers. These are shared between subdevices + * so the main IOC3 module manages them. + */ + regs = pci_ioremap_bar(pdev, 0); + if (!regs) { + dev_warn(&pdev->dev, "ioc3: Unable to remap PCI BAR for %s.\n", + pci_name(pdev)); + ret = -ENOMEM; + goto out_disable_device; + } + ipd->regs = regs; + + /* Track PCI-device specific data */ + pci_set_drvdata(pdev, ipd); + + ret = ioc3_setup(ipd); + if (ret) { + /* Remove all already added MFD devices */ + mfd_remove_devices(&ipd->pdev->dev); + if (ipd->domain) { + irq_domain_remove(ipd->domain); + free_irq(ipd->domain_irq, (void *)ipd); + } + pci_iounmap(pdev, regs); + goto out_disable_device; + } + + return 0; + +out_disable_device: + pci_disable_device(pdev); + return ret; +} + +static void ioc3_mfd_remove(struct pci_dev *pdev) +{ + struct ioc3_priv_data *ipd; + + ipd = pci_get_drvdata(pdev); + + /* Clear and disable all IRQs */ + writel(~0, &ipd->regs->sio_iec); + writel(~0, &ipd->regs->sio_ir); + + /* Release resources */ + mfd_remove_devices(&ipd->pdev->dev); + if (ipd->domain) { + irq_domain_remove(ipd->domain); + free_irq(ipd->domain_irq, (void *)ipd); + } + pci_iounmap(pdev, ipd->regs); + pci_disable_device(pdev); +} + +static struct pci_device_id ioc3_mfd_id_table[] = { + { PCI_VENDOR_ID_SGI, PCI_DEVICE_ID_SGI_IOC3, PCI_ANY_ID, PCI_ANY_ID }, + { 0, }, +}; +MODULE_DEVICE_TABLE(pci, ioc3_mfd_id_table); + +static struct pci_driver ioc3_mfd_driver = { + .name = "IOC3", + .id_table = ioc3_mfd_id_table, + .probe = ioc3_mfd_probe, + .remove = ioc3_mfd_remove, +}; + +module_pci_driver(ioc3_mfd_driver); + +MODULE_AUTHOR("Thomas Bogendoerfer <tbogendoerfer@suse.de>"); +MODULE_DESCRIPTION("SGI IOC3 MFD driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/ethernet/sgi/Kconfig b/drivers/net/ethernet/sgi/Kconfig index 37f048e1230c..bc26fa0d196f 100644 --- a/drivers/net/ethernet/sgi/Kconfig +++ b/drivers/net/ethernet/sgi/Kconfig @@ -6,7 +6,7 @@ config NET_VENDOR_SGI bool "SGI devices" default y - depends on (PCI && SGI_IP27) || SGI_IP32 + depends on (PCI && SGI_MFD_IOC3) || SGI_IP32 ---help--- If you have a network (Ethernet) card belonging to this class, say Y. @@ -19,7 +19,8 @@ if NET_VENDOR_SGI config SGI_IOC3_ETH bool "SGI IOC3 Ethernet" - depends on PCI && SGI_IP27 + depends on PCI && SGI_MFD_IOC3 + select CRC16 select CRC32 select MII ---help--- diff --git a/drivers/net/ethernet/sgi/ioc3-eth.c b/drivers/net/ethernet/sgi/ioc3-eth.c index 4ebb58b8572e..fa1c16f96065 100644 --- a/drivers/net/ethernet/sgi/ioc3-eth.c +++ b/drivers/net/ethernet/sgi/ioc3-eth.c @@ -14,7 +14,6 @@ * o Use prefetching for large packets. What is a good lower limit for * prefetching? * o Use hardware checksums. - * o Convert to using a IOC3 meta driver. * o Which PHYs might possibly be attached to the IOC3 in real live, * which workarounds are required for them? Do we ever have Lucent's? * o For the 2.5 branch kill the mii-tool ioctls. @@ -28,7 +27,8 @@ #include <linux/mm.h> #include <linux/errno.h> #include <linux/module.h> -#include <linux/pci.h> +#include <linux/init.h> +#include <linux/crc16.h> #include <linux/crc32.h> #include <linux/mii.h> #include <linux/in.h> @@ -37,28 +37,22 @@ #include <linux/tcp.h> #include <linux/udp.h> #include <linux/gfp.h> - -#ifdef CONFIG_SERIAL_8250 -#include <linux/serial_core.h> -#include <linux/serial_8250.h> -#include <linux/serial_reg.h> -#endif - #include <linux/netdevice.h> #include <linux/etherdevice.h> #include <linux/ethtool.h> #include <linux/skbuff.h> #include <linux/dma-mapping.h> +#include <linux/platform_device.h> +#include <linux/nvmem-consumer.h> #include <net/ip.h> -#include <asm/byteorder.h> -#include <asm/pgtable.h> -#include <linux/uaccess.h> -#include <asm/sn/types.h> #include <asm/sn/ioc3.h> #include <asm/pci/bridge.h> +#define CRC16_INIT 0 +#define CRC16_VALID 0xb001 + /* Number of RX buffers. This is tunable in the range of 16 <= x < 512. * The value must be a power of two. */ @@ -85,7 +79,6 @@ /* Private per NIC data of the driver. */ struct ioc3_private { struct ioc3_ethregs *regs; - struct ioc3 *all_regs; struct device *dma_dev; u32 *ssram; unsigned long *rxr; /* pointer to receiver ring */ @@ -104,9 +97,6 @@ struct ioc3_private { spinlock_t ioc3_lock; struct mii_if_info mii; - struct net_device *dev; - struct pci_dev *pdev; - /* Members used by autonegotiation */ struct timer_list ioc3_timer; }; @@ -123,10 +113,8 @@ static int ioc3_alloc_rx_bufs(struct net_device *dev); static void ioc3_free_rx_bufs(struct ioc3_private *ip); static inline void ioc3_clean_tx_ring(struct ioc3_private *ip); -static const char ioc3_str[] = "IOC3 Ethernet"; static const struct ethtool_ops ioc3_ethtool_ops; - static inline unsigned long aligned_rx_skb_addr(unsigned long addr) { return (~addr + 1) & (IOC3_DMA_XFER_LEN - 1UL); @@ -179,225 +167,61 @@ static inline unsigned long ioc3_map(dma_addr_t addr, unsigned long attr) #define ERBAR_VAL 0 #endif -#define IOC3_SIZE 0x100000 - -static inline u32 mcr_pack(u32 pulse, u32 sample) -{ - return (pulse << 10) | (sample << 2); -} - -static int nic_wait(u32 __iomem *mcr) -{ - u32 m; - - do { - m = readl(mcr); - } while (!(m & 2)); - - return m & 1; -} - -static int nic_reset(u32 __iomem *mcr) -{ - int presence; - - writel(mcr_pack(500, 65), mcr); - presence = nic_wait(mcr); - - writel(mcr_pack(0, 500), mcr); - nic_wait(mcr); - - return presence; -} - -static inline int nic_read_bit(u32 __iomem *mcr) -{ - int result; - - writel(mcr_pack(6, 13), mcr); - result = nic_wait(mcr); - writel(mcr_pack(0, 100), mcr); - nic_wait(mcr); - - return result; -} - -static inline void nic_write_bit(u32 __iomem *mcr, int bit) -{ - if (bit) - writel(mcr_pack(6, 110), mcr); - else - writel(mcr_pack(80, 30), mcr); - - nic_wait(mcr); -} - -/* Read a byte from an iButton device - */ -static u32 nic_read_byte(u32 __iomem *mcr) -{ - u32 result = 0; - int i; - - for (i = 0; i < 8; i++) - result = (result >> 1) | (nic_read_bit(mcr) << 7); - - return result; -} - -/* Write a byte to an iButton device - */ -static void nic_write_byte(u32 __iomem *mcr, int byte) +static int ioc3eth_nvmem_match(struct device *dev, const void *data) { - int i, bit; - - for (i = 8; i; i--) { - bit = byte & 1; - byte >>= 1; - - nic_write_bit(mcr, bit); - } -} + const char *name = dev_name(dev); + const char *prefix = data; + int prefix_len; -static u64 nic_find(u32 __iomem *mcr, int *last) -{ - int a, b, index, disc; - u64 address = 0; - - nic_reset(mcr); - /* Search ROM. */ - nic_write_byte(mcr, 0xf0); - - /* Algorithm from ``Book of iButton Standards''. */ - for (index = 0, disc = 0; index < 64; index++) { - a = nic_read_bit(mcr); - b = nic_read_bit(mcr); - - if (a && b) { - pr_warn("NIC search failed (not fatal).\n"); - *last = 0; - return 0; - } + prefix_len = strlen(prefix); + if (strlen(name) < (prefix_len + 3)) + return 0; - if (!a && !b) { - if (index == *last) { - address |= 1UL << index; - } else if (index > *last) { - address &= ~(1UL << index); - disc = index; - } else if ((address & (1UL << index)) == 0) { - disc = index; - } - nic_write_bit(mcr, address & (1UL << index)); - continue; - } else { - if (a) - address |= 1UL << index; - else - address &= ~(1UL << index); - nic_write_bit(mcr, a); - continue; - } - } - - *last = disc; - - return address; -} - -static int nic_init(u32 __iomem *mcr) -{ - const char *unknown = "unknown"; - const char *type = unknown; - u8 crc; - u8 serial[6]; - int save = 0, i; - - while (1) { - u64 reg; - - reg = nic_find(mcr, &save); - - switch (reg & 0xff) { - case 0x91: - type = "DS1981U"; - break; - default: - if (save == 0) { - /* Let the caller try again. */ - return -1; - } - continue; - } + if (memcmp(prefix, name, prefix_len) != 0) + return 0; - nic_reset(mcr); - - /* Match ROM. */ - nic_write_byte(mcr, 0x55); - for (i = 0; i < 8; i++) - nic_write_byte(mcr, (reg >> (i << 3)) & 0xff); - - reg >>= 8; /* Shift out type. */ - for (i = 0; i < 6; i++) { - serial[i] = reg & 0xff; - reg >>= 8; - } - crc = reg & 0xff; - break; - } - - pr_info("Found %s NIC", type); - if (type != unknown) - pr_cont(" registration number %pM, CRC %02x", serial, crc); - pr_cont(".\n"); + /* found nvmem device which is attached to our ioc3 + * now check for one wire family code 09, 89 and 91 + */ + if (memcmp(name + prefix_len, "09-", 3) == 0) + return 1; + if (memcmp(name + prefix_len, "89-", 3) == 0) + return 1; + if (memcmp(name + prefix_len, "91-", 3) == 0) + return 1; return 0; } -/* Read the NIC (Number-In-a-Can) device used to store the MAC address on - * SN0 / SN00 nodeboards and PCI cards. - */ -static void ioc3_get_eaddr_nic(struct ioc3_private *ip) +static int ioc3eth_get_mac_addr(struct resource *res, u8 mac_addr[6]) { - u32 __iomem *mcr = &ip->all_regs->mcr; - int tries = 2; /* There may be some problem with the battery? */ - u8 nic[14]; + struct nvmem_device *nvmem; + char prefix[24]; + u8 prom[16]; + int ret; int i; - writel(1 << 21, &ip->all_regs->gpcr_s); - - while (tries--) { - if (!nic_init(mcr)) - break; - udelay(500); - } - - if (tries < 0) { - pr_err("Failed to read MAC address\n"); - return; - } + snprintf(prefix, sizeof(prefix), "ioc3-%012llx-", + res->start & ~0xffff); - /* Read Memory. */ - nic_write_byte(mcr, 0xf0); - nic_write_byte(mcr, 0x00); - nic_write_byte(mcr, 0x00); + nvmem = nvmem_device_find(prefix, ioc3eth_nvmem_match); + if (IS_ERR(nvmem)) + return PTR_ERR(nvmem); - for (i = 13; i >= 0; i--) - nic[i] = nic_read_byte(mcr); + ret = nvmem_device_read(nvmem, 0, 16, prom); + nvmem_device_put(nvmem); + if (ret < 0) + return ret; - for (i = 2; i < 8; i++) - ip->dev->dev_addr[i - 2] = nic[i]; -} + /* check, if content is valid */ + if (prom[0] != 0x0a || + crc16(CRC16_INIT, prom, 13) != CRC16_VALID) + return -EINVAL; -/* Ok, this is hosed by design. It's necessary to know what machine the - * NIC is in in order to know how to read the NIC address. We also have - * to know if it's a PCI card or a NIC in on the node board ... - */ -static void ioc3_get_eaddr(struct ioc3_private *ip) -{ - ioc3_get_eaddr_nic(ip); + for (i = 0; i < 6; i++) + mac_addr[i] = prom[10 - i]; - pr_info("Ethernet address is %pM.\n", ip->dev->dev_addr); + return 0; } static void __ioc3_set_mac_address(struct net_device *dev) @@ -770,7 +594,7 @@ static int ioc3_mii_init(struct ioc3_private *ip) u16 word; for (i = 0; i < 32; i++) { - word = ioc3_mdio_read(ip->dev, i, MII_PHYSID1); + word = ioc3_mdio_read(ip->mii.dev, i, MII_PHYSID1); if (word != 0xffff && word != 0x0000) { found = 1; @@ -975,12 +799,6 @@ static int ioc3_open(struct net_device *dev) { struct ioc3_private *ip = netdev_priv(dev); - if (request_irq(dev->irq, ioc3_interrupt, IRQF_SHARED, ioc3_str, dev)) { - netdev_err(dev, "Can't get irq %d\n", dev->irq); - - return -EAGAIN; - } - ip->ehar_h = 0; ip->ehar_l = 0; @@ -1013,159 +831,6 @@ static int ioc3_close(struct net_device *dev) return 0; } -/* MENET cards have four IOC3 chips, which are attached to two sets of - * PCI slot resources each: the primary connections are on slots - * 0..3 and the secondaries are on 4..7 - * - * All four ethernets are brought out to connectors; six serial ports - * (a pair from each of the first three IOC3s) are brought out to - * MiniDINs; all other subdevices are left swinging in the wind, leave - * them disabled. - */ - -static int ioc3_adjacent_is_ioc3(struct pci_dev *pdev, int slot) -{ - struct pci_dev *dev = pci_get_slot(pdev->bus, PCI_DEVFN(slot, 0)); - int ret = 0; - - if (dev) { - if (dev->vendor == PCI_VENDOR_ID_SGI && - dev->device == PCI_DEVICE_ID_SGI_IOC3) - ret = 1; - pci_dev_put(dev); - } - - return ret; -} - -static int ioc3_is_menet(struct pci_dev *pdev) -{ - return !pdev->bus->parent && - ioc3_adjacent_is_ioc3(pdev, 0) && - ioc3_adjacent_is_ioc3(pdev, 1) && - ioc3_adjacent_is_ioc3(pdev, 2); -} - -#ifdef CONFIG_SERIAL_8250 -/* Note about serial ports and consoles: - * For console output, everyone uses the IOC3 UARTA (offset 0x178) - * connected to the master node (look in ip27_setup_console() and - * ip27prom_console_write()). - * - * For serial (/dev/ttyS0 etc), we can not have hardcoded serial port - * addresses on a partitioned machine. Since we currently use the ioc3 - * serial ports, we use dynamic serial port discovery that |