diff --git a/src/superio/smsc/Makefile.inc b/src/superio/smsc/Makefile.inc index 86cf9c510f..b5e79ba05f 100644 --- a/src/superio/smsc/Makefile.inc +++ b/src/superio/smsc/Makefile.inc @@ -13,3 +13,4 @@ subdirs-y += mec1308 subdirs-y += smscsuperio subdirs-y += sio1036 subdirs-y += sch5545 +subdirs-y += sch555x diff --git a/src/superio/smsc/sch555x/Kconfig b/src/superio/smsc/sch555x/Kconfig new file mode 100644 index 0000000000..63758571cd --- /dev/null +++ b/src/superio/smsc/sch555x/Kconfig @@ -0,0 +1,4 @@ +## SPDX-License-Identifier: GPL-2.0-only + +config SUPERIO_SMSC_SCH555x + bool diff --git a/src/superio/smsc/sch555x/Makefile.inc b/src/superio/smsc/sch555x/Makefile.inc new file mode 100644 index 0000000000..047fb5cf43 --- /dev/null +++ b/src/superio/smsc/sch555x/Makefile.inc @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only + +bootblock-$(CONFIG_SUPERIO_SMSC_SCH555x) += emi.c bootblock.c +ramstage-$(CONFIG_SUPERIO_SMSC_SCH555x) += ramstage.c diff --git a/src/superio/smsc/sch555x/bootblock.c b/src/superio/smsc/sch555x/bootblock.c new file mode 100644 index 0000000000..bae6e64f28 --- /dev/null +++ b/src/superio/smsc/sch555x/bootblock.c @@ -0,0 +1,70 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include +#include +#include "sch555x.h" + +static void pnp_enter_conf_state(pnp_devfn_t dev) +{ + unsigned int port = dev >> 8; + outb(0x55, port); +} + +static void pnp_exit_conf_state(pnp_devfn_t dev) +{ + unsigned int port = dev >> 8; + outb(0xaa, port); +} + +static void pnp_write_config32(pnp_devfn_t dev, uint8_t offset, uint32_t value) +{ + pnp_write_config(dev, offset, value & 0xff); + pnp_write_config(dev, offset + 1, (value >> 8) & 0xff); + pnp_write_config(dev, offset + 2, (value >> 16) & 0xff); + pnp_write_config(dev, offset + 3, (value >> 24) & 0xff); +} + +/* + * Do just enough init so that the motherboard specific magic EMI + * sequences can be sent before sch555x_enable_serial is called + */ +void sch555x_early_init(pnp_devfn_t global_dev) +{ + pnp_enter_conf_state(global_dev); + + // Enable IRQs + pnp_set_logical_device(global_dev); + pnp_write_config(global_dev, SCH555x_DEVICE_MODE, 0x04); + + // Map EMI and runtime registers + pnp_devfn_t lpci_dev = PNP_DEV(global_dev >> 8, SCH555x_LDN_LPCI); + + pnp_set_logical_device(lpci_dev); + pnp_write_config32(lpci_dev, SCH555x_LPCI_EMI_BAR, + (SCH555x_EMI_IOBASE << 16) | 0x800f); + pnp_write_config32(lpci_dev, SCH555x_LPCI_RUNTIME_BAR, + (SCH555x_RUNTIME_IOBASE << 16) | 0x8a3f); + + pnp_exit_conf_state(global_dev); +} + +void sch555x_enable_serial(pnp_devfn_t uart_dev, uint16_t serial_iobase) +{ + pnp_enter_conf_state(uart_dev); + + // Set LPCI BAR register to map UART into I/O space + pnp_devfn_t lpci_dev = PNP_DEV(uart_dev >> 8, SCH555x_LDN_LPCI); + + pnp_set_logical_device(lpci_dev); + u8 uart_bar = (uart_dev & 0xff) == SCH555x_LDN_UART1 + ? SCH555x_LPCI_UART1_BAR + : SCH555x_LPCI_UART2_BAR; + pnp_write_config32(lpci_dev, uart_bar, serial_iobase << 16 | 0x8707); + + // Set up the UART's configuration registers + pnp_set_logical_device(uart_dev); + pnp_set_enable(uart_dev, 1); // Activate + pnp_write_config(uart_dev, 0x0f, 0x02); // Config select + + pnp_exit_conf_state(uart_dev); +} diff --git a/src/superio/smsc/sch555x/emi.c b/src/superio/smsc/sch555x/emi.c new file mode 100644 index 0000000000..7b3b204181 --- /dev/null +++ b/src/superio/smsc/sch555x/emi.c @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include +#include +#include "sch555x.h" + +uint8_t sch555x_emi_read8(uint16_t addr) +{ + outw(addr | 0x8000, SCH555x_EMI_IOBASE + 2); + return inb(SCH555x_EMI_IOBASE + 4); +} + +uint16_t sch555x_emi_read16(uint16_t addr) +{ + outw(addr | 0x8001, SCH555x_EMI_IOBASE + 2); + return inw(SCH555x_EMI_IOBASE + 4); +} + +uint32_t sch555x_emi_read32(uint16_t addr) +{ + outw(addr | 0x8002, SCH555x_EMI_IOBASE + 2); + return inl(SCH555x_EMI_IOBASE + 4); +} + +void sch555x_emi_write8(uint16_t addr, uint8_t val) +{ + outw(addr | 0x8000, SCH555x_EMI_IOBASE + 2); + outb(val, SCH555x_EMI_IOBASE + 4); +} + +void sch555x_emi_write16(uint16_t addr, uint16_t val) +{ + outw(addr | 0x8001, SCH555x_EMI_IOBASE + 2); + outw(val, SCH555x_EMI_IOBASE + 4); +} + +void sch555x_emi_write32(uint16_t addr, uint32_t val) +{ + outw(addr | 0x8002, SCH555x_EMI_IOBASE + 2); + outl(val, SCH555x_EMI_IOBASE + 4); +} diff --git a/src/superio/smsc/sch555x/ramstage.c b/src/superio/smsc/sch555x/ramstage.c new file mode 100644 index 0000000000..2245001af2 --- /dev/null +++ b/src/superio/smsc/sch555x/ramstage.c @@ -0,0 +1,141 @@ +;;/* SPDX-License-Identifier: GPL-2.0-only */ + +#include +#include +#include +#include +#include +#include "sch555x.h" + +static void sch555x_init(struct device *dev) +{ + if (dev->enabled && dev->path.pnp.device == SCH555x_LDN_8042) + pc_keyboard_init(NO_AUX_DEVICE); +} + +static uint8_t sch555x_ldn_to_bar(uint8_t ldn) +{ + switch (ldn) { + case SCH555x_LDN_LPCI: + return SCH555x_LPCI_LPCI_BAR; + case SCH555x_LDN_EMI: + return SCH555x_LPCI_EMI_BAR; + case SCH555x_LDN_UART1: + return SCH555x_LPCI_UART1_BAR; + case SCH555x_LDN_UART2: + return SCH555x_LPCI_UART2_BAR; + case SCH555x_LDN_RUNTIME: + return SCH555x_LPCI_RUNTIME_BAR; + case SCH555x_LDN_8042: + return SCH555x_LPCI_8042_BAR; + case SCH555x_LDN_FDC: + return SCH555x_LPCI_FDC_BAR; + case SCH555x_LDN_PP: + return SCH555x_LPCI_PP_BAR; + default: + return 0; + } +} + +/* + * IO BARs don't live in normal LDN configuration space but in the LPC interface. + * Thus we ignore the index and choose what BAR to set just based on the LDN. + */ +static void sch555x_set_iobase(struct device *lpci, struct device *dev, + uint8_t index, uint16_t iobase) +{ + const uint8_t bar = sch555x_ldn_to_bar(dev->path.pnp.device); + if (bar) { + pnp_set_logical_device(lpci); + pnp_unset_and_set_config(lpci, bar + 1, 0, 1 << 7); + pnp_write_config(lpci, bar + 2, iobase & 0xff); + pnp_write_config(lpci, bar + 3, (iobase >> 8) & 0xff); + } +} + +/* + * IRQs don't live in normal LDN configuration space but in the LPC interface. + * + * The following fake offsets are used: + * 0x70 => First IRQ + * 0x72 => Second IRQ + */ +static void sch555x_set_irq(struct device *lpci, struct device *dev, + uint8_t index, uint8_t irq) +{ + if (index >= PNP_IDX_MSC0) { + pnp_set_logical_device(dev); + pnp_write_config(dev, index, irq); + return; + } + + pnp_set_logical_device(lpci); + switch (index) { + case 0x70: + pnp_write_config(lpci, SCH555x_LPCI_IRQ(irq), dev->path.pnp.device); + break; + case 0x72: + pnp_write_config(lpci, SCH555x_LPCI_IRQ(irq), dev->path.pnp.device | 0x80); + break; + } +} + +/* + * DMA channels don't live in normal LDN configuration space but in the LPC interface. + */ +static void sch555x_set_drq(struct device *lpci, struct device *dev, + uint8_t index, uint8_t drq) +{ + pnp_set_logical_device(lpci); + pnp_write_config(lpci, SCH555x_LPCI_DMA(drq), dev->path.pnp.device | 0x80); +} + +static void sch555x_set_resources(struct device *dev) +{ + struct device *lpci = dev_find_slot_pnp(dev->path.pnp.port, SCH555x_LDN_LPCI); + if (!lpci) { + printk(BIOS_ERR, "SCH555x LPC interface not present in device tree!\n"); + return; + } + + pnp_enter_conf_mode(dev); + for (struct resource *res = dev->resource_list; res; res = res->next) { + if (res->flags & IORESOURCE_IO) + sch555x_set_iobase(lpci, dev, res->index, res->base); + else if (res->flags & IORESOURCE_DRQ) + sch555x_set_drq(lpci, dev, res->index, res->base); + else if (res->flags & IORESOURCE_IRQ) + sch555x_set_irq(lpci, dev, res->index, res->base); + } + pnp_exit_conf_mode(dev); +} + +static void sch555x_enable_dev(struct device *dev) +{ + static struct device_operations ops = { + .read_resources = pnp_read_resources, + .set_resources = sch555x_set_resources, + .enable_resources = pnp_enable_resources, + .enable = pnp_alt_enable, + .init = sch555x_init, + .ops_pnp_mode = &pnp_conf_mode_55_aa, + }; + + static struct pnp_info pnp_dev_info[] = { + { NULL, SCH555x_LDN_EMI, PNP_IO0 | PNP_IRQ0 | PNP_IRQ1, 0x0ff0 }, + { NULL, SCH555x_LDN_8042, PNP_IO0 | PNP_IRQ0 | PNP_IRQ1, 0x0fff }, + { NULL, SCH555x_LDN_UART1, PNP_IO0 | PNP_IRQ0 | PNP_MSC0, 0x0ff8 }, + { NULL, SCH555x_LDN_UART2, PNP_IO0 | PNP_IRQ0 | PNP_MSC0, 0x0ff8 }, + { NULL, SCH555x_LDN_LPCI, PNP_IO0, 0x0ffe }, + { NULL, SCH555x_LDN_RUNTIME, PNP_IO0 | PNP_IRQ0 | PNP_IRQ1, 0x0fc0 }, + { NULL, SCH555x_LDN_FDC, PNP_IO0 | PNP_IRQ0 | PNP_DRQ0, 0x0ff8, }, + { NULL, SCH555x_LDN_PP, PNP_IO0 | PNP_IRQ0 | PNP_DRQ0, 0x0ff8 }, + }; + + pnp_enable_devices(dev, &ops, ARRAY_SIZE(pnp_dev_info), pnp_dev_info); +} + +struct chip_operations superio_smsc_sch555x_ops = { + CHIP_NAME("SMSC SCH555x Super I/O") + .enable_dev = sch555x_enable_dev, +}; diff --git a/src/superio/smsc/sch555x/sch555x.h b/src/superio/smsc/sch555x/sch555x.h new file mode 100644 index 0000000000..4af95eca1c --- /dev/null +++ b/src/superio/smsc/sch555x/sch555x.h @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef SUPERIO_SCH555x_H +#define SUPERIO_SCH555x_H + +#include + +// Global registers +#define SCH555x_DEVICE_ID 0x20 +#define SCH555x_DEVICE_REV 0x21 +#define SCH555x_DEVICE_MODE 0x24 + +// Logical device numbers +#define SCH555x_LDN_EMI 0x00 +#define SCH555x_LDN_8042 0x01 +#define SCH555x_LDN_UART1 0x07 +#define SCH555x_LDN_UART2 0x08 +#define SCH555x_LDN_RUNTIME 0x0a +#define SCH555x_LDN_FDC 0x0b +#define SCH555x_LDN_LPCI 0x0c +#define SCH555x_LDN_PP 0x11 +#define SCH555x_LDN_GLOBAL 0x3f + +// LPC interface registers +#define SCH555x_LPCI_IRQ(i) (0x40 + (i)) +// DMA channel register is 2 bytes, we care about the second byte +#define SCH555x_LPCI_DMA(i) (0x50 + (i) * 2 + 1) +// BAR offset (inside LPCI) for each LDN +#define SCH555x_LPCI_LPCI_BAR 0x60 +#define SCH555x_LPCI_EMI_BAR 0x64 +#define SCH555x_LPCI_UART1_BAR 0x68 +#define SCH555x_LPCI_UART2_BAR 0x6c +#define SCH555x_LPCI_RUNTIME_BAR 0x70 +#define SCH555x_LPCI_8042_BAR 0x78 +#define SCH555x_LPCI_FDC_BAR 0x7c +#define SCH555x_LPCI_PP_BAR 0x80 + +// Runtime registers (in I/O space) +#define SCH555x_RUNTIME_PME_STS 0x00 +#define SCH555x_RUNTIME_PME_EN 0x01 +#define SCH555x_RUNTIME_PME_EN1 0x05 +#define SCH555x_RUNTIME_LED 0x25 +// NOTE: not in the SCH5627P datasheet but Dell's firmware writes to it +#define SCH555x_RUNTIME_UNK1 0x35 + +// Needed in the bootblock, thus we map them at a fixed address +#define SCH555x_EMI_IOBASE 0xa00 +#define SCH555x_RUNTIME_IOBASE 0xa40 + +/* + * EMI access + */ + +uint8_t sch555x_emi_read8(uint16_t addr); +uint16_t sch555x_emi_read16(uint16_t addr); +uint32_t sch555x_emi_read32(uint16_t addr); +void sch555x_emi_write8(uint16_t addr, uint8_t val); +void sch555x_emi_write16(uint16_t addr, uint16_t val); +void sch555x_emi_write32(uint16_t addr, uint32_t val); + +/* + * Bootblock entry points + */ + +void sch555x_early_init(pnp_devfn_t global_dev); +void sch555x_enable_serial(pnp_devfn_t uart_dev, uint16_t serial_iobase); + +#endif