Message ID | 20190409154610.6735-1-tbogendoerfer@suse.de (mailing list archive) |
---|---|
Headers | show |
Series | Use MFD framework for SGI IOC3 drivers | expand |
On Tue, Apr 09, 2019 at 05:46:06PM +0200, Thomas Bogendoerfer wrote: > 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. > > Signed-off-by: Thomas Bogendoerfer <tbogendoerfer@suse.de> > --- > arch/mips/include/asm/sn/ioc3.h | 345 +++--- > arch/mips/sgi-ip27/ip27-timer.c | 20 - > drivers/mfd/Kconfig | 13 + > drivers/mfd/Makefile | 1 + > drivers/mfd/ioc3.c | 802 ++++++++++++++ > drivers/net/ethernet/sgi/Kconfig | 4 +- > drivers/net/ethernet/sgi/ioc3-eth.c | 1867 ++++++++++++--------------------- > drivers/tty/serial/8250/8250_ioc3.c | 98 ++ > drivers/tty/serial/8250/Kconfig | 11 + > drivers/tty/serial/8250/Makefile | 1 + > include/linux/platform_data/ioc3eth.h | 15 + > 11 files changed, 1762 insertions(+), 1415 deletions(-) > create mode 100644 drivers/mfd/ioc3.c > create mode 100644 drivers/tty/serial/8250/8250_ioc3.c > create mode 100644 include/linux/platform_data/ioc3eth.h Serial portion: Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
On Tue, 09 Apr 2019, Thomas Bogendoerfer wrote: > 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. > > Signed-off-by: Thomas Bogendoerfer <tbogendoerfer@suse.de> > --- > arch/mips/include/asm/sn/ioc3.h | 345 +++--- > arch/mips/sgi-ip27/ip27-timer.c | 20 - > drivers/mfd/Kconfig | 13 + > drivers/mfd/Makefile | 1 + > drivers/mfd/ioc3.c | 802 ++++++++++++++ > drivers/net/ethernet/sgi/Kconfig | 4 +- > drivers/net/ethernet/sgi/ioc3-eth.c | 1867 ++++++++++++--------------------- > drivers/tty/serial/8250/8250_ioc3.c | 98 ++ > drivers/tty/serial/8250/Kconfig | 11 + > drivers/tty/serial/8250/Makefile | 1 + > include/linux/platform_data/ioc3eth.h | 15 + > 11 files changed, 1762 insertions(+), 1415 deletions(-) > create mode 100644 drivers/mfd/ioc3.c > create mode 100644 drivers/tty/serial/8250/8250_ioc3.c > create mode 100644 include/linux/platform_data/ioc3eth.h [...] > +config SGI_MFD_IOC3 > + tristate "SGI IOC3 core driver" > + depends on PCI && MIPS > + 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 b4569ed7f3f3..07255e499129 100644 > --- a/drivers/mfd/Makefile > +++ b/drivers/mfd/Makefile > @@ -246,4 +246,5 @@ obj-$(CONFIG_MFD_MXS_LRADC) += mxs-lradc.o > obj-$(CONFIG_MFD_SC27XX_PMIC) += sprd-sc27xx-spi.o > obj-$(CONFIG_RAVE_SP_CORE) += rave-sp.o > obj-$(CONFIG_MFD_ROHM_BD718XX) += rohm-bd718x7.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..ad715805b16e > --- /dev/null > +++ b/drivers/mfd/ioc3.c > @@ -0,0 +1,802 @@ > +// 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/errno.h> > +#include <linux/module.h> > +#include <linux/pci.h> > +#include <linux/interrupt.h> > +#include <linux/delay.h> > +#include <linux/mfd/core.h> > +#include <linux/platform_device.h> > +#include <linux/platform_data/ioc3eth.h> These should be in alphabetical order. > +#include <asm/sn/ioc3.h> > +#include <asm/pci/bridge.h> > + > +#define IOC3_ETH BIT(0) > +#define IOC3_SER BIT(1) > +#define IOC3_PAR BIT(2) > +#define IOC3_KBD BIT(3) > +#define IOC3_M48T35 BIT(4) > + > +static int ioc3_serial_id = 1; > +static int ioc3_eth_id = 1; > +static int ioc3_kbd_id = 1; > +static struct mfd_cell ioc3_mfd_cells[5]; > + > +struct ioc3_board_info { > + const char *name; > + int irq_offset; > + int funcs; > +}; > + > +struct ioc3_priv_data { > + struct ioc3_board_info *info; > + struct irq_domain *domain; > + struct ioc3 __iomem *regs; > + struct pci_dev *pdev; > + char nic_part[32]; > + char nic_mac[6]; > + int irq_io; > +}; > + > +#define MCR_PACK(pulse, sample) (((pulse) << 10) | ((sample) << 2)) > + > +static int ioc3_nic_wait(u32 __iomem *mcr) > +{ > + u32 mcr_val; > + > + do { > + mcr_val = readl(mcr); > + } while (!(mcr_val & 2)); Why 2? Is bit 2 always set on a successful read? Either way, you should provide a comment to improve readability. > + return (mcr_val & 1); Reads just a bit at a time? Again, a comment please. > +} > + > +static int ioc3_nic_reset(u32 __iomem *mcr) > +{ > + int presence; > + unsigned long flags; > + > + local_irq_save(flags); > + writel(MCR_PACK(520, 65), mcr); What do these magic values mean? Best to define them. > + presence = ioc3_nic_wait(mcr); > + local_irq_restore(flags); > + > + udelay(500); What are you waiting for? Why 500us? > + return presence; > +} > + > +static int ioc3_nic_read_bit(u32 __iomem *mcr) > +{ > + int result; > + unsigned long flags; > + > + local_irq_save(flags); > + writel(MCR_PACK(6, 13), mcr); Why 6 and 13? Define all magic values please. > + result = ioc3_nic_wait(mcr); > + local_irq_restore(flags); > + > + udelay(100); Why 100? > + return result; > +} > + > +static u32 ioc3_nic_read_byte(u32 __iomem *mcr) > +{ > + u32 result = 0; > + int i; > + > + for (i = 0; i < 8; i++) > + result = ((result >> 1) | (ioc3_nic_read_bit(mcr) << 7)); > + > + return result; > +} > + > +static void ioc3_nic_write_bit(u32 __iomem *mcr, int bit) > +{ > + if (bit) > + writel(MCR_PACK(6, 110), mcr); > + else > + writel(MCR_PACK(80, 30), mcr); > + > + ioc3_nic_wait(mcr); > +} > + > +static void ioc3_nic_write_byte(u32 __iomem *mcr, int byte) > +{ > + int i; > + > + for (i = 0; i < 8; i++) { > + ioc3_nic_write_bit(mcr, byte & 1); > + byte >>= 1; > + } > +} > + > +static u64 ioc3_nic_find(u32 __iomem *mcr, int *last, u64 addr) > +{ > + int a, b, index, disc; > + > + ioc3_nic_reset(mcr); > + > + /* Search ROM. */ > + ioc3_nic_write_byte(mcr, 0xf0); What does 0xf0 mean? Please define it and all magic numbers that follow. > + /* Algorithm from ``Book of iButton Standards''. */ That's great, but what is it actually doing. Please provide suitable comments such that the reader doesn't have to painstakingly work it all out. > + for (index = 0, disc = 0; index < 64; index++) { > + a = ioc3_nic_read_bit(mcr); > + b = ioc3_nic_read_bit(mcr); > + > + if (unlikely(a && b)) { > + pr_warn("ioc3: NIC search failed.\n"); > + *last = 0; > + return 0; > + } > + > + if (!a && !b) { > + if (index == *last) > + addr |= 1UL << index; > + else if (index > *last) { > + addr &= ~(1UL << index); > + disc = index; > + } else if ((addr & (1UL << index)) == 0) > + disc = index; > + ioc3_nic_write_bit(mcr, (addr >> index) & 1); > + continue; > + } else { > + if (a) > + addr |= (1UL << index); > + else > + addr &= ~(1UL << index); > + ioc3_nic_write_bit(mcr, a); > + continue; > + } > + } > + *last = disc; > + return addr; > +} > + > +static void ioc3_nic_addr(u32 __iomem *mcr, u64 addr) > +{ > + int index; > + > + ioc3_nic_reset(mcr); > + ioc3_nic_write_byte(mcr, 0xf0); > + > + for (index = 0; index < 64; index++) { > + ioc3_nic_read_bit(mcr); > + ioc3_nic_read_bit(mcr); > + ioc3_nic_write_bit(mcr, ((addr >> index) & 1)); > + } What is this function doing? Setting the NIC address? Why are the 2 sequential reads required before each bit write? > +} > + > +static void crc16_byte(u32 *crc, u8 db) > +{ > + int i; > + > + for (i = 0; i < 8; i++) { > + *crc <<= 1; > + if ((db ^ (*crc >> 16)) & 1) > + *crc ^= 0x8005; > + db >>= 1; > + } > + *crc &= 0xffff; > +} > + > +static u32 crc16_area(u8 *dbs, int size, u32 crc) > +{ > + while (size--) > + crc16_byte(&crc, *(dbs++)); > + > + return crc; > +} > + > +static void crc8_byte(u32 *crc, u8 db) > +{ > + int i, f; > + > + for (i = 0; i < 8; i++) { > + f = ((*crc ^ db) & 1); > + *crc >>= 1; > + db >>= 1; > + if (f) > + *crc ^= 0x8c; > + } > + *crc &= 0xff; > +} > + > +static u32 crc8_addr(u64 addr) > +{ > + u32 crc = 0; > + int i; > + > + for (i = 0; i < 64; i += 8) > + crc8_byte(&crc, addr >> i); > + return crc; > +} Not looked into these in any detail, but are you not able to use the CRC functions already provided by the kernel? > +static void ioc3_read_redir_page(u32 __iomem *mcr, u64 addr, int page, What is a redir page? > + u8 *redir, u8 *data) > +{ > + int loops = 16, i; > + > + while (redir[page] != 0xff) { > + page = (redir[page] ^ 0xff); > + loops--; > + if (unlikely(loops < 0)) { > + pr_err("ioc3: NIC circular redirection\n"); > + return; > + } > + } > + > + loops = 3; > + while (loops > 0) { > + ioc3_nic_addr(mcr, addr); > + ioc3_nic_write_byte(mcr, 0xf0); > + ioc3_nic_write_byte(mcr, (page << 5) & 0xe0); > + ioc3_nic_write_byte(mcr, (page >> 3) & 0x1f); > + > + for (i = 0; i < 0x20; i++) > + data[i] = ioc3_nic_read_byte(mcr); > + > + if (crc16_area(data, 0x20, 0) == 0x800d) > + return; > + > + loops--; > + } > + > + pr_err("ioc3: CRC error in data page\n"); > + for (i = 0; i < 0x20; i++) > + data[i] = 0x00; Comments required throughout. > +} > + > +static void ioc3_read_redir_map(u32 __iomem *mcr, u64 addr, u8 *redir) > +{ > + int i, j, crc_ok, loops = 3; > + u32 crc; > + > + while (loops > 0) { > + crc_ok = 1; > + ioc3_nic_addr(mcr, addr); > + ioc3_nic_write_byte(mcr, 0xaa); > + ioc3_nic_write_byte(mcr, 0x00); > + ioc3_nic_write_byte(mcr, 0x01); > + > + for (i = 0; i < 64; i += 8) { > + for (j = 0; j < 8; j++) > + redir[i + j] = ioc3_nic_read_byte(mcr); > + > + crc = crc16_area(redir + i, 8, i == 0 ? 0x8707 : 0); > + > + crc16_byte(&crc, ioc3_nic_read_byte(mcr)); > + crc16_byte(&crc, ioc3_nic_read_byte(mcr)); > + > + if (crc != 0x800d) > + crc_ok = 0; > + } > + if (crc_ok) > + return; > + loops--; > + } > + pr_err("ioc3: CRC error in redirection page\n"); > + for (i = 0; i < 64; i++) > + redir[i] = 0xff; As above. > +} > + > +static void ioc3_read_nic(struct ioc3_priv_data *ipd, u32 __iomem *mcr, > + u64 addr) > +{ > + u8 redir[64]; > + u8 data[64], part[32]; > + int i, j; > + > + /* Read redirections */ > + ioc3_read_redir_map(mcr, addr, redir); > + > + /* Read data pages */ > + ioc3_read_redir_page(mcr, addr, 0, redir, data); > + ioc3_read_redir_page(mcr, addr, 1, redir, (data + 32)); > + > + /* Assemble the part # */ > + j = 0; > + for (i = 0; i < 19; i++) > + if (data[i + 11] != ' ') > + part[j++] = data[i + 11]; > + > + for (i = 0; i < 6; i++) > + if (data[i + 32] != ' ') > + part[j++] = data[i + 32]; > + > + part[j] = 0; > + > + /* Skip Octane (IP30) power supplies */ > + if (!(strncmp(part, "060-0035-", 9)) || > + !(strncmp(part, "060-0038-", 9)) || > + !(strncmp(part, "060-0028-", 9))) > + return; > + > + strlcpy(ipd->nic_part, part, sizeof(ipd->nic_part)); > +} > + > +static void ioc3_read_mac(struct ioc3_priv_data *ipd, u64 addr) > +{ > + int i, loops = 3; > + u8 data[13]; > + u32 __iomem *mcr = &ipd->regs->mcr; > + > + while (loops > 0) { > + ioc3_nic_addr(mcr, addr); > + ioc3_nic_write_byte(mcr, 0xf0); > + ioc3_nic_write_byte(mcr, 0x00); > + ioc3_nic_write_byte(mcr, 0x00); > + ioc3_nic_read_byte(mcr); > + > + for (i = 0; i < 13; i++) > + data[i] = ioc3_nic_read_byte(mcr); > + > + if (crc16_area(data, 13, 0) == 0x800d) { > + for (i = 10; i > 4; i--) > + ipd->nic_mac[10 - i] = data[i]; > + return; > + } > + loops--; > + } > + > + pr_err("ioc3: CRC error in MAC address\n"); > + for (i = 0; i < 6; i++) > + ipd->nic_mac[i] = 0x00; > +} > + > +static void ioc3_probe_nic(struct ioc3_priv_data *ipd, u32 __iomem *mcr) > +{ > + int save = 0, loops = 3; > + u64 first, addr; > + > + while (loops > 0) { > + ipd->nic_part[0] = 0; > + first = ioc3_nic_find(mcr, &save, 0); > + addr = first; > + > + if (unlikely(!first)) > + return; > + > + while (1) { > + if (crc8_addr(addr)) > + break; > + > + switch (addr & 0xff) { > + case 0x0b: > + ioc3_read_nic(ipd, mcr, addr); > + break; > + case 0x09: > + case 0x89: > + case 0x91: > + ioc3_read_mac(ipd, addr); > + break; > + } > + > + addr = ioc3_nic_find(mcr, &save, addr); > + if (addr == first) > + return; > + } > + loops--; > + } > + pr_err("ioc3: CRC error in NIC address\n"); > +} This all looks like networking code. If this is the case, it should be moved to drivers/networking or similar. > +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, > +}; > + > +#define IOC3_LVL_MASK (BIT(0) | BIT(2) | BIT(6) | BIT(9) | BIT(11) | BIT(15)) You need to define what each of these bits are. BIT(2) doesn't tell the reader anything, meaning the code is unreadable. > +static int ioc3_irq_domain_map(struct irq_domain *d, unsigned int irq, > + irq_hw_number_t hwirq) > +{ > + if (BIT(hwirq) & IOC3_LVL_MASK) Comment needed. > + 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; > +} > + > + Drop the superfluous '\n'. > +static const struct irq_domain_ops ioc3_irq_domain_ops = { > + .map = ioc3_irq_domain_map, > +}; > + > +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; > + unsigned int irq = 0; Why does these need to be pre-assigned? > + u32 pending; > + > + pending = readl(®s->sio_ir); > + pending &= readl(®s->sio_ies); Comment required. > + if (pending) > + irq = irq_find_mapping(domain, __ffs(pending)); > + else if (!ipd->info->irq_offset && > + (readl(®s->eth.eisr) & readl(®s->eth.eier))) Comment required. > + irq = irq_find_mapping(domain, 23); > + > + if (irq) > + generic_handle_irq(irq); > + else > + spurious_interrupt(); > +} > + > +static struct resource ioc3_uarta_resources[] = { > + DEFINE_RES_MEM(offsetof(struct ioc3, sregs.uarta), You are the first user of offsetof() in MFD. Could you tell me why it's required please? > + sizeof_field(struct ioc3, sregs.uarta)), > + DEFINE_RES_IRQ(6) Please define all magic numbers. > +}; > + > +static struct resource ioc3_uartb_resources[] = { > + DEFINE_RES_MEM(offsetof(struct ioc3, sregs.uartb), > + sizeof_field(struct ioc3, sregs.uartb)), > + DEFINE_RES_IRQ(15) > +}; > + > +static struct resource ioc3_kbd_resources[] = { > + DEFINE_RES_MEM(offsetof(struct ioc3, serio), > + sizeof_field(struct ioc3, serio)), > + DEFINE_RES_IRQ(22) > +}; > + > +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 ioc3eth_platform_data ioc3_eth_platform_data; I doubt you'll need this in here. The MAC address info will need to be moved out to a networking driver of some sort. > +#ifdef CONFIG_SGI_IP27 #ifery in C files is highly discouraged. Please remove them. > +static struct resource ioc3_rtc_resources[] = { > + DEFINE_RES_MEM(IOC3_BYTEBUS_DEV0, 32768) Define all magic numbers please. > +}; > + > +static struct ioc3_board_info ip27_baseio_info = { > + .name = "IP27 BaseIO", > + .funcs = IOC3_ETH | IOC3_SER | IOC3_M48T35, > + .irq_offset = 2 > +}; > + > +static struct ioc3_board_info ip27_baseio6g_info = { > + .name = "IP27 BaseIO6G", > + .funcs = IOC3_ETH | IOC3_SER | IOC3_KBD | IOC3_M48T35, > + .irq_offset = 2 > +}; > + > +static struct ioc3_board_info ip27_mio_info = { > + .name = "MIO", > + .funcs = IOC3_SER | IOC3_PAR | IOC3_KBD, > + .irq_offset = 0 > +}; > + > +static struct ioc3_board_info ip29_baseio_info = { > + .name = "IP29 System Board", > + .funcs = IOC3_ETH | IOC3_SER | IOC3_PAR | IOC3_KBD | IOC3_M48T35, > + .irq_offset = 1 > +}; > + > +#endif /* CONFIG_SGI_IP27 */ > + > +static struct ioc3_board_info ioc3_menet_info = { > + .name = "MENET", > + .funcs = IOC3_ETH | IOC3_SER, > + .irq_offset = 4 > +}; > + > +static struct ioc3_board_info ioc3_cad_duo_info = { > + .name = "CAD DUO", > + .funcs = IOC3_ETH | IOC3_KBD, > + .irq_offset = 0 > +}; Please drop all of these and statically create the MFD cells like almost all other MFD drivers do. > +#define IOC3_BOARD(_partno, _info) { .info = _info, .match = _partno } > + > +static struct { > + struct ioc3_board_info *info; > + const char *match; > +} ioc3_boards[] = { > +#ifdef CONFIG_SGI_IP27 > + IOC3_BOARD("030-0734-", &ip27_baseio6g_info), > + IOC3_BOARD("030-1023-", &ip27_baseio_info), > + IOC3_BOARD("030-1124-", &ip27_baseio_info), > + IOC3_BOARD("030-1025-", &ip29_baseio_info), > + IOC3_BOARD("030-1244-", &ip29_baseio_info), > + IOC3_BOARD("030-1389-", &ip29_baseio_info), > + IOC3_BOARD("030-0880-", &ip27_mio_info), > +#endif > + IOC3_BOARD("030-0873-", &ioc3_menet_info), > + IOC3_BOARD("030-1155-", &ioc3_cad_duo_info), > +}; > + > +static int ioc3_identify(struct ioc3_priv_data *idp) > +{ > + int i; > + > + for (i = 0; i < ARRAY_SIZE(ioc3_boards); i++) > + if (!strncmp(idp->nic_part, ioc3_boards[i].match, > + strlen(ioc3_boards[i].match))) { > + idp->info = ioc3_boards[i].info; > + return 0; > + } > + > + return -1; Please return a proper Linux error code. > +} > + > +static void ioc3_create_devices(struct ioc3_priv_data *ipd) > +{ > + struct mfd_cell *cell; > + > + memset(ioc3_mfd_cells, 0, sizeof(ioc3_mfd_cells)); > + cell = ioc3_mfd_cells; > + > + if (ipd->info->funcs & IOC3_ETH) { > + memcpy(ioc3_eth_platform_data.mac_addr, ipd->nic_mac, > + sizeof(ioc3_eth_platform_data.mac_addr)); Better to pull the MAC address from within the Ethernet driver. > + cell->name = "ioc3-eth"; > + cell->id = ioc3_eth_id++; > + cell->resources = ioc3_eth_resources; > + cell->num_resources = ARRAY_SIZE(ioc3_eth_resources); > + cell->platform_data = &ioc3_eth_platform_data; > + cell->pdata_size = sizeof(ioc3_eth_platform_data); Please define all of this in a static struct, external to this function. > + if (ipd->info->irq_offset) { > + /* > + * Ethernet interrupt is on an extra interrupt > + * not inside the irq domain, so we need an > + * extra mfd_add_devices without the domain > + * argument > + */ > + ioc3_eth_resources[2].start = ipd->pdev->irq; > + ioc3_eth_resources[2].end = ipd->pdev->irq; Using '2' is fragile. In this case, seeing as you're using the parent's IRQ, you can obtain the IRQ using the usual platform_*() handlers, using pdev->parent. > + mfd_add_devices(&ipd->pdev->dev, -1, cell, 1, Don't use -1, use the defines instead. Instead of 1, use ARRAY_SIZE() once the cells are defined statically. > + &ipd->pdev->resource[0], 0, NULL); > + memset(cell, 0, sizeof(*cell)); > + } else { > + /* fake hwirq in domain */ What does this mean? > + ioc3_eth_resources[2].start = 23; > + ioc3_eth_resources[2].end = 23; > + cell++; > + } > + } > + if (ipd->info->funcs & IOC3_SER) { > + writel(GPCR_UARTA_MODESEL | GPCR_UARTB_MODESEL, > + &ipd->regs->gpcr_s); > + writel(0, &ipd->regs->gppr[6]); > + writel(0, &ipd->regs->gppr[7]); > + udelay(100); > + 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); No idea what any of this does. It looks like it belongs in the serial driver (and needs comments). > + cell->name = "ioc3-serial8250"; > + cell->id = ioc3_serial_id++; > + cell->resources = ioc3_uarta_resources; > + cell->num_resources = ARRAY_SIZE(ioc3_uarta_resources); > + cell++; > + cell->name = "ioc3-serial8250"; > + cell->id = ioc3_serial_id++; > + cell->resources = ioc3_uartb_resources; > + cell->num_resources = ARRAY_SIZE(ioc3_uartb_resources); > + cell++; Please define this statically. > + } > + if (ipd->info->funcs & IOC3_KBD) { > + cell->name = "ioc3-kbd", > + cell->id = ioc3_kbd_id++; > + cell->resources = ioc3_kbd_resources, > + cell->num_resources = ARRAY_SIZE(ioc3_kbd_resources), > + cell++; As above. > + } > +#if defined(CONFIG_SGI_IP27) What is this? Can't you obtain this dynamically by probing the H/W? > + if (ipd->info->funcs & IOC3_M48T35) { > + cell->name = "rtc-m48t35"; > + cell->id = -1; > + cell->resources = ioc3_rtc_resources; > + cell->num_resources = ARRAY_SIZE(ioc3_rtc_resources); > + cell++; Static please. > + } > +#endif > + mfd_add_devices(&ipd->pdev->dev, -1, ioc3_mfd_cells, Use the defines. > + cell - ioc3_mfd_cells, &ipd->pdev->resource[0], ? > + 0, ipd->domain); > +} > + > +static int ioc3_mfd_probe(struct pci_dev *pdev, > + const struct pci_device_id *pci_id) > +{ > + struct ioc3_priv_data *ipd; > + int err, ret = -ENODEV, io_irqno; > + struct ioc3 __iomem *regs; > + struct irq_domain *domain; > + struct fwnode_handle *fn; > + > + err = pci_enable_device(pdev); > + if (err) > + return err; > + > + pci_write_config_byte(pdev, PCI_LATENCY_TIMER, 64); > + pci_set_master(pdev); > + > + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); > + if (ret) { > + dev_warn(&pdev->dev, "Warning: couldn_t set 64-bit DMA mask\n"); > + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); > + if (ret) { > + dev_err(&pdev->dev, "Can't set DMA mask, aborting\n"); > + return ret; > + } > + } > + > + /* Set up per-IOC3 data */ > + ipd = kzalloc(sizeof(struct ioc3_priv_data), GFP_KERNEL); Does PCI not provide managed (devm_*() like) helpers? > + if (!ipd) { > + ret = -ENOMEM; > + goto out_disable_device; > + } > + ipd->pdev = pdev; '\n' > + /* > + * Map all IOC3 registers. These are shared between subdevices > + * so the main IOC3 module manages them. > + */ > + regs = pci_ioremap_bar(pdev, 0); > + if (!regs) { > + pr_warn("ioc3: Unable to remap PCI BAR for %s.\n", > + pci_name(pdev)); > + goto out_free_ipd; > + } > + ipd->regs = regs; > + > + /* Track PCI-device specific data */ > + pci_set_drvdata(pdev, ipd); Do you don't need 'ipd->pdev = pdev' then. > + writel(GPCR_MLAN_EN, &ipd->regs->gpcr_s); > + ioc3_probe_nic(ipd, ®s->mcr); This should probably be moved to the networking driver. > +#ifdef CONFIG_SGI_IP27 No #ifery in MFD c-files please. > + /* BaseIO NIC is attached to bridge */ > + if (!ipd->nic_part[0]) { > + struct bridge_controller *bc = BRIDGE_CONTROLLER(pdev->bus); > + > + ioc3_probe_nic(ipd, &bc->base->b_nic); > + } > +#endif > + > + if (ioc3_identify(ipd)) { > + pr_err("ioc3: part: [%s] unknown card\n", ipd->nic_part); > + goto out_iounmap; > + } > + > + pr_info("ioc3: part: [%s] %s\n", ipd->nic_part, ipd->info->name); > + > + /* Clear IRQs */ A comment! I just fell off my chair! =;-) > + writel(~0, ®s->sio_iec); > + writel(~0, ®s->sio_ir); > + writel(0, ®s->eth.eier); > + writel(~0, ®s->eth.eisr); > + > + if (ipd->info->irq_offset) { What does this really signify? > + struct pci_host_bridge *hbrg = pci_find_host_bridge(pdev->bus); > + > + io_irqno = hbrg->map_irq(pdev, > + PCI_SLOT(pdev->devfn) + ipd->info->irq_offset, 0); > + } else { > + io_irqno = pdev->irq; > + } > + ipd->irq_io = io_irqno; > + > + fn = irq_domain_alloc_named_fwnode("IOC3"); > + if (!fn) > + goto out_iounmap; > + > + domain = irq_domain_create_linear(fn, 24, &ioc3_irq_domain_ops, ipd); > + irq_domain_free_fwnode(fn); > + if (!domain) > + goto out_iounmap; > + ipd->domain = domain; > + > + irq_set_chained_handler_and_data(io_irqno, ioc3_irq_handler, domain); > + > + ioc3_create_devices(ipd); > + > + return 0; > + > +out_iounmap: > + iounmap(ipd->regs); > + > +out_free_ipd: > + kfree(ipd); > + > +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 */ > + irq_domain_remove(ipd->domain); > + free_irq(ipd->irq_io, (void *)ipd); > + iounmap(ipd->regs); > + > + pci_disable_device(pdev); > + > + kfree(ipd); > +} > + > +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");
On Wed, 8 May 2019 11:23:13 +0100 Lee Jones <lee.jones@linaro.org> wrote: > On Tue, 09 Apr 2019, Thomas Bogendoerfer wrote: > > > +static u32 crc8_addr(u64 addr) > > +{ > > + u32 crc = 0; > > + int i; > > + > > + for (i = 0; i < 64; i += 8) > > + crc8_byte(&crc, addr >> i); > > + return crc; > > +} > > Not looked into these in any detail, but are you not able to use the > CRC functions already provided by the kernel? they are using a different polynomial, so I can't use it. > > + } > > + pr_err("ioc3: CRC error in NIC address\n"); > > +} > > This all looks like networking code. If this is the case, it should > be moved to drivers/networking or similar. no it's not. nic stands for number in a can produced by Dallas Semi also known under the name 1-Wire (https://en.wikipedia.org/wiki/1-Wire). SGI used them to provide partnumber, serialnumber and mac addresses. By placing the code to read the NiCs inside ioc3 driver there is no need for locking and adding library code for accessing these informations. > > +static struct resource ioc3_uarta_resources[] = { > > + DEFINE_RES_MEM(offsetof(struct ioc3, sregs.uarta), > > You are the first user of offsetof() in MFD. Could you tell me why > it's required please? to get the offsets of different chip functions out of a struct. > Please drop all of these and statically create the MFD cells like > almost all other MFD drivers do. I started that way and it blew up the driver and create a bigger mess than I wanted to have. What's your concern with my approach ? I could use static mfd_cell arrays, if there would be a init/startup method per cell, which is called before setting up the platform device. That way I could do the dynamic setup for ethernet and serial devices. > > +static void ioc3_create_devices(struct ioc3_priv_data *ipd) > > +{ > > + struct mfd_cell *cell; > > + > > + memset(ioc3_mfd_cells, 0, sizeof(ioc3_mfd_cells)); > > + cell = ioc3_mfd_cells; > > + > > + if (ipd->info->funcs & IOC3_ETH) { > > + memcpy(ioc3_eth_platform_data.mac_addr, ipd->nic_mac, > > + sizeof(ioc3_eth_platform_data.mac_addr)); > > Better to pull the MAC address from within the Ethernet driver. the NiC where the MAC address is provided is connected to the ioc3 chip outside of the ethernet register set. And there is another NiC connected to the same 1-W bus. So moving reading of the MAC address to the ethernet driver duplicates code and adds complexity (locking). Again what's your concern here ? > > + if (ipd->info->funcs & IOC3_SER) { > > + writel(GPCR_UARTA_MODESEL | GPCR_UARTB_MODESEL, > > + &ipd->regs->gpcr_s); > > + writel(0, &ipd->regs->gppr[6]); > > + writel(0, &ipd->regs->gppr[7]); > > + udelay(100); > > + 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); > > No idea what any of this does. > > It looks like it belongs in the serial driver (and needs comments). it configures the IOC3 chip for serial usage. This is not part of the serial register set, so it IMHO belongs in the MFD driver. > > + } > > +#if defined(CONFIG_SGI_IP27) > > What is this? Can't you obtain this dynamically by probing the H/W? that's the machine type and the #ifdef CONFIG_xxx are just for saving space, when compiled for other machines and it's easy to remove. > > + if (ipd->info->irq_offset) { > > What does this really signify? IOC3 ASICs are most of the time connected to a SGI bridge chip. IOC3 can provide two interrupt lines, which are wired to the bridge chip. The first interrupt is assigned via the PCI core, but since IOC3 is not a PCI multi function device the second interrupt must be treated here. And the used interrupt line on the bridge chip differs between boards. Thank you for your review. I'll address all other comments not cited in my mail. Thomas.
On Thu, 09 May 2019, Thomas Bogendoerfer wrote: > On Wed, 8 May 2019 11:23:13 +0100 > Lee Jones <lee.jones@linaro.org> wrote: > > > On Tue, 09 Apr 2019, Thomas Bogendoerfer wrote: > > > > > +static u32 crc8_addr(u64 addr) > > > +{ > > > + u32 crc = 0; > > > + int i; > > > + > > > + for (i = 0; i < 64; i += 8) > > > + crc8_byte(&crc, addr >> i); > > > + return crc; > > > +} > > > > Not looked into these in any detail, but are you not able to use the > > CRC functions already provided by the kernel? > > they are using a different polynomial, so I can't use it. Would it be worth moving support out to somewhere more central so others can use this "polynomial"? > > > + } > > > + pr_err("ioc3: CRC error in NIC address\n"); > > > +} > > > > This all looks like networking code. If this is the case, it should > > be moved to drivers/networking or similar. > > no it's not. nic stands for number in a can produced by Dallas Semi also > known under the name 1-Wire (https://en.wikipedia.org/wiki/1-Wire). > SGI used them to provide partnumber, serialnumber and mac addresses. > By placing the code to read the NiCs inside ioc3 driver there is no need > for locking and adding library code for accessing these informations. Great. So it looks like you should be using this, no? drivers/base/regmap/regmap-w1.c > > > +static struct resource ioc3_uarta_resources[] = { > > > + DEFINE_RES_MEM(offsetof(struct ioc3, sregs.uarta), > > > > You are the first user of offsetof() in MFD. Could you tell me why > > it's required please? > > to get the offsets of different chip functions out of a struct. I can see what it does on a coding level. What are you using it for in practical/real terms? Why wouldn't any other MFD driver require this, but you do? > > Please drop all of these and statically create the MFD cells like > > almost all other MFD drivers do. > > I started that way and it blew up the driver and create a bigger mess > than I wanted to have. What's your concern with my approach ? > > I could use static mfd_cell arrays, if there would be a init/startup > method per cell, which is called before setting up the platform device. > That way I could do the dynamic setup for ethernet and serial devices. You can set platform data later. There are plenty of examples of this in the MFD subsystem. Statically define what you can, and add the dynamic stuff later. > > > +static void ioc3_create_devices(struct ioc3_priv_data *ipd) > > > +{ > > > + struct mfd_cell *cell; > > > + > > > + memset(ioc3_mfd_cells, 0, sizeof(ioc3_mfd_cells)); > > > + cell = ioc3_mfd_cells; > > > + > > > + if (ipd->info->funcs & IOC3_ETH) { > > > + memcpy(ioc3_eth_platform_data.mac_addr, ipd->nic_mac, > > > + sizeof(ioc3_eth_platform_data.mac_addr)); > > > > Better to pull the MAC address from within the Ethernet driver. > > the NiC where the MAC address is provided is connected to the ioc3 > chip outside of the ethernet register set. And there is another > NiC connected to the same 1-W bus. So moving reading of the MAC > address to the ethernet driver duplicates code and adds complexity > (locking). Again what's your concern here ? Does this go away if you use the already provided 1-wire API? > > > + if (ipd->info->funcs & IOC3_SER) { > > > + writel(GPCR_UARTA_MODESEL | GPCR_UARTB_MODESEL, > > > + &ipd->regs->gpcr_s); > > > + writel(0, &ipd->regs->gppr[6]); > > > + writel(0, &ipd->regs->gppr[7]); > > > + udelay(100); > > > + 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); > > > > No idea what any of this does. > > > > It looks like it belongs in the serial driver (and needs comments). > > it configures the IOC3 chip for serial usage. This is not part of > the serial register set, so it IMHO belongs in the MFD driver. So it does serial things, but doesn't belong in the serial driver? Could you please go into a bit more detail as to why you think that? Why is it better here? It's also totally unreadable by the way! > > > + } > > > +#if defined(CONFIG_SGI_IP27) > > > > What is this? Can't you obtain this dynamically by probing the H/W? > > that's the machine type and the #ifdef CONFIG_xxx are just for saving space, > when compiled for other machines and it's easy to remove. Please find other ways to save the space. #ifery can get very messy, very quickly and is almost always avoidable. > > > + if (ipd->info->irq_offset) { > > > > What does this really signify? > > IOC3 ASICs are most of the time connected to a SGI bridge chip. IOC3 can > provide two interrupt lines, which are wired to the bridge chip. The first > interrupt is assigned via the PCI core, but since IOC3 is not a PCI multi > function device the second interrupt must be treated here. And the used > interrupt line on the bridge chip differs between boards. Please provide a MACRO, function or something else which results in more readable code. Whatever you choose to use, please add this text above, it will be helpful for future readers. > Thank you for your review. I'll address all other comments not cited in > my mail. NP
On Tue, 09 Apr 2019, Thomas Bogendoerfer wrote: > > diff --git a/drivers/net/ethernet/sgi/ioc3-eth.c b/drivers/net/ethernet/sgi/ioc3-eth.c > index 358e66b81926..21fe722ebcd8 100644 > --- a/drivers/net/ethernet/sgi/ioc3-eth.c > +++ b/drivers/net/ethernet/sgi/ioc3-eth.c > > [ ... ] > > - err = pci_request_regions(pdev, "ioc3"); Why are you dropping the call to pci_request_regions()? Shouldn't you do something similar in the new mfd driver? When you are use the the BAR 0 resource as mem_base argument to mfd_add_devices() later on, it will be split into child resources for the child devices, but they will not be related to the IORESOURCE_MEM root tree (iomem_resource) anymore. I don't think that is how it is supposed to be done, as it will allow random other drivers to request the exact same memory area. How/where is the memory resources inserted in the root IORESOURCE_MEM resource (iomem_resource)? Or is it allowed to use resources without inserting it into the root tree? > + SET_NETDEV_DEV(dev, &pdev->dev); > + ip = netdev_priv(dev); > + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); > + ip->regs = ioremap(r->start, resource_size(r)); > + r = platform_get_resource(pdev, IORESOURCE_MEM, 1); > + ip->ssram = ioremap(r->start, resource_size(r)); Maybe use devm_platform_ioremap_resource() instead, which handles both platform_get_resource() and ioremap() in one call.. /Esben
On Fri, 10 May 2019 08:14:19 +0100 Lee Jones <lee.jones@linaro.org> wrote: > On Thu, 09 May 2019, Thomas Bogendoerfer wrote: > > > > + } > > > > + pr_err("ioc3: CRC error in NIC address\n"); > > > > +} > > > > > > This all looks like networking code. If this is the case, it should > > > be moved to drivers/networking or similar. > > > > no it's not. nic stands for number in a can produced by Dallas Semi also > > known under the name 1-Wire (https://en.wikipedia.org/wiki/1-Wire). > > SGI used them to provide partnumber, serialnumber and mac addresses. > > By placing the code to read the NiCs inside ioc3 driver there is no need > > for locking and adding library code for accessing these informations. > > Great. So it looks like you should be using this, no? > > drivers/base/regmap/regmap-w1.c not sure about regmap-w1, but drivers/w1 contains usefull stuff like w1_crc8. The only drawback I see right now is, that I need information about part numbers at ioc3 probe time, but for using w1 framework I need to create a platform device, which will give me this information not synchronous. I'll see how I could solve that. > > > > +static struct resource ioc3_uarta_resources[] = { > > > > + DEFINE_RES_MEM(offsetof(struct ioc3, sregs.uarta), > > > > > > You are the first user of offsetof() in MFD. Could you tell me why > > > it's required please? > > > > to get the offsets of different chip functions out of a struct. > > I can see what it does on a coding level. > > What are you using it for in practical/real terms? ioc3 has one PCI bar, where all different functions are accessible. The current ioc3 register map has all these functions set up in one struct. The base address of these registers comes out of the PCI framework and to use the MFD framework I need offsets for the different functions. And because there was already struct ioc3 I'm using offsetof on this struct. > Why wouldn't any other MFD driver require this, but you do? the other PCI MFD drivers I've looked at, have a PCI BAR per function, which makes live easier and no need for offsetof. Other MFD drivers #define the offsets and don't have a big struct, which contains all function registers. If you really insist on using #defines I need to go through a few parts of the kernel where struct ioc3 is still used. > > > > + if (ipd->info->funcs & IOC3_SER) { > > > > + writel(GPCR_UARTA_MODESEL | GPCR_UARTB_MODESEL, > > > > + &ipd->regs->gpcr_s); > > > > + writel(0, &ipd->regs->gppr[6]); > > > > + writel(0, &ipd->regs->gppr[7]); > > > > + udelay(100); > > > > + 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); > > > > > > No idea what any of this does. > > > > > > It looks like it belongs in the serial driver (and needs comments). > > > > it configures the IOC3 chip for serial usage. This is not part of > > the serial register set, so it IMHO belongs in the MFD driver. > > So it does serial things, but doesn't belong in the serial driver? It sets up IOC3 GPIOs and IOC3 serial mode in way the 8250 driver can work with the connected superio. > Could you please go into a bit more detail as to why you think that? > > Why is it better here? access to gpio and serial mode is outside of the 8250 register space. So either I need to export with some additional resources/new special platform data or just set it where it is done. > It's also totally unreadable by the way! sure, I'll add comments. > > > > + } > > > > +#if defined(CONFIG_SGI_IP27) > > > > > > What is this? Can't you obtain this dynamically by probing the H/W? > > > > that's the machine type and the #ifdef CONFIG_xxx are just for saving space, > > when compiled for other machines and it's easy to remove. > > Please find other ways to save the space. #ifery can get very messy, > very quickly and is almost always avoidable. space isn't a problem at all, so removing #ifdef CONFIG is easy. > > > > > + if (ipd->info->irq_offset) { > > > > > > What does this really signify? > > > > IOC3 ASICs are most of the time connected to a SGI bridge chip. IOC3 can > > provide two interrupt lines, which are wired to the bridge chip. The first > > interrupt is assigned via the PCI core, but since IOC3 is not a PCI multi > > function device the second interrupt must be treated here. And the used > > interrupt line on the bridge chip differs between boards. > > Please provide a MACRO, function or something else which results in > more readable code. Whatever you choose to use, please add this text > above, it will be helpful for future readers. will do. Thomas.