diff options
author | Michał Żygowski <michal.zygowski@3mdeb.com> | 2020-04-13 20:51:32 +0200 |
---|---|---|
committer | Michał Żygowski <michal.zygowski@3mdeb.com> | 2020-05-16 17:37:29 +0000 |
commit | fba08308f086d7b77f95554df094288fd55903d1 (patch) | |
tree | bbe7381c3b59178f9e830632755ee5dc27e91f53 /src/superio/smsc/sch5545/superio.c | |
parent | 6191b85f8770ed807a5954339aef6c99609d7660 (diff) |
superio/smsc/sch5545: add support for SMSC SCH5545
The SMSC SCH5545 is very similar to the publicly available
datasheet for the SCH5627.
TEST=use PS2 keyboard and mouse, serial port, runtime registers and
Embedded Memory Interface on Dell Optiplex 9010
Signed-off-by: Michał Żygowski <michal.zygowski@3mdeb.com>
Change-Id: If8a60d5802675f09b08014ed583d2d8afa29fc04
Reviewed-on: https://review.coreboot.org/c/coreboot/+/40350
Reviewed-by: Felix Held <felix-coreboot@felixheld.de>
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Diffstat (limited to 'src/superio/smsc/sch5545/superio.c')
-rw-r--r-- | src/superio/smsc/sch5545/superio.c | 301 |
1 files changed, 301 insertions, 0 deletions
diff --git a/src/superio/smsc/sch5545/superio.c b/src/superio/smsc/sch5545/superio.c new file mode 100644 index 0000000000..b6e5308f3c --- /dev/null +++ b/src/superio/smsc/sch5545/superio.c @@ -0,0 +1,301 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include <arch/io.h> +#include <device/device.h> +#include <device/pnp.h> +#include <superio/conf_mode.h> +#include <console/console.h> +#include <pc80/keyboard.h> +#include <stdlib.h> + +#include "sch5545.h" + +int sch5545_get_gpio(uint8_t sio_port, uint8_t gpio) +{ + struct device *dev; + uint16_t runtime_reg_base; + uint8_t gpio_bank, gpio_num; + + gpio_bank = gpio / 10; + gpio_num = gpio % 10; + /* + * GPIOs are divided into banks of 8 GPIOs (kind of). Each group starts + * at decimal base, i.e. 8 GPIOs from GPIO000, 8 GPIOs from GPIO010, + * etc., up to GPIO071 and GPIO072 which are an exception (only two + * gpios in the bank 7). + */ + if (gpio_num > 7) + return -1; + else if (gpio_bank == 7 && gpio_num > 1) + return -1; + else if (gpio_bank > 7) + return -1; + + dev = dev_find_slot_pnp(sio_port, SCH5545_LDN_LPC); + + if (!dev) { + printk(BIOS_ERR, "%s: ERROR: LPC interface LDN not present." + "Check the devicetree!\n", __func__); + return -1; + } + + pnp_enter_conf_mode(dev); + pnp_set_logical_device(dev); + + runtime_reg_base = pnp_read_config(dev, SCH5545_BAR_RUNTIME_REG + 2); + runtime_reg_base |= pnp_read_config(dev, SCH5545_BAR_RUNTIME_REG + 3) << 8; + + pnp_exit_conf_mode(dev); + + if (runtime_reg_base == 0) + return -1; + + outb(gpio_bank * 8 + gpio_num, runtime_reg_base + SCH5545_RR_GPIO_SEL); + + return inb(runtime_reg_base + SCH5545_RR_GPIO_READ) & 1; +} + +static void sch5545_init(struct device *dev) +{ + if (!dev->enabled) + return; + + switch (dev->path.pnp.device) { + case SCH5545_LDN_KBC: + pc_keyboard_init(NO_AUX_DEVICE); + break; + case SCH5545_LDN_LPC: + pnp_enter_conf_mode(dev); + pnp_set_logical_device(dev); + /* Enable SERIRQ */ + pnp_write_config(dev, 0x24, pnp_read_config(dev, 0x24) | 0x04); + pnp_exit_conf_mode(dev); + break; + } +} + +static void sch5545_set_iobase(struct device *dev, u8 index, u16 iobase) +{ + u8 val; + struct device *lpc_if; + u8 iobase_reg = 0; + + lpc_if = dev_find_slot_pnp(dev->path.pnp.port, SCH5545_LDN_LPC); + + if (!lpc_if) { + printk(BIOS_ERR, "ERROR: %s LPC interface LDN not present." + "Check the devicetree!\n", dev_path(dev)); + return; + } + + switch (dev->path.pnp.device) { + case SCH5545_LDN_EMI: + iobase_reg = SCH5545_BAR_EM_IF; + break; + case SCH5545_LDN_KBC: + iobase_reg = SCH5545_BAR_KBC; + break; + case SCH5545_LDN_UART1: + iobase_reg = SCH5545_BAR_UART1; + break; + case SCH5545_LDN_UART2: + iobase_reg = SCH5545_BAR_UART2; + break; + case SCH5545_LDN_RR: + iobase_reg = SCH5545_BAR_RUNTIME_REG; + break; + case SCH5545_LDN_FDC: + iobase_reg = SCH5545_BAR_FLOPPY; + break; + case SCH5545_LDN_LPC: + iobase_reg = SCH5545_BAR_LPC_IF; + break; + case SCH5545_LDN_PP: + iobase_reg = SCH5545_BAR_PARPORT; + break; + default: + return; + } + + pnp_set_logical_device(lpc_if); + + /* Flip the bytes in IO base, LSB goes first */ + pnp_write_config(lpc_if, iobase_reg + 2, iobase & 0xff); + pnp_write_config(lpc_if, iobase_reg + 3, (iobase >> 8) & 0xff); + + /* Set valid bit */ + val = pnp_read_config(lpc_if, iobase_reg + 1); + val |= 0x80; + pnp_write_config(lpc_if, iobase_reg + 1, val); + + pnp_set_logical_device(dev); +} + +static void sch5545_set_irq(struct device *dev, u8 index, u8 irq) +{ + u8 select_bit = 0; + struct device *lpc_if; + + /* In case it is not the IRQ, write misc register directly */ + if (index >= PNP_IDX_MSC0) { + pnp_write_config(dev, index, irq); + return; + } + + lpc_if = dev_find_slot_pnp(dev->path.pnp.port, SCH5545_LDN_LPC); + + if (!lpc_if) { + printk(BIOS_ERR, "ERROR: %s LPC interface LDN not present." + "Check the devicetree!\n", dev_path(dev)); + return; + } + + pnp_set_logical_device(lpc_if); + + /* + * Some LDNs can generate IRQs from two sources, i.e. + * - EMI may generate interrupts for Mailbox and INT source register + * - KBC may generate separate IRQ for mouse and keyboard, + * - RR LDN may generate IRQ for PME and SMI etc. + * SELECT bit allows to distinguish IRQ source for single LDN. + * Use the standard IRQs for devices. + */ + switch (dev->path.pnp.device) { + case SCH5545_LDN_EMI: + case SCH5545_LDN_KBC: + case SCH5545_LDN_RR: + if (index == 0x72) + select_bit = 0x80; + break; + default: + break; + } + + /* + * IRQs are set in a little different manner. Each IRQ number has its + * own register which is programmed with LDN number which should use + * the IRQ. Ignore the index offset and choose register based on IRQ + * number counting from IRQ base. + */ + pnp_write_config(lpc_if, SCH5545_IRQ_BASE + irq, dev->path.pnp.device | select_bit); + pnp_set_logical_device(dev); +} + +static void sch5545_set_drq(struct device *dev, u8 index, u8 drq) +{ + struct device *lpc_if; + + if (drq == 4) { + printk(BIOS_ERR, "ERROR: %s %02x: Trying to set reserved DMA channel 4!\n", + dev_path(dev), index); + printk(BIOS_ERR, "This configuration is untested. Trying to continue.\n"); + } + + /* DMA channel is programmed via LPC LDN */ + lpc_if = dev_find_slot_pnp(dev->path.pnp.port, SCH5545_LDN_LPC); + + if (!lpc_if) { + printk(BIOS_ERR, "ERROR: %s LPC interface LDN not present." + "Check the devicetree!\n", dev_path(dev)); + return; + } + + pnp_set_logical_device(lpc_if); + + /* + * There are 8 configurable DMA channels. DRQs are set in a little + * different manner. Each DMA channel has its own 16-bit register which + * is programmed with LDN number (in higher byte) which should use the + * IRQ. Ignore the index offset and choose register based on IRQ number + * counting from IRQ base. Set valid bit (bit 7) additionally. + */ + pnp_write_config(dev, SCH5545_DRQ_BASE + (drq * 2) + 1, dev->path.pnp.device | 0x80); + + pnp_set_logical_device(dev); +} + +static void sch5545_set_resource(struct device *dev, struct resource *resource) +{ + if (!(resource->flags & IORESOURCE_ASSIGNED)) { + /* + * The PNP_MSC super IO registers have the IRQ flag set. If no + * value is assigned in the devicetree, the corresponding + * PNP_MSC register doesn't get written, which should be printed + * as warning and not as error. + */ + if (resource->flags & IORESOURCE_IRQ && + (resource->index != PNP_IDX_IRQ0) && + (resource->index != PNP_IDX_IRQ1)) + printk(BIOS_WARNING, "WARNING: %s %02lx %s size: " + "0x%010llx not assigned\n", dev_path(dev), + resource->index, resource_type(resource), + resource->size); + else + printk(BIOS_ERR, "ERROR: %s %02lx %s size: 0x%010llx " + "not assigned\n", dev_path(dev), resource->index, + resource_type(resource), resource->size); + return; + } + + /* Now store the resource. */ + if (resource->flags & IORESOURCE_IO) { + sch5545_set_iobase(dev, resource->index, resource->base); + } else if (resource->flags & IORESOURCE_DRQ) { + sch5545_set_drq(dev, resource->index, resource->base); + } else if (resource->flags & IORESOURCE_IRQ) { + sch5545_set_irq(dev, resource->index, resource->base); + } else { + printk(BIOS_ERR, "ERROR: %s %02lx unknown resource type\n", + dev_path(dev), resource->index); + return; + } + resource->flags |= IORESOURCE_STORED; + + report_resource_stored(dev, resource, ""); +} + +static void sch5545_set_resources(struct device *dev) +{ + struct resource *res; + + pnp_enter_conf_mode(dev); + + /* Select the logical device (LDN). */ + pnp_set_logical_device(dev); + + for (res = dev->resource_list; res; res = res->next) + sch5545_set_resource(dev, res); + + pnp_exit_conf_mode(dev); +} + +static struct device_operations ops = { + .read_resources = pnp_read_resources, + .set_resources = sch5545_set_resources, + .enable_resources = pnp_enable_resources, + .enable = pnp_alt_enable, + .init = sch5545_init, + .ops_pnp_mode = &pnp_conf_mode_55_aa, +}; + +static struct pnp_info pnp_dev_info[] = { + { NULL, SCH5545_LDN_EMI, PNP_IO0 | PNP_IRQ0 | PNP_IRQ1, 0x0ff0 }, + { NULL, SCH5545_LDN_KBC, PNP_IO0 | PNP_IRQ0 | PNP_IRQ1 | PNP_MSC0 | PNP_MSC1, 0x0fff }, + { NULL, SCH5545_LDN_UART1, PNP_IO0 | PNP_IRQ0 | PNP_MSC0, 0x0ff8 }, + { NULL, SCH5545_LDN_UART2, PNP_IO0 | PNP_IRQ0 | PNP_MSC0, 0x0ff8 }, + { NULL, SCH5545_LDN_RR, PNP_IO0 | PNP_IRQ0 | PNP_IRQ1 | PNP_MSC0, 0x0fc0 }, + { NULL, SCH5545_LDN_FDC, PNP_IO0 | PNP_IRQ0 | PNP_DRQ0 | PNP_MSC0 | PNP_MSC1 | + PNP_MSC2 | PNP_MSC3 | PNP_MSC4 | PNP_MSC5, 0x0ff8, }, + { NULL, SCH5545_LDN_LPC, PNP_IO0, 0x0ffe }, + { NULL, SCH5545_LDN_PP, PNP_IO0 | PNP_IRQ0 | PNP_DRQ0 | PNP_MSC0 | PNP_MSC1, 0x0ff8 }, +}; + +static void enable_dev(struct device *dev) +{ + pnp_enable_devices(dev, &ops, ARRAY_SIZE(pnp_dev_info), pnp_dev_info); +} + +struct chip_operations superio_smsc_sch5545_ops = { + CHIP_NAME("SMSC SCH5545 Super I/O") + .enable_dev = enable_dev, +}; |