ns16550: enable Pericom controller support Other than the controllers supported so far, multiple port Pericom boards map all of their ports via BAR0, which requires a number of adjustments: Instead of tracking "max_bars" we now flag whether all ports use BAR0, and whether to expect a port-I/O or MMIO resource. As a result pci_uart_config() now gets handed a port index, which it then maps into a BAR index or an offset into BAR0 depending on the bar0 flag. Signed-off-by: Jan Beulich --- a/xen/drivers/char/ns16550.c +++ b/xen/drivers/char/ns16550.c @@ -78,10 +78,20 @@ static struct ns16550 { #endif } ns16550_com[2] = { { 0 } }; -struct ns16550_config_mmio { +#ifdef CONFIG_HAS_PCI +struct ns16550_config { u16 vendor_id; u16 dev_id; - unsigned int param; + enum { + param_default, /* Must not be referenced by any table entry. */ + param_trumanage, + param_oxford, + param_oxford_2port, + param_pericom_1port, + param_pericom_2port, + param_pericom_4port, + param_pericom_8port, + } param; }; /* Defining uart config options for MMIO devices */ @@ -90,57 +100,91 @@ struct ns16550_config_param { unsigned int reg_width; unsigned int fifo_size; u8 lsr_mask; - unsigned int max_bars; + bool_t mmio; + bool_t bar0; + unsigned int max_ports; unsigned int base_baud; unsigned int uart_offset; unsigned int first_offset; }; - -#ifdef CONFIG_HAS_PCI -enum { - param_default = 0, - param_trumanage, - param_oxford, - param_oxford_2port, -}; /* - * Create lookup tables for specific MMIO devices.. - * It is assumed that if the device found is MMIO, - * then you have indexed it here. Else, the driver - * does nothing. + * Create lookup tables for specific devices. It is assumed that if + * the device found is MMIO, then you have indexed it here. Else, the + * driver does nothing for MMIO based devices. */ static const struct ns16550_config_param __initconst uart_param[] = { - [param_default] = { }, /* Ignored. */ + [param_default] = { + .reg_width = 1, + .lsr_mask = UART_LSR_THRE, + .max_ports = 1, + }, [param_trumanage] = { .reg_shift = 2, .reg_width = 1, .fifo_size = 16, .lsr_mask = (UART_LSR_THRE | UART_LSR_TEMT), - .max_bars = 1, + .mmio = 1, + .max_ports = 1, }, [param_oxford] = { .base_baud = 4000000, .uart_offset = 0x200, .first_offset = 0x1000, .reg_width = 1, - .reg_shift = 0, .fifo_size = 16, .lsr_mask = UART_LSR_THRE, - .max_bars = 1, /* It can do more, but we would need more custom code.*/ + .mmio = 1, + .max_ports = 1, /* It can do more, but we would need more custom code.*/ }, [param_oxford_2port] = { .base_baud = 4000000, .uart_offset = 0x200, .first_offset = 0x1000, .reg_width = 1, - .reg_shift = 0, .fifo_size = 16, .lsr_mask = UART_LSR_THRE, - .max_bars = 2, + .mmio = 1, + .max_ports = 2, + }, + [param_pericom_1port] = { + .base_baud = 921600, + .uart_offset = 8, + .reg_width = 1, + .fifo_size = 16, + .lsr_mask = UART_LSR_THRE, + .bar0 = 1, + .max_ports = 1, + }, + [param_pericom_2port] = { + .base_baud = 921600, + .uart_offset = 8, + .reg_width = 1, + .fifo_size = 16, + .lsr_mask = UART_LSR_THRE, + .bar0 = 1, + .max_ports = 2, + }, + [param_pericom_4port] = { + .base_baud = 921600, + .uart_offset = 8, + .reg_width = 1, + .fifo_size = 16, + .lsr_mask = UART_LSR_THRE, + .bar0 = 1, + .max_ports = 4, + }, + [param_pericom_8port] = { + .base_baud = 921600, + .uart_offset = 8, + .reg_width = 1, + .fifo_size = 16, + .lsr_mask = UART_LSR_THRE, + .bar0 = 1, + .max_ports = 8, } }; -static const struct ns16550_config_mmio __initconst uart_config[] = +static const struct ns16550_config __initconst uart_config[] = { /* Broadcom TruManage device */ { @@ -339,6 +383,30 @@ static const struct ns16550_config_mmio .vendor_id = PCI_VENDOR_ID_OXSEMI, .dev_id = 0xc4cf, .param = param_oxford, + }, + /* Pericom PI7C9X7951 Uno UART */ + { + .vendor_id = PCI_VENDOR_ID_PERICOM, + .dev_id = 0x7951, + .param = param_pericom_1port + }, + /* Pericom PI7C9X7952 Duo UART */ + { + .vendor_id = PCI_VENDOR_ID_PERICOM, + .dev_id = 0x7952, + .param = param_pericom_2port + }, + /* Pericom PI7C9X7954 Quad UART */ + { + .vendor_id = PCI_VENDOR_ID_PERICOM, + .dev_id = 0x7954, + .param = param_pericom_4port + }, + /* Pericom PI7C9X7958 Octal UART */ + { + .vendor_id = PCI_VENDOR_ID_PERICOM, + .dev_id = 0x7958, + .param = param_pericom_8port } }; #endif @@ -629,7 +697,8 @@ static void __init ns16550_init_postirq( uart->ps_bdf[2])); else { - if ( rangeset_add_range(mmio_ro_ranges, + if ( uart->param->mmio && + rangeset_add_range(mmio_ro_ranges, uart->io_base, uart->io_base + uart->io_size - 1) ) printk(XENLOG_INFO "Error while adding MMIO range of device to mmio_ro_ranges\n"); @@ -830,12 +899,11 @@ static int __init check_existence(struct #ifdef CONFIG_HAS_PCI static int __init -pci_uart_config(struct ns16550 *uart, bool_t skip_amt, unsigned int bar_idx) +pci_uart_config(struct ns16550 *uart, bool_t skip_amt, unsigned int idx) { u64 orig_base = uart->io_base; unsigned int b, d, f, nextf, i; - uart->io_base = 0; /* NB. Start at bus 1 to avoid AMT: a plug-in card cannot be on bus 0. */ for ( b = skip_amt ? 1 : 0; b < 0x100; b++ ) { @@ -843,8 +911,10 @@ pci_uart_config(struct ns16550 *uart, bo { for ( f = 0; f < 8; f = nextf ) { + unsigned int bar_idx = 0, port_idx = idx; uint32_t bar, bar_64 = 0, len, len_64; - u64 size; + u64 size = 0; + const struct ns16550_config_param *param = uart_param; nextf = (f || (pci_conf_read16(0, b, d, f, PCI_HEADER_TYPE) & 0x80)) ? f + 1 : 8; @@ -863,15 +933,38 @@ pci_uart_config(struct ns16550 *uart, bo continue; } + /* Check for params in uart_config lookup table */ + for ( i = 0; i < ARRAY_SIZE(uart_config); i++) + { + u16 vendor = pci_conf_read16(0, b, d, f, PCI_VENDOR_ID); + u16 device = pci_conf_read16(0, b, d, f, PCI_DEVICE_ID); + + if ( uart_config[i].vendor_id == vendor && + uart_config[i].dev_id == device ) + { + param += uart_config[i].param; + if ( !param->bar0 ) + { + bar_idx = idx; + port_idx = 0; + } + break; + } + } + + if ( port_idx >= param->max_ports ) + { + idx -= param->max_ports; + continue; + } + + uart->io_base = 0; bar = pci_conf_read32(0, b, d, f, PCI_BASE_ADDRESS_0 + bar_idx*4); /* MMIO based */ - if ( !(bar & PCI_BASE_ADDRESS_SPACE_IO) ) + if ( param->mmio && !(bar & PCI_BASE_ADDRESS_SPACE_IO) ) { - u16 vendor = pci_conf_read16(0, b, d, f, PCI_VENDOR_ID); - u16 device = pci_conf_read16(0, b, d, f, PCI_DEVICE_ID); - pci_conf_write32(0, b, d, f, PCI_BASE_ADDRESS_0 + bar_idx*4, ~0u); len = pci_conf_read32(0, b, d, f, PCI_BASE_ADDRESS_0 + bar_idx*4); @@ -895,56 +988,11 @@ pci_uart_config(struct ns16550 *uart, bo else size = len & PCI_BASE_ADDRESS_MEM_MASK; - size &= -size; - - /* Check for params in uart_config lookup table */ - for ( i = 0; i < ARRAY_SIZE(uart_config); i++) - { - const struct ns16550_config_param *param; - - if ( uart_config[i].vendor_id != vendor ) - continue; - - if ( uart_config[i].dev_id != device ) - continue; - - param = uart_param + uart_config[i].param; - - /* - * Force length of mmio region to be at least - * 8 bytes times (1 << reg_shift) - */ - if ( size < (0x8 * (1 << param->reg_shift)) ) - continue; - - if ( bar_idx >= param->max_bars ) - continue; - - uart->param = param; - - if ( param->fifo_size ) - uart->fifo_size = param->fifo_size; - - uart->reg_shift = param->reg_shift; - uart->reg_width = param->reg_width; - uart->lsr_mask = param->lsr_mask; - uart->io_base = ((u64)bar_64 << 32) | - (bar & PCI_BASE_ADDRESS_MEM_MASK); - uart->io_base += param->first_offset; - uart->io_base += bar_idx * param->uart_offset; - if ( param->base_baud ) - uart->clock_hz = param->base_baud * 16; - size = max(8U << param->reg_shift, - param->uart_offset); - break; - } - - /* If we have an io_base, then we succeeded in the lookup */ - if ( !uart->io_base ) - continue; + uart->io_base = ((u64)bar_64 << 32) | + (bar & PCI_BASE_ADDRESS_MEM_MASK); } /* IO based */ - else + else if ( !param->mmio && (bar & PCI_BASE_ADDRESS_SPACE_IO) ) { pci_conf_write32(0, b, d, f, PCI_BASE_ADDRESS_0 + bar_idx*4, ~0u); @@ -952,22 +1000,45 @@ pci_uart_config(struct ns16550 *uart, bo pci_conf_write32(0, b, d, f, PCI_BASE_ADDRESS_0 + bar_idx*4, bar); size = len & PCI_BASE_ADDRESS_IO_MASK; - size &= -size; - - /* Not at least 8 bytes */ - if ( size < 8 ) - continue; uart->io_base = bar & ~PCI_BASE_ADDRESS_SPACE_IO; } + /* If we have an io_base, then we succeeded in the lookup. */ + if ( !uart->io_base ) + continue; + + size &= -size; + + /* + * Require length of actually used region to be at least + * 8 bytes times (1 << reg_shift). + */ + if ( size < param->first_offset + + port_idx * param->uart_offset + + (8 << param->reg_shift) ) + continue; + + uart->param = param; + + uart->reg_shift = param->reg_shift; + uart->reg_width = param->reg_width; + uart->lsr_mask = param->lsr_mask; + uart->io_base += param->first_offset + + port_idx * param->uart_offset; + if ( param->base_baud ) + uart->clock_hz = param->base_baud * 16; + if ( param->fifo_size ) + uart->fifo_size = param->fifo_size; + uart->ps_bdf[0] = b; uart->ps_bdf[1] = d; uart->ps_bdf[2] = f; uart->bar_idx = bar_idx; uart->bar = bar; uart->bar64 = bar_64; - uart->io_size = size; + uart->io_size = max(8U << param->reg_shift, + param->uart_offset); uart->irq = pci_conf_read8(0, b, d, f, PCI_INTERRUPT_PIN) ? pci_conf_read8(0, b, d, f, PCI_INTERRUPT_LINE) : 0; --- a/xen/include/xen/pci_ids.h +++ b/xen/include/xen/pci_ids.h @@ -2,6 +2,8 @@ #define PCI_VENDOR_ID_NVIDIA 0x10de +#define PCI_VENDOR_ID_PERICOM 0x12d8 + #define PCI_VENDOR_ID_OXSEMI 0x1415 #define PCI_VENDOR_ID_BROADCOM 0x14e4