Message ID | 1453972838-30268-2-git-send-email-hpeter+linux_kernel@gmail.com |
---|---|
State | New |
Headers | show |
> +config MFD_FINTEK_F81504_CORE > + tristate "Fintek F81504/508/512 PCIE-to-UART/GPIO MFD support" > + depends on PCI > + select MFD_CORE > + default y > + help > + This driver generate F81504/508/512 UART & GPIO platform This driver provides the F81504/508/512 UART & GPIO platform > + device. It should enable CONFIG_GPIO_F81504 to get GPIOLIB devices. You should enable CONFIG_GPIO_F81504 to get GPIOLIB > + support and CONFIG_8250_F81504 to get serial ports support. port rather than ports > + Please bulit-in kernel if you need early console support. This driver needs to be built into the kernel to use early console support. > + switch (dev->device) { > + case FINTEK_F81504: /* 4 ports */ > + /* F81504 max 2 sets of GPIO, others are max 6 sets*/ > + gpio_en &= 0x03; > + case FINTEK_F81508: /* 8 ports */ > + max_port = dev->device & 0xff; If that is meant to fall through from F81504 into F81508 it's worth commenting, otherwise someone reviewing the code can't always be sure it was intentional. > + break; > + case FINTEK_F81512: /* 12 ports */ > + max_port = 12; > + break; > + default: > + return -EINVAL; > + } > + > + /* rewrite GPIO Mode setting */ > + pci_write_config_byte(dev, F81504_GPIO_ENABLE_REG, gpio_en & 0x3f); > + pci_write_config_byte(dev, F81504_GPIO_MODE_REG, ~gpio_en & 0x3f); > + > + /* Get the UART IO address dispatch from the BIOS */ > + pci_read_config_dword(dev, 0x24, &bar_data[0]); > + pci_read_config_dword(dev, 0x20, &bar_data[1]); > + pci_read_config_dword(dev, 0x1c, &bar_data[2]); Take these from the pci device itself. On some non PC platforms the values in the pci bar may be remapped by bridges and not give you the true answer. pci_resource_start(dev, barnumber) Alan -- To unsubscribe from this list: send the line "unsubscribe linux-gpio" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
On Thu, 2016-01-28 at 17:20 +0800, Peter Hung wrote: > The Fintek F81504/508/512 had implemented the basic serial port > function in > 8250_pci.c. We try to implement high baudrate & GPIOLIB with a spilt > file > 8250_f81504.c, but it seems too complex to add GPIOLIB. > > Alan & Andy recommend us to rewrite and spilt our driver with MFD > architecture. > https://lkml.org/lkml/2016/1/19/288 My comments below. > --- a/drivers/mfd/Kconfig > +++ b/drivers/mfd/Kconfig > @@ -310,6 +310,17 @@ config HTC_I2CPLD > This device provides input and output GPIOs through an I2C > interface to one or more sub-chips. > > +config MFD_FINTEK_F81504_CORE > + tristate "Fintek F81504/508/512 PCIE-to-UART/GPIO MFD > support" > + depends on PCI > + select MFD_CORE > > + default y I'm not sure we have to have this always y. Perhaps default SERIAL_8250_PCI > --- a/drivers/mfd/Makefile > +++ b/drivers/mfd/Makefile > @@ -21,6 +21,8 @@ obj-$(CONFIG_HTC_EGPIO) += htc- > egpio.o > obj-$(CONFIG_HTC_PASIC3) += htc-pasic3.o > obj-$(CONFIG_HTC_I2CPLD) += htc-i2cpld.o > > +obj-$(CONFIG_MFD_FINTEK_F81504_CORE) += f81504-core.o I think '_' is better than '-'. What I saw and usually do is '_' for regular source modules and '-' for the resulting objects when they have more than one file. > --- /dev/null > +++ b/drivers/mfd/f81504-core.c > @@ -0,0 +1,331 @@ > +/* > + * Core operations for Fintek F81504/508/512 PCIE-to-UART/GPIO > device > + */ > +#include <linux/platform_device.h> > +#include <linux/pci.h> > +#include <linux/gpio.h> > +#include <linux/module.h> > +#include <linux/version.h> > +#include <linux/mfd/core.h> > +#include <linux/mfd/f81504.h> > + > +#define F81504_DRIVER_NAME "f81504_core" Yeah, exactly. > +#define F81504_DEV_DESC "Fintek F81504/508/512 PCIE- > to-UART core" Do you need this definition? Is it used more than once? > +#define F81504_IO_REGION 8 > + > +const u8 fintek_gpio_mapping[F81504_MAX_GPIO_CNT] = { 2, 3, 8, 9, > 10, 11 }; > +EXPORT_SYMBOL(fintek_gpio_mapping); > + > +static int f81504_port_init(struct pci_dev *dev) > +{ > + size_t i, j; > + u32 max_port, iobase, gpio_addr; > + u32 bar_data[3]; > + u16 tmp; > + u8 config_base, gpio_en, f0h_data, f3h_data; > + bool is_gpio; > + struct f81504_pci_private *priv = pci_get_drvdata(dev); I would suggest to rearrange definition block here (and in the rest of the functions in entire series) to somehow follow the following pattern 1) assignments go first, especially container_of() based ones; 2) group variables by meaning and put longer lines upper; 3) return value variable, if exists, goes last. Besides that choose types carefully. If there is known limit for counters, no need to define wider type than needed. Use proper types for corresponding values. > + > + /* Init GPIO IO Address */ > + pci_read_config_dword(dev, 0x18, &gpio_addr); > + gpio_addr &= 0xffffffe0; > + pci_write_config_byte(dev, F81504_GPIO_IO_LSB_REG, gpio_addr > & 0xff); > + pci_write_config_byte(dev, F81504_GPIO_IO_MSB_REG, > (gpio_addr >> 8) & > + 0xff); Could it be written at once as a word? > + if (priv) { > + /* Reinit from resume(), read the previous value > from priv */ > > + gpio_en = priv->gpio_en; I would suggest to move this line down to follow same pattern in else branch. > + > + /* re-save GPIO IO addr for called by resume() */ re-save -> Re-save addr -> address > + priv->gpio_ioaddr = gpio_addr; > + } else { > + /* Driver first init */ > + pci_read_config_byte(dev, F81504_GPIO_ENABLE_REG, > &f0h_data); > + pci_read_config_byte(dev, F81504_GPIO_MODE_REG, > &f3h_data); > + > + /* find the max set of GPIOs */ Use one pattern for all comments, like "Capital letter first, and full words avoiding abbreviations". > + gpio_en = f0h_data | ~f3h_data; > + } > + > + switch (dev->device) { > + case FINTEK_F81504: /* 4 ports */ > + /* F81504 max 2 sets of GPIO, others are max 6 > sets*/ Space before * is needed. > + gpio_en &= 0x03; > + case FINTEK_F81508: /* 8 ports */ > + max_port = dev->device & 0xff; > + break; > + case FINTEK_F81512: /* 12 ports */ > + max_port = 12; > + break; > + default: > + return -EINVAL; > + } > + > + /* rewrite GPIO Mode setting */ > + pci_write_config_byte(dev, F81504_GPIO_ENABLE_REG, gpio_en & > 0x3f); > + pci_write_config_byte(dev, F81504_GPIO_MODE_REG, ~gpio_en & > 0x3f); > + > + /* Get the UART IO address dispatch from the BIOS */ > + pci_read_config_dword(dev, 0x24, &bar_data[0]); > + pci_read_config_dword(dev, 0x20, &bar_data[1]); > + pci_read_config_dword(dev, 0x1c, &bar_data[2]); > + > + /* Compatible bit for newer step IC */ > + pci_read_config_word(dev, F81504_IRQSEL_REG, &tmp); > + tmp |= BIT(8); > + pci_write_config_word(dev, F81504_IRQSEL_REG, tmp); > + > + for (i = 0; i < max_port; ++i) { i++, since it's more usual pattern. > + /* UART0 configuration offset start from 0x40 */ > + config_base = F81504_UART_START_ADDR + > F81504_UART_OFFSET * i; > + is_gpio = false; > + > > + /* find every port to check is multi-function port? > */ > + for (j = 0; j < ARRAY_SIZE(fintek_gpio_mapping); > ++j) { > + if (fintek_gpio_mapping[j] != i || !(gpio_en > & BIT(j))) > + continue; > + > + /* > + * This port is multi-function and enabled > as gpio > + * mode. So we'll not configure it as serial > port. > + */ > + is_gpio = true; > + break; > + } Looks like a separate function. func() { for () { if () return X; } return Y; } > + > + /* > + * If the serial port is setting to gpio mode, don't > init it. > + * Disable the serial port for user-space > application to > + * control. > + */ > + if (is_gpio) { > + /* Disable current serial port */ > + pci_write_config_byte(dev, config_base + > 0x00, 0x00); > + continue; > + } > + > + /* Calculate Real IO Port */ > + iobase = (bar_data[i / 4] & 0xffffffe0) + (i % 4) * > 8; > + > + /* Enable UART I/O port */ > + pci_write_config_byte(dev, config_base + 0x00, > 0x01); > + > + /* Select 128-byte FIFO and 8x FIFO threshold */ > + pci_write_config_byte(dev, config_base + 0x01, > 0x33); > + > + /* LSB UART */ > + pci_write_config_byte(dev, config_base + 0x04, > + (u8)(iobase & 0xff)); Redundant explicit casting. > + > + /* MSB UART */ > + pci_write_config_byte(dev, config_base + 0x05, > + (u8)((iobase & 0xff00) >> 8)); Ditto. And similar question, can it be written as a word? > + > + pci_write_config_byte(dev, config_base + 0x06, dev- > >irq); > + > + /* > + * Force init to RS232 / Share Mode, recovery > previous mode > + * will done in F81504 8250 platform driver resume() Period at the end of sentences in multi-line comments. > + */ > + pci_write_config_byte(dev, config_base + 0x07, > 0x01); > + } > + > + return 0; > +} > + > +static int f81504_prepage_serial_port(struct pci_dev *dev, int > max_port) > +{ > + size_t i; > + int status; > + u8 tmp; > + u16 iobase; > + struct resource resources = DEFINE_RES_IO(0, 0); > + struct mfd_cell f81504_serial_cell = { > + .name = F81504_SERIAL_NAME, > + .num_resources = 1, > + }; > + > + for (i = 0; i < max_port; ++i) { > + /* Check UART is enabled */ > > + pci_read_config_byte(dev, F81504_UART_START_ADDR + i > * > + F81504_UART_OFFSET, &tmp); > + if (!tmp) > + continue; So, this is the same as below, right? > + > + /* Get UART IO Address */ > + pci_read_config_word(dev, F81504_UART_START_ADDR + i > * > + F81504_UART_OFFSET + 4, &iobase); …why not to check here? > + > + resources.start = iobase; > + resources.end = iobase + F81504_IO_REGION - 1; > + > + f81504_serial_cell.resources = &resources; > + f81504_serial_cell.pdata_size = sizeof(i); This is static, can be part of definition. > + f81504_serial_cell.platform_data = &i; > + > + status = mfd_add_devices(&dev->dev, > PLATFORM_DEVID_AUTO, > + &f81504_serial_cell, 1, > NULL, dev->irq, > + NULL); > + if (status) { > + dev_warn(&dev->dev, "%s: add device failed: > %d\n", > + __func__, status); Indent _ under &. > + return status; > + } > + } > + > + return 0; > +} > + > +static int f81504_prepage_gpiolib(struct pci_dev *dev) > +{ > + size_t i; > + int status; > + struct f81504_pci_private *priv = pci_get_drvdata(dev); > + struct mfd_cell f81504_gpio_cell = { > + .name = F81504_GPIO_NAME, > + }; > + > + for (i = 0; i < ARRAY_SIZE(fintek_gpio_mapping); ++i) { > + if (!(priv->gpio_en & BIT(i))) > + continue; > + > > + f81504_gpio_cell.pdata_size = sizeof(i); Static. The problem here is badly chosen type of i. Like I mentioned at the top of review… > + f81504_gpio_cell.platform_data = &i; > + > + status = mfd_add_devices(&dev->dev, > PLATFORM_DEVID_AUTO, > + &f81504_gpio_cell, 1, NULL, > dev->irq, > + NULL); > + if (status) { > + dev_warn(&dev->dev, "%s: add device failed: > %d\n", > + __func__, status); > + return status; > + } > + } > + > + return 0; > +} > + > +static int f81504_probe(struct pci_dev *dev, const struct > pci_device_id > + *dev_id) Usually drivers are using pdev, id pair in parameters. Shorter; known pattern. > +{ > + int status; > + u8 tmp; > + struct f81504_pci_private *priv; > + > + status = pci_enable_device(dev); Please, pcim_, see comment below. > + if (status) > + return status; > + > + /* Init PCI Configuration Space */ > + status = f81504_port_init(dev); > + if (status) Device left enabled if not managed. > + return status; > + > + priv = devm_kzalloc(&dev->dev, sizeof(struct > f81504_pci_private), > + GFP_KERNEL); > + if (!priv) Ditto. > + return -ENOMEM; > + > + /* Save the GPIO_ENABLE_REG after f81504_port_init() for > future use */ > + pci_read_config_byte(dev, F81504_GPIO_ENABLE_REG, &priv- > >gpio_en); > + > + /* Save GPIO IO Addr to private data */ > > + pci_read_config_byte(dev, F81504_GPIO_IO_MSB_REG, &tmp); > + priv->gpio_ioaddr = tmp << 8; > + pci_read_config_byte(dev, F81504_GPIO_IO_LSB_REG, &tmp); > + priv->gpio_ioaddr |= tmp; One read as word? > + > + pci_set_drvdata(dev, priv); > + > + /* Generate UART Ports */ > + status = f81504_prepage_serial_port(dev, dev_id- > >driver_data); > + if (status) > + goto failed; > + > + /* Generate GPIO Sets */ > + status = f81504_prepage_gpiolib(dev); > + if (status) > + goto failed; > + > + return 0; > + > +failed: > > + mfd_remove_devices(&dev->dev); mfd_add_devices takes care. > + pci_disable_device(dev); pcim_ takes case. > + return status; > +} > + > +static void f81504_remove(struct pci_dev *dev) > +{ > + mfd_remove_devices(&dev->dev); > + pci_disable_device(dev); Ditto. > +} > + > +#ifdef CONFIG_PM_SLEEP > +static int f81504_suspend(struct device *dev) > +{ > + return 0; > +} > + > +static int f81504_resume(struct device *dev) > +{ > + int status; > + struct pci_dev *pdev = to_pci_dev(dev); > + > > + status = pci_enable_device(pdev); > + if (status) > + return status; It's done by PCI core I believe. > + > + /* Re-init PCI Configuration Space */ > + status = f81504_port_init(pdev); > + if (status) > + return status; > + > + return 0; > +} > +#endif > + > +static const struct pci_device_id f81504_dev_table[] = { > + /* Fintek PCI serial cards */ > + {PCI_DEVICE(FINTEK_VID, FINTEK_F81504), .driver_data = 4}, > + {PCI_DEVICE(FINTEK_VID, FINTEK_F81508), .driver_data = 8}, > + {PCI_DEVICE(FINTEK_VID, FINTEK_F81512), .driver_data = 12}, > + {} So, if you have default y for this and 8250_pci, which one will be loaded? > +}; > + > +static SIMPLE_DEV_PM_OPS(f81504_pm_ops, f81504_suspend, > f81504_resume); > + > +static struct pci_driver f81504_driver = { > + .name = F81504_DRIVER_NAME, > + .probe = f81504_probe, > + .remove = f81504_remove, > + .driver = { > + .pm = &f81504_pm_ops, > + .owner = THIS_MODULE, kbuild already complained about. > + }, > + .id_table = f81504_dev_table, > +}; > + > +module_pci_driver(f81504_driver); > + > +MODULE_DEVICE_TABLE(pci, f81504_dev_table); > +MODULE_DESCRIPTION(F81504_DEV_DESC); > +MODULE_AUTHOR("Peter Hong <Peter_Hong@fintek.com.tw>"); > +MODULE_LICENSE("GPL"); > diff --git a/include/linux/mfd/f81504.h b/include/linux/mfd/f81504.h > new file mode 100644 > index 0000000..13bd0ae > --- /dev/null > +++ b/include/linux/mfd/f81504.h > @@ -0,0 +1,52 @@ > +#ifndef __F81504_H__ __MFD_F… > +#define __F81504_H__ > + > +#define FINTEK_VID 0x1c29 > +#define FINTEK_F81504 0x1104 > +#define FINTEK_F81508 0x1108 > +#define FINTEK_F81512 0x1112 > + > +#define FINTEK_MAX_PORT 12 > +#define FINTEK_GPIO_NAME_LEN 32 > +#define FINTEK_GPIO_DISPLAY "GPIO" > + > +#define F81504_UART_START_ADDR 0x40 > +#define F81504_UART_MODE_OFFSET 0x07 > +#define F81504_UART_OFFSET 0x08 > + > +/* RTS will control by MCR if this bit is 0 */ > +#define F81504_RTS_CONTROL_BY_HW BIT(4) > +/* only worked with FINTEK_RTS_CONTROL_BY_HW on */ > +#define F81504_RTS_INVERT BIT(5) > + > +#define F81504_CLOCK_RATE_MASK 0xc0 > +#define F81504_CLKSEL_1DOT846_MHZ 0x00 > +#define F81504_CLKSEL_18DOT46_MHZ 0x40 > +#define F81504_CLKSEL_24_MHZ 0x80 > +#define F81504_CLKSEL_14DOT77_MHZ 0xc0 > + > +#define F81504_IRQSEL_REG 0xb8 > + > +#define F81504_GPIO_ENABLE_REG 0xf0 > +#define F81504_GPIO_IO_LSB_REG 0xf1 > +#define F81504_GPIO_IO_MSB_REG 0xf2 > +#define F81504_GPIO_MODE_REG 0xf3 > + > +#define F81504_GPIO_START_ADDR 0xf8 > +#define F81504_GPIO_OUT_EN_OFFSET 0x00 > +#define F81504_GPIO_DRIVE_EN_OFFSET 0x01 > +#define F81504_GPIO_SET_OFFSET 0x08 > + > +#define F81504_GPIO_NAME "f81504_gpio" > +#define F81504_SERIAL_NAME "f81504_serial" > +#define F81504_MAX_GPIO_CNT 6 > + > +extern const u8 fintek_gpio_mapping[F81504_MAX_GPIO_CNT]; > + > +struct f81504_pci_private { > + int line[FINTEK_MAX_PORT]; > + u8 gpio_en; > + u16 gpio_ioaddr; > + u32 uart_count, gpio_count; > +}; > +#endif
Hi Alan, One Thousand Gnomes 於 2016/1/28 下午 06:04 寫道: >> + Please bulit-in kernel if you need early console support. > This driver needs to be built into the kernel to use early console > support. > ok >> + switch (dev->device) { >> + case FINTEK_F81504: /* 4 ports */ >> + /* F81504 max 2 sets of GPIO, others are max 6 sets*/ >> + gpio_en &= 0x03; >> + case FINTEK_F81508: /* 8 ports */ >> + max_port = dev->device & 0xff; > > If that is meant to fall through from F81504 into F81508 it's worth > commenting, otherwise someone reviewing the code can't always be sure it > was intentional. ok, I'll add comments to describe this. >> + /* Get the UART IO address dispatch from the BIOS */ >> + pci_read_config_dword(dev, 0x24, &bar_data[0]); >> + pci_read_config_dword(dev, 0x20, &bar_data[1]); >> + pci_read_config_dword(dev, 0x1c, &bar_data[2]); > > Take these from the pci device itself. On some non PC platforms the > values in the pci bar may be remapped by bridges and not give you the > true answer. > > pci_resource_start(dev, barnumber) > Thanks for point this out, I'll rewrite here with pci_resource_start().
Hi Andy, Andy Shevchenko 於 2016/1/28 下午 07:55 寫道: >> + default y > > I'm not sure we have to have this always y. Perhaps > default SERIAL_8250_PCI Your comment is right, this device major function is serial port. GPIO maybe not enabled by H/W manufacturer. I'll set it default with SERIAL_8250_PCI. >> +obj-$(CONFIG_MFD_FINTEK_F81504_CORE) += f81504-core.o > > I think '_' is better than '-'. What I saw and usually do is '_' for > regular source modules and '-' for the resulting objects when they have > more than one file. I used f81504_core.c originally, but I found most of files are named xxx-ooo.c when I try to modify makefile. Should I change it to f81504_core.c ?? >> +#define F81504_DEV_DESC "Fintek F81504/508/512 PCIE- >> to-UART core" > > Do you need this definition? Is it used more than once? ok, I'll direct use the string without define. >> +static int f81504_port_init(struct pci_dev *dev) >> +{ >> + size_t i, j; >> + u32 max_port, iobase, gpio_addr; >> + u32 bar_data[3]; >> + u16 tmp; >> + u8 config_base, gpio_en, f0h_data, f3h_data; >> + bool is_gpio; >> + struct f81504_pci_private *priv = pci_get_drvdata(dev); > > I would suggest to rearrange definition block here (and in the rest of > the functions in entire series) to somehow follow the following pattern > > 1) assignments go first, especially container_of() based ones; > 2) group variables by meaning and put longer lines upper; > 3) return value variable, if exists, goes last. > > Besides that choose types carefully. If there is known limit for > counters, no need to define wider type than needed. Use proper types > for corresponding values. I'll try to rearrange the definition blocks. For the counter issue, some senior recommend me to change count from int to size_t for performance. In this case, should I use u8 to replace i & j ? It should be less than 12 & 6. >> + >> + /* Init GPIO IO Address */ >> + pci_read_config_dword(dev, 0x18, &gpio_addr); >> + gpio_addr &= 0xffffffe0; > > >> + pci_write_config_byte(dev, F81504_GPIO_IO_LSB_REG, gpio_addr >> & 0xff); >> + pci_write_config_byte(dev, F81504_GPIO_IO_MSB_REG, >> (gpio_addr >> 8) & >> + 0xff); > > Could it be written at once as a word? I tested with writing a word to "F81504_GPIO_IO_LSB_REG", but it'll failed, so I'll remain it. >> + if (priv) { >> + /* Reinit from resume(), read the previous value >> from priv */ >> > >> + gpio_en = priv->gpio_en; > > I would suggest to move this line down to follow same pattern in else > branch. The f81504_port_init() will be called probe()/resume(). We'll initialize gpio_en = (f0h | ~f3h) and save it in private data when probe(), reload gpio_en from private data when resume(). The F81504/508/512 can be used EEPROM to override F0h/F3h on power on. Some motherboard designer will put the IC on board and want to cost down EEPROM. They will setting F0/F3h with ASL code, but without EEPROM , the IC back from resume() will get F0/F3h with 0x00, so we should save gpio_en when probe(), and restore it when resume(). >> + >> + /* re-save GPIO IO addr for called by resume() */ > > re-save -> Re-save > addr -> address ok >> + priv->gpio_ioaddr = gpio_addr; >> + } else { >> + /* Driver first init */ >> + pci_read_config_byte(dev, F81504_GPIO_ENABLE_REG, >> &f0h_data); >> + pci_read_config_byte(dev, F81504_GPIO_MODE_REG, >> &f3h_data); >> + >> + /* find the max set of GPIOs */ > > Use one pattern for all comments, like "Capital letter first, and full > words avoiding abbreviations". ok >> + case FINTEK_F81504: /* 4 ports */ >> + /* F81504 max 2 sets of GPIO, others are max 6 >> sets*/ > > Space before * is needed. ok >> + for (i = 0; i < max_port; ++i) { > > i++, since it's more usual pattern. ok >> + /* find every port to check is multi-function port? >> */ >> + for (j = 0; j < ARRAY_SIZE(fintek_gpio_mapping); >> ++j) { >> + if (fintek_gpio_mapping[j] != i || !(gpio_en >> & BIT(j))) >> + continue; >> + >> + /* >> + * This port is multi-function and enabled >> as gpio >> + * mode. So we'll not configure it as serial >> port. >> + */ >> + is_gpio = true; >> + break; >> + } > > Looks like a separate function. > > func() > { > for () { > if () > return X; > } > return Y; > } ok. >> + /* Select 128-byte FIFO and 8x FIFO threshold */ >> + pci_write_config_byte(dev, config_base + 0x01, >> 0x33); >> + >> + /* LSB UART */ >> + pci_write_config_byte(dev, config_base + 0x04, >> + (u8)(iobase & 0xff)); > > Redundant explicit casting. ok >> + >> + /* MSB UART */ >> + pci_write_config_byte(dev, config_base + 0x05, >> + (u8)((iobase & 0xff00) >> 8)); > > Ditto. > > And similar question, can it be written as a word? Here can be written with a word, I'll rewrite it. >> + >> + pci_write_config_byte(dev, config_base + 0x06, dev- >>> irq); >> + >> + /* >> + * Force init to RS232 / Share Mode, recovery >> previous mode >> + * will done in F81504 8250 platform driver resume() > > Period at the end of sentences in multi-line comments. Sorry, what this mean for ? I cant use multi-line comments in the end ?? >> + for (i = 0; i < max_port; ++i) { >> + /* Check UART is enabled */ >> > >> + pci_read_config_byte(dev, F81504_UART_START_ADDR + i >> * >> + F81504_UART_OFFSET, &tmp); >> + if (!tmp) >> + continue; > > So, this is the same as below, right? > >> + >> + /* Get UART IO Address */ >> + pci_read_config_word(dev, F81504_UART_START_ADDR + i >> * >> + F81504_UART_OFFSET + 4, &iobase); > > …why not to check here? It's a bit difference, this section will check if UART enabled (tmp). It'll generate platform device and get IO address when tmp != 0. If tmp = 0, skip this idx, dont need to get current IO address and try with next. >> + f81504_serial_cell.pdata_size = sizeof(i); > > This is static, can be part of definition. ok >> + f81504_serial_cell.platform_data = &i; >> + >> + status = mfd_add_devices(&dev->dev, >> PLATFORM_DEVID_AUTO, >> + &f81504_serial_cell, 1, >> NULL, dev->irq, >> + NULL); >> + if (status) { >> + dev_warn(&dev->dev, "%s: add device failed: >> %d\n", >> + __func__, status); > > Indent _ under &. Some senior recommend me to do at least 2-tabs to do indent when code cross multi-line. So I'll use to 2-tabs from "dev" to do indent. How should I do with indent ?? It seems no consensus, but Lindent will do indent like your comments. > >> + f81504_gpio_cell.pdata_size = sizeof(i); > > Static. The problem here is badly chosen type of i. Like I mentioned at > the top of review… I'll try to rewrite it with pass a structure. It seems more make sense. >> +static int f81504_probe(struct pci_dev *dev, const struct >> pci_device_id >> + *dev_id) > > Usually drivers are using pdev, id pair in parameters. Shorter; known > pattern. ok. >> +{ >> + int status; >> + u8 tmp; >> + struct f81504_pci_private *priv; >> + >> + status = pci_enable_device(dev); > > Please, pcim_, see comment below. >> + if (status) >> + return status; >> + >> + /* Init PCI Configuration Space */ >> + status = f81504_port_init(dev); >> + if (status) > > Device left enabled if not managed. >> + return status; >> + >> + priv = devm_kzalloc(&dev->dev, sizeof(struct >> f81504_pci_private), >> + GFP_KERNEL); >> + if (!priv) > > Ditto. ok, I'll rewrite it with managed type. >> + return -ENOMEM; >> + >> + /* Save the GPIO_ENABLE_REG after f81504_port_init() for >> future use */ >> + pci_read_config_byte(dev, F81504_GPIO_ENABLE_REG, &priv- >>> gpio_en); >> + >> + /* Save GPIO IO Addr to private data */ >> > >> + pci_read_config_byte(dev, F81504_GPIO_IO_MSB_REG, &tmp); >> + priv->gpio_ioaddr = tmp << 8; >> + pci_read_config_byte(dev, F81504_GPIO_IO_LSB_REG, &tmp); >> + priv->gpio_ioaddr |= tmp; > > One read as word? It can't read as word. The "F81504_GPIO_IO_LSB_REG" is 0xf1, It seems can't be read word/dword from a odd address. I'll remain it. >> + mfd_remove_devices(&dev->dev); > > mfd_add_devices takes care. > >> + pci_disable_device(dev); > > pcim_ takes case. ok >> + return status; >> +} >> + >> +static void f81504_remove(struct pci_dev *dev) >> +{ > >> + mfd_remove_devices(&dev->dev); >> + pci_disable_device(dev); > > Ditto. ok >> +static int f81504_resume(struct device *dev) >> +{ >> + int status; >> + struct pci_dev *pdev = to_pci_dev(dev); >> + >> + status = pci_enable_device(pdev); >> + if (status) >> + return status; > > It's done by PCI core I believe. ok >> +static const struct pci_device_id f81504_dev_table[] = { >> + /* Fintek PCI serial cards */ >> + {PCI_DEVICE(FINTEK_VID, FINTEK_F81504), .driver_data = 4}, >> + {PCI_DEVICE(FINTEK_VID, FINTEK_F81508), .driver_data = 8}, >> + {PCI_DEVICE(FINTEK_VID, FINTEK_F81512), .driver_data = 12}, >> + {} > > So, if you have default y for this and 8250_pci, which one will be > loaded? I had tested on x86, It'll handle by 8250_pci.c when this & 8250_pci.c all built-in mode. BTW, due to the series patches across 3 subsystems MFD/GPIO/8250. I make the series patches under MFD subsystem, but the buildbot report git repository conflict with GPIO & 8250 (patch 2 & 3). Should I split the series patches into 3 independent patch and based on their maintainer git repository? >> +static struct pci_driver f81504_driver = { >> + .name = F81504_DRIVER_NAME, >> + .probe = f81504_probe, >> + .remove = f81504_remove, >> + .driver = { >> + .pm = &f81504_pm_ops, > > >> + .owner = THIS_MODULE, > > kbuild already complained about. ok >> diff --git a/include/linux/mfd/f81504.h b/include/linux/mfd/f81504.h >> new file mode 100644 >> index 0000000..13bd0ae >> --- /dev/null >> +++ b/include/linux/mfd/f81504.h >> @@ -0,0 +1,52 @@ >> +#ifndef __F81504_H__ > > __MFD_F… ok Thanks for your advices
On Fri, 29 Jan 2016, Peter Hung wrote: > >>+obj-$(CONFIG_MFD_FINTEK_F81504_CORE) += f81504-core.o > > > >I think '_' is better than '-'. What I saw and usually do is '_' for > >regular source modules and '-' for the resulting objects when they have > >more than one file. > > I used f81504_core.c originally, but I found most of files are named > xxx-ooo.c when I try to modify makefile. Should I change it to > f81504_core.c ?? I prefer '-' in MFD. > >>+#define F81504_DEV_DESC "Fintek F81504/508/512 PCIE- > >>to-UART core" > > > >Do you need this definition? Is it used more than once? > > ok, I'll direct use the string without define. Please rid all of the {DEV_NAME,DEV_DEC} defines, they only serve to obfuscate code.
On Fri, 2016-01-29 at 13:50 +0800, Peter Hung wrote: > Andy Shevchenko 於 2016/1/28 下午 07:55 寫道: > > > +obj-$(CONFIG_MFD_FINTEK_F81504_CORE) += f81504-core.o > +static int f81504_port_init(struct pci_dev *dev) > > > +{ > > > + size_t i, j; > > > + u32 max_port, iobase, gpio_addr; > > > + u32 bar_data[3]; > > > + u16 tmp; > > > + u8 config_base, gpio_en, f0h_data, f3h_data; > > > + bool is_gpio; > > > + struct f81504_pci_private *priv = pci_get_drvdata(dev); > > > > I would suggest to rearrange definition block here (and in the rest > > of > > the functions in entire series) to somehow follow the following > > pattern > > > > 1) assignments go first, especially container_of() based ones; > > 2) group variables by meaning and put longer lines upper; > > 3) return value variable, if exists, goes last. > > > > Besides that choose types carefully. If there is known limit for > > counters, no need to define wider type than needed. Use proper > > types > > for corresponding values. > > I'll try to rearrange the definition blocks. > For the counter issue, some senior recommend me to change count from > int > to size_t for performance. Wow, how come? On which arch? Also mark this as (1) to refer below. > In this case, should I use u8 to replace > i & j ? It should be less than 12 & 6. At least you tell compiler that it may use any suitable type. In any case the last decision is by compiler if you don't do any tricks. So, I suggest to use non-fixed width type like (unsigned) int and leave everything else on compiler. > > > + > > > + /* Init GPIO IO Address */ > > > + pci_read_config_dword(dev, 0x18, &gpio_addr); > > > + gpio_addr &= 0xffffffe0; > > > > > > > + pci_write_config_byte(dev, F81504_GPIO_IO_LSB_REG, > > > gpio_addr > > > & 0xff); > > > + pci_write_config_byte(dev, F81504_GPIO_IO_MSB_REG, > > > (gpio_addr >> 8) & > > > + 0xff); > > > > Could it be written at once as a word? > > I tested with writing a word to "F81504_GPIO_IO_LSB_REG", but > it'll failed, so I'll remain it. Please, add a comment line above. > > > > + if (priv) { > > > + /* Reinit from resume(), read the previous value > > > from priv */ > > > > > > > > + gpio_en = priv->gpio_en; > > > > I would suggest to move this line down to follow same pattern in > > else > > branch. I'm talking here to make simple rearrangement like if () { … gpio_en = … } else { … gpio_en = … } Thus the gpio_en assignment goes last in both branches. > > > + > > > + pci_write_config_byte(dev, config_base + 0x06, > > > dev- > > > > irq); > > > + > > > + /* > > > + * Force init to RS232 / Share Mode, recovery > > > previous mode > > > + * will done in F81504 8250 platform driver > > > resume() > > > > Period at the end of sentences in multi-line comments. > > Sorry, what this mean for ? > I cant use multi-line comments in the end ?? Sentences are started with capital letters and end with period '.' character, like this one. > > > + for (i = 0; i < max_port; ++i) { > > > + /* Check UART is enabled */ > > > > > > > > + pci_read_config_byte(dev, F81504_UART_START_ADDR > > > + i > > > * > > > + F81504_UART_OFFSET, &tmp); > > > + if (!tmp) > > > + continue; > > > > So, this is the same as below, right? > > > > > + > > > + /* Get UART IO Address */ > > > + pci_read_config_word(dev, F81504_UART_START_ADDR > > > + i > > > * > > > + F81504_UART_OFFSET + 4, > > > &iobase); > > > > …why not to check here? > > It's a bit difference, this section will check if UART enabled (tmp). > It'll generate platform device and get IO address when tmp != 0. If > tmp = 0, skip this idx, dont need to get current IO address and try > with next. Yeah, I missed that the offset is different. > + if (status) { > > > + dev_warn(&dev->dev, "%s: add device > > > failed: > > > %d\n", > > > + __func__, status); > > > > Indent _ under &. > > Some senior recommend me to do at least 2-tabs to do indent when > code cross multi-line. So I'll use to 2-tabs from "dev" to do indent. > How should I do with indent ?? It seems no consensus, but Lindent > will do indent like your comments. I would suggest to use what is used in most recent drivers and modules. I don't remember that 2 tabs fact is somehow reflected in CodingStyle. Maybe your seniour was talkin about multi-line function definition? > + f81504_gpio_cell.pdata_size = sizeof(i); > > > > Static. The problem here is badly chosen type of i. Like I > > mentioned at > > the top of review… > > I'll try to rewrite it with pass a structure. It seems more make > sense. That's correct on one side, on the other I'm not sure you got my comment. size_t is arch-dependent type, sizeof() is not the same everywhere. > + pci_read_config_byte(dev, F81504_GPIO_IO_MSB_REG, &tmp); > > > + priv->gpio_ioaddr = tmp << 8; > > > + pci_read_config_byte(dev, F81504_GPIO_IO_LSB_REG, &tmp); > > > + priv->gpio_ioaddr |= tmp; > > > > One read as word? > > It can't read as word. The "F81504_GPIO_IO_LSB_REG" is 0xf1, It > seems can't be read word/dword from a odd address. > > I'll remain it. Put comment about that. > > > > +static const struct pci_device_id f81504_dev_table[] = { > > > + /* Fintek PCI serial cards */ > > > + {PCI_DEVICE(FINTEK_VID, FINTEK_F81504), .driver_data = > > > 4}, > > > + {PCI_DEVICE(FINTEK_VID, FINTEK_F81508), .driver_data = > > > 8}, > > > + {PCI_DEVICE(FINTEK_VID, FINTEK_F81512), .driver_data = > > > 12}, > > > + {} > > > > So, if you have default y for this and 8250_pci, which one will be > > loaded? > > I had tested on x86, It'll handle by 8250_pci.c when this & > 8250_pci.c > all built-in mode. Yeah. here is the problem. When you introduce that you have to be sure that no-one will try to build kernel with both included right now. To avoid potential problem you may split the driver to main part and Kconfig enabling part. Lee, what is your advice here? > > BTW, due to the series patches across 3 subsystems MFD/GPIO/8250. > I make the series patches under MFD subsystem, but the buildbot > report git repository conflict with GPIO & 8250 (patch 2 & 3). > > Should I split the series patches into 3 independent patch and > based on their maintainer git repository? It should go via one subsystem, otherwise user may end up with non- working interim kernel version, i.e. when bisecting. In case if it based on some stuff which is not in upstream yet, you have to describe this carefully to maintainers that they may create immutable branches and cross-apply the stuff. But I suppose it's not your case and your series is quite straightforward.
Hi Andy, Andy Shevchenko 於 2016/1/29 下午 09:41 寫道: > On Fri, 2016-01-29 at 13:50 +0800, Peter Hung wrote: >> Andy Shevchenko 於 2016/1/28 下午 07:55 寫道: >>> I would suggest to rearrange definition block here (and in the rest >>> of >>> the functions in entire series) to somehow follow the following >>> pattern >>> >>> 1) assignments go first, especially container_of() based ones; >>> 2) group variables by meaning and put longer lines upper; >>> 3) return value variable, if exists, goes last. >>> >>> Besides that choose types carefully. If there is known limit for >>> counters, no need to define wider type than needed. Use proper >>> types >>> for corresponding values. >> >> I'll try to rearrange the definition blocks. > > >> For the counter issue, some senior recommend me to change count from >> int >> to size_t for performance. > > Wow, how come? On which arch? > > Also mark this as (1) to refer below. > >> In this case, should I use u8 to replace >> i & j ? It should be less than 12 & 6. > > At least you tell compiler that it may use any suitable type. In any > case the last decision is by compiler if you don't do any tricks. > > So, I suggest to use non-fixed width type like (unsigned) int and leave > everything else on compiler. Sorry for my misunderstanding, not for performance. The senior just recommend me to replace all size/count variables to size_t. https://lkml.org/lkml/2016/1/3/193 size_t in x86 is unsigned int, x86_64 is unsigned long, It maybe good for prefetch or match register size I guess. I'll make the size_t of series patches to unsigned int. >>>> + pci_write_config_byte(dev, F81504_GPIO_IO_LSB_REG, >>>> gpio_addr >>>> & 0xff); >>>> + pci_write_config_byte(dev, F81504_GPIO_IO_MSB_REG, >>>> (gpio_addr >> 8) & >>>> + 0xff); >>> >>> Could it be written at once as a word? >> >> I tested with writing a word to "F81504_GPIO_IO_LSB_REG", but >> it'll failed, so I'll remain it. > > Please, add a comment line above. ok >> >>>> + if (priv) { >>>> + /* Reinit from resume(), read the previous value >>>> from priv */ >>>> >>> >>>> + gpio_en = priv->gpio_en; >>> >>> I would suggest to move this line down to follow same pattern in >>> else >>> branch. > > I'm talking here to make simple rearrangement like > > if () { > … > gpio_en = … > } else { > … > gpio_en = … > } > > Thus the gpio_en assignment goes last in both branches. ok >>>> + >>>> + pci_write_config_byte(dev, config_base + 0x06, >>>> dev- >>>>> irq); >>>> + >>>> + /* >>>> + * Force init to RS232 / Share Mode, recovery >>>> previous mode >>>> + * will done in F81504 8250 platform driver >>>> resume() >>> >>> Period at the end of sentences in multi-line comments. >> >> Sorry, what this mean for ? >> I cant use multi-line comments in the end ?? > > Sentences are started with capital letters and end with period '.' > character, like this one. ok. >> + if (status) { >>>> + dev_warn(&dev->dev, "%s: add device >>>> failed: >>>> %d\n", >>>> + __func__, status); >>> >>> Indent _ under &. >> >> Some senior recommend me to do at least 2-tabs to do indent when >> code cross multi-line. So I'll use to 2-tabs from "dev" to do indent. > >> How should I do with indent ?? It seems no consensus, but Lindent >> will do indent like your comments. > > I would suggest to use what is used in most recent drivers and modules. > I don't remember that 2 tabs fact is somehow reflected in CodingStyle. > > Maybe your seniour was talkin about multi-line function definition? ok, I'll indent multi-line statement as your comment and multi-line function with 2 tabs. >> + f81504_gpio_cell.pdata_size = sizeof(i); >>> >>> Static. The problem here is badly chosen type of i. Like I >>> mentioned at >>> the top of review… >> >> I'll try to rewrite it with pass a structure. It seems more make >> sense. > > That's correct on one side, on the other I'm not sure you got my > comment. size_t is arch-dependent type, sizeof() is not the same > everywhere. I'll change it to pass unsigned int. >> + pci_read_config_byte(dev, F81504_GPIO_IO_MSB_REG, &tmp); >>>> + priv->gpio_ioaddr = tmp << 8; >>>> + pci_read_config_byte(dev, F81504_GPIO_IO_LSB_REG, &tmp); >>>> + priv->gpio_ioaddr |= tmp; >>> >>> One read as word? >> >> It can't read as word. The "F81504_GPIO_IO_LSB_REG" is 0xf1, It >> seems can't be read word/dword from a odd address. >> >> I'll remain it. > > Put comment about that. ok >>> So, if you have default y for this and 8250_pci, which one will be >>> loaded? >> >> I had tested on x86, It'll handle by 8250_pci.c when this & >> 8250_pci.c >> all built-in mode. > > Yeah. here is the problem. When you introduce that you have to be sure > that no-one will try to build kernel with both included right now. Paul had recommend me to reduce the impact of code change. I'll remove all F81504/508/512 code in 8250_pci.c until the series patches had applied. The device will handle with 8250_pci.c before applide patch no4, and handle with f81504_code.c when applied patch no4. This maybe reduce the bisect misleading. >> >> BTW, due to the series patches across 3 subsystems MFD/GPIO/8250. >> I make the series patches under MFD subsystem, but the buildbot >> report git repository conflict with GPIO & 8250 (patch 2 & 3). >> >> Should I split the series patches into 3 independent patch and >> based on their maintainer git repository? > > It should go via one subsystem, otherwise user may end up with non- > working interim kernel version, i.e. when bisecting. > > In case if it based on some stuff which is not in upstream yet, you > have to describe this carefully to maintainers that they may create > immutable branches and cross-apply the stuff. But I suppose it's not > your case and your series is quite straightforward. > I'll still try the series patch based on MFD subsystem. Thanks for your advices.
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 9ca66de..40503a4 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -310,6 +310,17 @@ config HTC_I2CPLD This device provides input and output GPIOs through an I2C interface to one or more sub-chips. +config MFD_FINTEK_F81504_CORE + tristate "Fintek F81504/508/512 PCIE-to-UART/GPIO MFD support" + depends on PCI + select MFD_CORE + default y + help + This driver generate F81504/508/512 UART & GPIO platform + device. It should enable CONFIG_GPIO_F81504 to get GPIOLIB + support and CONFIG_8250_F81504 to get serial ports support. + Please bulit-in kernel if you need early console support. + config MFD_INTEL_QUARK_I2C_GPIO tristate "Intel Quark MFD I2C GPIO" depends on PCI diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 0f230a6..f7382b3 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -21,6 +21,8 @@ obj-$(CONFIG_HTC_EGPIO) += htc-egpio.o obj-$(CONFIG_HTC_PASIC3) += htc-pasic3.o obj-$(CONFIG_HTC_I2CPLD) += htc-i2cpld.o +obj-$(CONFIG_MFD_FINTEK_F81504_CORE) += f81504-core.o + obj-$(CONFIG_MFD_DAVINCI_VOICECODEC) += davinci_voicecodec.o obj-$(CONFIG_MFD_DM355EVM_MSP) += dm355evm_msp.o obj-$(CONFIG_MFD_TI_AM335X_TSCADC) += ti_am335x_tscadc.o diff --git a/drivers/mfd/f81504-core.c b/drivers/mfd/f81504-core.c new file mode 100644 index 0000000..9059171 --- /dev/null +++ b/drivers/mfd/f81504-core.c @@ -0,0 +1,331 @@ +/* + * Core operations for Fintek F81504/508/512 PCIE-to-UART/GPIO device + */ +#include <linux/platform_device.h> +#include <linux/pci.h> +#include <linux/gpio.h> +#include <linux/module.h> +#include <linux/version.h> +#include <linux/mfd/core.h> +#include <linux/mfd/f81504.h> + +#define F81504_DRIVER_NAME "f81504_core" +#define F81504_DEV_DESC "Fintek F81504/508/512 PCIE-to-UART core" +#define F81504_IO_REGION 8 + +const u8 fintek_gpio_mapping[F81504_MAX_GPIO_CNT] = { 2, 3, 8, 9, 10, 11 }; +EXPORT_SYMBOL(fintek_gpio_mapping); + +static int f81504_port_init(struct pci_dev *dev) +{ + size_t i, j; + u32 max_port, iobase, gpio_addr; + u32 bar_data[3]; + u16 tmp; + u8 config_base, gpio_en, f0h_data, f3h_data; + bool is_gpio; + struct f81504_pci_private *priv = pci_get_drvdata(dev); + + /* Init GPIO IO Address */ + pci_read_config_dword(dev, 0x18, &gpio_addr); + gpio_addr &= 0xffffffe0; + pci_write_config_byte(dev, F81504_GPIO_IO_LSB_REG, gpio_addr & 0xff); + pci_write_config_byte(dev, F81504_GPIO_IO_MSB_REG, (gpio_addr >> 8) & + 0xff); + + /* + * The PCI board is multi-function, some serial port can converts to + * GPIO function. Customers could changes the F0/F3h values in EEPROM + * + * F0h bit0~5: Enable GPIO0~5 + * bit6~7: Reserve + * + * F3h bit0~5: Multi-Functional Flag (0:GPIO/1:UART) + * bit0: UART2 pin out for UART2 / GPIO0 + * bit1: UART3 pin out for UART3 / GPIO1 + * bit2: UART8 pin out for UART8 / GPIO2 + * bit3: UART9 pin out for UART9 / GPIO3 + * bit4: UART10 pin out for UART10 / GPIO4 + * bit5: UART11 pin out for UART11 / GPIO5 + * bit6~7: Reserve + */ + if (priv) { + /* Reinit from resume(), read the previous value from priv */ + gpio_en = priv->gpio_en; + + /* re-save GPIO IO addr for called by resume() */ + priv->gpio_ioaddr = gpio_addr; + } else { + /* Driver first init */ + pci_read_config_byte(dev, F81504_GPIO_ENABLE_REG, &f0h_data); + pci_read_config_byte(dev, F81504_GPIO_MODE_REG, &f3h_data); + + /* find the max set of GPIOs */ + gpio_en = f0h_data | ~f3h_data; + } + + switch (dev->device) { + case FINTEK_F81504: /* 4 ports */ + /* F81504 max 2 sets of GPIO, others are max 6 sets*/ + gpio_en &= 0x03; + case FINTEK_F81508: /* 8 ports */ + max_port = dev->device & 0xff; + break; + case FINTEK_F81512: /* 12 ports */ + max_port = 12; + break; + default: + return -EINVAL; + } + + /* rewrite GPIO Mode setting */ + pci_write_config_byte(dev, F81504_GPIO_ENABLE_REG, gpio_en & 0x3f); + pci_write_config_byte(dev, F81504_GPIO_MODE_REG, ~gpio_en & 0x3f); + + /* Get the UART IO address dispatch from the BIOS */ + pci_read_config_dword(dev, 0x24, &bar_data[0]); + pci_read_config_dword(dev, 0x20, &bar_data[1]); + pci_read_config_dword(dev, 0x1c, &bar_data[2]); + + /* Compatible bit for newer step IC */ + pci_read_config_word(dev, F81504_IRQSEL_REG, &tmp); + tmp |= BIT(8); + pci_write_config_word(dev, F81504_IRQSEL_REG, tmp); + + for (i = 0; i < max_port; ++i) { + /* UART0 configuration offset start from 0x40 */ + config_base = F81504_UART_START_ADDR + F81504_UART_OFFSET * i; + is_gpio = false; + + /* find every port to check is multi-function port? */ + for (j = 0; j < ARRAY_SIZE(fintek_gpio_mapping); ++j) { + if (fintek_gpio_mapping[j] != i || !(gpio_en & BIT(j))) + continue; + + /* + * This port is multi-function and enabled as gpio + * mode. So we'll not configure it as serial port. + */ + is_gpio = true; + break; + } + + /* + * If the serial port is setting to gpio mode, don't init it. + * Disable the serial port for user-space application to + * control. + */ + if (is_gpio) { + /* Disable current serial port */ + pci_write_config_byte(dev, config_base + 0x00, 0x00); + continue; + } + + /* Calculate Real IO Port */ + iobase = (bar_data[i / 4] & 0xffffffe0) + (i % 4) * 8; + + /* Enable UART I/O port */ + pci_write_config_byte(dev, config_base + 0x00, 0x01); + + /* Select 128-byte FIFO and 8x FIFO threshold */ + pci_write_config_byte(dev, config_base + 0x01, 0x33); + + /* LSB UART */ + pci_write_config_byte(dev, config_base + 0x04, + (u8)(iobase & 0xff)); + + /* MSB UART */ + pci_write_config_byte(dev, config_base + 0x05, + (u8)((iobase & 0xff00) >> 8)); + + pci_write_config_byte(dev, config_base + 0x06, dev->irq); + + /* + * Force init to RS232 / Share Mode, recovery previous mode + * will done in F81504 8250 platform driver resume() + */ + pci_write_config_byte(dev, config_base + 0x07, 0x01); + } + + return 0; +} + +static int f81504_prepage_serial_port(struct pci_dev *dev, int max_port) +{ + size_t i; + int status; + u8 tmp; + u16 iobase; + struct resource resources = DEFINE_RES_IO(0, 0); + struct mfd_cell f81504_serial_cell = { + .name = F81504_SERIAL_NAME, + .num_resources = 1, + }; + + for (i = 0; i < max_port; ++i) { + /* Check UART is enabled */ + pci_read_config_byte(dev, F81504_UART_START_ADDR + i * + F81504_UART_OFFSET, &tmp); + if (!tmp) + continue; + + /* Get UART IO Address */ + pci_read_config_word(dev, F81504_UART_START_ADDR + i * + F81504_UART_OFFSET + 4, &iobase); + + resources.start = iobase; + resources.end = iobase + F81504_IO_REGION - 1; + + f81504_serial_cell.resources = &resources; + f81504_serial_cell.pdata_size = sizeof(i); + f81504_serial_cell.platform_data = &i; + + status = mfd_add_devices(&dev->dev, PLATFORM_DEVID_AUTO, + &f81504_serial_cell, 1, NULL, dev->irq, + NULL); + if (status) { + dev_warn(&dev->dev, "%s: add device failed: %d\n", + __func__, status); + return status; + } + } + + return 0; +} + +static int f81504_prepage_gpiolib(struct pci_dev *dev) +{ + size_t i; + int status; + struct f81504_pci_private *priv = pci_get_drvdata(dev); + struct mfd_cell f81504_gpio_cell = { + .name = F81504_GPIO_NAME, + }; + + for (i = 0; i < ARRAY_SIZE(fintek_gpio_mapping); ++i) { + if (!(priv->gpio_en & BIT(i))) + continue; + + f81504_gpio_cell.pdata_size = sizeof(i); + f81504_gpio_cell.platform_data = &i; + + status = mfd_add_devices(&dev->dev, PLATFORM_DEVID_AUTO, + &f81504_gpio_cell, 1, NULL, dev->irq, + NULL); + if (status) { + dev_warn(&dev->dev, "%s: add device failed: %d\n", + __func__, status); + return status; + } + } + + return 0; +} + +static int f81504_probe(struct pci_dev *dev, const struct pci_device_id + *dev_id) +{ + int status; + u8 tmp; + struct f81504_pci_private *priv; + + status = pci_enable_device(dev); + if (status) + return status; + + /* Init PCI Configuration Space */ + status = f81504_port_init(dev); + if (status) + return status; + + priv = devm_kzalloc(&dev->dev, sizeof(struct f81504_pci_private), + GFP_KERNEL); + if (!priv) + return -ENOMEM; + + /* Save the GPIO_ENABLE_REG after f81504_port_init() for future use */ + pci_read_config_byte(dev, F81504_GPIO_ENABLE_REG, &priv->gpio_en); + + /* Save GPIO IO Addr to private data */ + pci_read_config_byte(dev, F81504_GPIO_IO_MSB_REG, &tmp); + priv->gpio_ioaddr = tmp << 8; + pci_read_config_byte(dev, F81504_GPIO_IO_LSB_REG, &tmp); + priv->gpio_ioaddr |= tmp; + + pci_set_drvdata(dev, priv); + + /* Generate UART Ports */ + status = f81504_prepage_serial_port(dev, dev_id->driver_data); + if (status) + goto failed; + + /* Generate GPIO Sets */ + status = f81504_prepage_gpiolib(dev); + if (status) + goto failed; + + return 0; + +failed: + mfd_remove_devices(&dev->dev); + pci_disable_device(dev); + return status; +} + +static void f81504_remove(struct pci_dev *dev) +{ + mfd_remove_devices(&dev->dev); + pci_disable_device(dev); +} + +#ifdef CONFIG_PM_SLEEP +static int f81504_suspend(struct device *dev) +{ + return 0; +} + +static int f81504_resume(struct device *dev) +{ + int status; + struct pci_dev *pdev = to_pci_dev(dev); + + status = pci_enable_device(pdev); + if (status) + return status; + + /* Re-init PCI Configuration Space */ + status = f81504_port_init(pdev); + if (status) + return status; + + return 0; +} +#endif + +static const struct pci_device_id f81504_dev_table[] = { + /* Fintek PCI serial cards */ + {PCI_DEVICE(FINTEK_VID, FINTEK_F81504), .driver_data = 4}, + {PCI_DEVICE(FINTEK_VID, FINTEK_F81508), .driver_data = 8}, + {PCI_DEVICE(FINTEK_VID, FINTEK_F81512), .driver_data = 12}, + {} +}; + +static SIMPLE_DEV_PM_OPS(f81504_pm_ops, f81504_suspend, f81504_resume); + +static struct pci_driver f81504_driver = { + .name = F81504_DRIVER_NAME, + .probe = f81504_probe, + .remove = f81504_remove, + .driver = { + .pm = &f81504_pm_ops, + .owner = THIS_MODULE, + }, + .id_table = f81504_dev_table, +}; + +module_pci_driver(f81504_driver); + +MODULE_DEVICE_TABLE(pci, f81504_dev_table); +MODULE_DESCRIPTION(F81504_DEV_DESC); +MODULE_AUTHOR("Peter Hong <Peter_Hong@fintek.com.tw>"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/mfd/f81504.h b/include/linux/mfd/f81504.h new file mode 100644 index 0000000..13bd0ae --- /dev/null +++ b/include/linux/mfd/f81504.h @@ -0,0 +1,52 @@ +#ifndef __F81504_H__ +#define __F81504_H__ + +#define FINTEK_VID 0x1c29 +#define FINTEK_F81504 0x1104 +#define FINTEK_F81508 0x1108 +#define FINTEK_F81512 0x1112 + +#define FINTEK_MAX_PORT 12 +#define FINTEK_GPIO_NAME_LEN 32 +#define FINTEK_GPIO_DISPLAY "GPIO" + +#define F81504_UART_START_ADDR 0x40 +#define F81504_UART_MODE_OFFSET 0x07 +#define F81504_UART_OFFSET 0x08 + +/* RTS will control by MCR if this bit is 0 */ +#define F81504_RTS_CONTROL_BY_HW BIT(4) +/* only worked with FINTEK_RTS_CONTROL_BY_HW on */ +#define F81504_RTS_INVERT BIT(5) + +#define F81504_CLOCK_RATE_MASK 0xc0 +#define F81504_CLKSEL_1DOT846_MHZ 0x00 +#define F81504_CLKSEL_18DOT46_MHZ 0x40 +#define F81504_CLKSEL_24_MHZ 0x80 +#define F81504_CLKSEL_14DOT77_MHZ 0xc0 + +#define F81504_IRQSEL_REG 0xb8 + +#define F81504_GPIO_ENABLE_REG 0xf0 +#define F81504_GPIO_IO_LSB_REG 0xf1 +#define F81504_GPIO_IO_MSB_REG 0xf2 +#define F81504_GPIO_MODE_REG 0xf3 + +#define F81504_GPIO_START_ADDR 0xf8 +#define F81504_GPIO_OUT_EN_OFFSET 0x00 +#define F81504_GPIO_DRIVE_EN_OFFSET 0x01 +#define F81504_GPIO_SET_OFFSET 0x08 + +#define F81504_GPIO_NAME "f81504_gpio" +#define F81504_SERIAL_NAME "f81504_serial" +#define F81504_MAX_GPIO_CNT 6 + +extern const u8 fintek_gpio_mapping[F81504_MAX_GPIO_CNT]; + +struct f81504_pci_private { + int line[FINTEK_MAX_PORT]; + u8 gpio_en; + u16 gpio_ioaddr; + u32 uart_count, gpio_count; +}; +#endif
The Fintek F81504/508/512 had implemented the basic serial port function in 8250_pci.c. We try to implement high baudrate & GPIOLIB with a spilt file 8250_f81504.c, but it seems too complex to add GPIOLIB. Alan & Andy recommend us to rewrite and spilt our driver with MFD architecture. https://lkml.org/lkml/2016/1/19/288 This driver is core driver for F81504/508/512, it'll handle the generation of UART/GPIO platform device and initialize PCIE configuration space when probe()/resume(). IC function list: F81504: Max 2x8 GPIOs and max 4 serial ports port2/3 are multi-function F81508: Max 6x8 GPIOs and max 8 serial ports port2/3 are multi-function, port8/9/10/11 are gpio only F81512: Max 6x8 GPIOs and max 12 serial ports port2/3/8/9/10/11 are multi-function H/W provider could changes the PCI configure space F0/F3h values in EEPROM or ASL code to change mode. F0h bit0~5: Enable GPIO0~5 bit6~7: Reserve F3h bit0~5: Multi-Functional Flag (0:GPIO/1:UART) bit0: UART2 pin out for UART2 / GPIO0 bit1: UART3 pin out for UART3 / GPIO1 bit2: UART8 pin out for UART8 / GPIO2 bit3: UART9 pin out for UART9 / GPIO3 bit4: UART10 pin out for UART10 / GPIO4 bit5: UART11 pin out for UART11 / GPIO5 bit6~7: Reserve Suggested-by: One Thousand Gnomes <gnomes@lxorguk.ukuu.org.uk> Suggested-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com> Signed-off-by: Peter Hung <hpeter+linux_kernel@gmail.com> --- drivers/mfd/Kconfig | 11 ++ drivers/mfd/Makefile | 2 + drivers/mfd/f81504-core.c | 331 +++++++++++++++++++++++++++++++++++++++++++++ include/linux/mfd/f81504.h | 52 +++++++ 4 files changed, 396 insertions(+) create mode 100644 drivers/mfd/f81504-core.c create mode 100644 include/linux/mfd/f81504.h