aboutsummaryrefslogtreecommitdiff
path: root/src/superio/smsc
diff options
context:
space:
mode:
authorMate Kukri <kukri.mate@gmail.com>2021-06-06 14:00:57 +0100
committerFelix Held <felix-coreboot@felixheld.de>2023-12-01 17:40:11 +0000
commit62c25351c101a3d5c7104aa6fc34e71990478dde (patch)
tree16895ba4f9af99c1d5de2be49ee8cb7409023c9d /src/superio/smsc
parentb9523a4281a568afa62c4755f6b3e99b0924d64a (diff)
superio/smsc: Add support for the SCH555x series
Used by the OptiPlex 3020/7020/9020: - EMI and Runtime registers work - UART1 works (including IRQs) - PS/2 keyboard and mouse untested Signed-off-by: Mate Kukri <kukri.mate@gmail.com> Change-Id: I9323198f1139cd0c3dd37f977ae7693b721654f4 Reviewed-on: https://review.coreboot.org/c/coreboot/+/64359 Tested-by: build bot (Jenkins) <no-reply@coreboot.org> Reviewed-by: Martin L Roth <gaumless@gmail.com>
Diffstat (limited to 'src/superio/smsc')
-rw-r--r--src/superio/smsc/Makefile.inc1
-rw-r--r--src/superio/smsc/sch555x/Kconfig4
-rw-r--r--src/superio/smsc/sch555x/Makefile.inc4
-rw-r--r--src/superio/smsc/sch555x/bootblock.c70
-rw-r--r--src/superio/smsc/sch555x/emi.c41
-rw-r--r--src/superio/smsc/sch555x/ramstage.c141
-rw-r--r--src/superio/smsc/sch555x/sch555x.h68
7 files changed, 329 insertions, 0 deletions
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 <arch/io.h>
+#include <device/pnp_ops.h>
+#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 <arch/io.h>
+#include <device/pnp_ops.h>
+#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 <arch/io.h>
+#include <console/console.h>
+#include <device/pnp.h>
+#include <pc80/keyboard.h>
+#include <superio/conf_mode.h>
+#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 <types.h>
+
+// 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