summaryrefslogtreecommitdiff
path: root/src/superio/smsc/sch5545/superio.c
diff options
context:
space:
mode:
authorMichał Żygowski <michal.zygowski@3mdeb.com>2020-04-13 20:51:32 +0200
committerMichał Żygowski <michal.zygowski@3mdeb.com>2020-05-16 17:37:29 +0000
commitfba08308f086d7b77f95554df094288fd55903d1 (patch)
treebbe7381c3b59178f9e830632755ee5dc27e91f53 /src/superio/smsc/sch5545/superio.c
parent6191b85f8770ed807a5954339aef6c99609d7660 (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.c301
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,
+};