/* * This file is part of the coreboot project. * * Copyright (C) 2015 Intel Corp. * (Written by Alexandru Gagniuc for Intel Corp.) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include /* This list must be in order, from highest pad numbers, to lowest pad numbers*/ static const struct pad_community { uint16_t first_pad; uint8_t port; uint8_t num_gpi_regs; uint8_t gpi_offset; const char *grp_name; } gpio_communities[] = { { .port = GPIO_SW, .first_pad = SW_OFFSET, .num_gpi_regs = NUM_SW_GPI_REGS, .gpi_offset = 0, .grp_name = "GPIO_GPE_SW", }, { .port = GPIO_W, .first_pad = W_OFFSET, .num_gpi_regs = NUM_W_GPI_REGS, .gpi_offset = NUM_SW_GPI_REGS, .grp_name = "GPIO_GPE_W", }, { .port = GPIO_NW, .first_pad = NW_OFFSET, .num_gpi_regs = NUM_NW_GPI_REGS, .gpi_offset = NUM_W_GPI_REGS + NUM_SW_GPI_REGS, .grp_name = "GPIO_GPE_NW", }, { .port = GPIO_N, .first_pad = N_OFFSET, .num_gpi_regs = NUM_N_GPI_REGS, .gpi_offset = NUM_NW_GPI_REGS + NUM_W_GPI_REGS + NUM_SW_GPI_REGS, .grp_name = "GPIO_GPE_N", } }; static const struct pad_community *gpio_get_community(uint16_t pad) { const struct pad_community *map = gpio_communities; assert(pad < TOTAL_PADS); while (map->first_pad > pad) map++; return map; } static void gpio_configure_itss(const struct pad_config *cfg, uint16_t port, uint16_t pad_cfg_offset) { /* No ITSS configuration in SMM. */ if (ENV_SMM) return; int irq; /* Set up ITSS polarity if pad is routed to APIC. * * The ITSS takes only active high interrupt signals. Therefore, * if the pad configuration indicates an inversion assume the * intent is for the ITSS polarity. Before forwarding on the * request to the APIC there's an inversion setting for how the * signal is forwarded to the APIC. Honor the inversion setting * in the GPIO pad configuration so that a hardware active low * signal looks that way to the APIC (double inversion). */ if (!(cfg->config0 & PAD_CFG0_ROUTE_IOAPIC)) return; irq = iosf_read(port, pad_cfg_offset + sizeof(uint32_t)); irq &= PAD_CFG1_IRQ_MASK; if (!irq) { printk(BIOS_ERR, "GPIO %u doesn't support APIC routing,\n", cfg->pad); return; } itss_set_irq_polarity(irq, !!(cfg->config0 & PAD_CFG0_RX_POL_INVERT)); } static void gpio_configure_owner(const struct pad_config *cfg, uint16_t port, int pin) { uint32_t val; uint16_t hostsw_reg; /* The 4th bit in pad_config 1 (RO) is used to indicate if the pad * needs GPIO driver ownership. */ if (!(cfg->config1 & PAD_CFG1_GPIO_DRIVER)) return; /* Based on the gpio pin number configure the corresponding bit in * HOSTSW_OWN register. Value of 0x1 indicates GPIO Driver onwership. */ hostsw_reg = HOSTSW_OWN_REG_BASE + ((pin / 32) * sizeof(uint32_t)); val = iosf_read(port, hostsw_reg); val |= 1 << (pin % 32); iosf_write(port, hostsw_reg, val); } static void gpi_enable_smi(const struct pad_config *cfg, uint16_t port, int pin) { uint32_t value; uint16_t sts_reg; uint16_t en_reg; int group; if (((cfg->config0) & PAD_CFG0_ROUTE_SMI) != PAD_CFG0_ROUTE_SMI) return; group = pin / GPIO_MAX_NUM_PER_GROUP; sts_reg = GPI_SMI_STS_OFFSET(group); value = iosf_read(port, sts_reg); /* Write back 1 to reset the sts bits */ iosf_write(port, sts_reg, value); /* Set enable bits */ en_reg = GPI_SMI_EN_OFFSET(group); value = iosf_read(port, en_reg); value |= 1 << (pin % GPIO_MAX_NUM_PER_GROUP); iosf_write(port, en_reg, value); } void gpio_configure_pad(const struct pad_config *cfg) { uint32_t dw1; const struct pad_community *comm = gpio_get_community(cfg->pad); uint16_t config_offset = PAD_CFG_OFFSET(cfg->pad - comm->first_pad); /* Iostandby bits are tentatively stored in [3:0] bits (RO) of config1. * dw1 is used to extract the bits of Iostandby. * This is done to preserve config1 size as unit16 in gpio.h. */ dw1 = cfg->config1 & ~PAD_CFG1_IOSSTATE_MASK; dw1 |= (cfg->config1 & PAD_CFG1_IOSSTATE_MASK) << PAD_CFG1_IOSSTATE_SHIFT; iosf_write(comm->port, config_offset, cfg->config0); iosf_write(comm->port, config_offset + sizeof(uint32_t), dw1); gpio_configure_itss(cfg, comm->port, config_offset); gpio_configure_owner(cfg, comm->port, cfg->pad - comm->first_pad); gpi_enable_smi(cfg, comm->port, cfg->pad - comm->first_pad); } void gpio_configure_pads(const struct pad_config *cfg, size_t num_pads) { uint32_t i; for (i = 0; i < num_pads; i++) gpio_configure_pad(cfg + i); } void *gpio_dwx_address(const uint16_t pad) { /* Calculate Address of DW0 register for given GPIO * pad - GPIO number * returns - address of GPIO */ const struct pad_community *comm = gpio_get_community(pad); return iosf_address(comm->port, PAD_CFG_OFFSET(pad - comm->first_pad)); } void gpio_input_pulldown(gpio_t gpio) { struct pad_config cfg = PAD_CFG_GPI(gpio, DN_20K, DEEP); gpio_configure_pad(&cfg); } void gpio_input_pullup(gpio_t gpio) { struct pad_config cfg = PAD_CFG_GPI(gpio, UP_20K, DEEP); gpio_configure_pad(&cfg); } void gpio_input(gpio_t gpio) { struct pad_config cfg = PAD_CFG_GPI(gpio, NONE, DEEP); gpio_configure_pad(&cfg); } void gpio_output(gpio_t gpio, int value) { struct pad_config cfg = PAD_CFG_GPO(gpio, value, DEEP); gpio_configure_pad(&cfg); } int gpio_get(gpio_t gpio_num) { uint32_t reg; const struct pad_community *comm = gpio_get_community(gpio_num); uint16_t config_offset = PAD_CFG_OFFSET(gpio_num - comm->first_pad); reg = iosf_read(comm->port, config_offset); return !!(reg & PAD_CFG0_RX_STATE); } void gpio_set(gpio_t gpio_num, int value) { uint32_t reg; const struct pad_community *comm = gpio_get_community(gpio_num); uint16_t config_offset = PAD_CFG_OFFSET(gpio_num - comm->first_pad); reg = iosf_read(comm->port, config_offset); reg &= ~PAD_CFG0_TX_STATE; reg |= !!value & PAD_CFG0_TX_STATE; iosf_write(comm->port, config_offset, reg); } const char *gpio_acpi_path(gpio_t gpio_num) { const struct pad_community *comm = gpio_get_community(gpio_num); switch (comm->port) { case GPIO_N: return "\\_SB.GPO0"; case GPIO_NW: return "\\_SB.GPO1"; case GPIO_W: return "\\_SB.GPO2"; case GPIO_SW: return "\\_SB.GPO3"; } return NULL; } uint16_t gpio_acpi_pin(gpio_t gpio_num) { const struct pad_community *comm = gpio_get_community(gpio_num); switch (comm->port) { case GPIO_N: return PAD_N(gpio_num); case GPIO_NW: return PAD_NW(gpio_num); case GPIO_W: return PAD_W(gpio_num); case GPIO_SW: return PAD_SW(gpio_num); } return gpio_num; } static void print_gpi_status(const struct gpi_status *sts) { int i; int group; int index = 0; int bit_set; int num_groups; int abs_bit; const struct pad_community *comm; for (i = 0; i < ARRAY_SIZE(gpio_communities); i++) { comm = &gpio_communities[i]; num_groups = comm->num_gpi_regs; index = comm->gpi_offset; for (group = 0; group < num_groups; group++, index++) { for (bit_set = 31; bit_set >= 0; bit_set--) { if (!(sts->grp[index] & (1 << bit_set))) continue; abs_bit = bit_set; abs_bit += group * GPIO_MAX_NUM_PER_GROUP; printk(BIOS_DEBUG, "%s %d\n", comm->grp_name, abs_bit); } } } } void gpi_clear_get_smi_status(struct gpi_status *sts) { int i; int group; int index = 0; uint32_t sts_value; uint32_t en_value; int num_groups; const struct pad_community *comm; for (i = 0; i < ARRAY_SIZE(gpio_communities); i++) { comm = &gpio_communities[i]; num_groups = comm->num_gpi_regs; index = comm->gpi_offset; for (group = 0; group < num_groups; group++, index++) { sts_value = iosf_read(gpio_communities[i].port, GPI_SMI_STS_OFFSET(group)); en_value = iosf_read(gpio_communities[i].port, GPI_SMI_EN_OFFSET(group)); sts->grp[index] = sts_value & en_value; /* Clear the set status bits. */ iosf_write(gpio_communities[i].port, GPI_SMI_STS_OFFSET(group), sts->grp[index]); } } if (IS_ENABLED(CONFIG_DEBUG_SMI)) print_gpi_status(sts); } int gpi_status_get(const struct gpi_status *sts, gpio_t gpi) { uint8_t sts_index; const struct pad_community *comm = gpio_get_community(gpi); /* Check if valid gpi */ if (comm == NULL) return 0; sts_index = comm->gpi_offset + ((gpi - comm->first_pad) / GPIO_MAX_NUM_PER_GROUP); return !!(sts->grp[sts_index] & (1 << (gpi % GPIO_MAX_NUM_PER_GROUP))); } /* Helper function to map PMC register groups to tier1 sci groups */ static int pmc_gpe_route_to_gpio(int route) { switch (route) { case PMC_GPE_SW_31_0: return GPIO_GPE_SW_31_0; case PMC_GPE_SW_63_32: return GPIO_GPE_SW_63_32; case PMC_GPE_NW_31_0: return GPIO_GPE_NW_31_0; case PMC_GPE_NW_63_32: return GPIO_GPE_NW_63_32; case PMC_GPE_NW_95_64: return GPIO_GPE_NW_95_64; case PMC_GPE_N_31_0: return GPIO_GPE_N_31_0; case PMC_GPE_N_63_32: return GPIO_GPE_N_63_32; case PMC_GPE_W_31_0: return GPIO_GPE_W_31_0; default: return -1; } } void gpio_route_gpe(uint8_t gpe0b, uint8_t gpe0c, uint8_t gpe0d) { int i; uint32_t misccfg_mask; uint32_t misccfg_value; uint32_t value; int ret; /* Get the group here for community specific MISCCFG register. * If any of these returns -1 then there is some error in devicetree * where the group is probably hardcoded and does not comply with the * PMC group defines. So we return from here and MISCFG is set to * default. */ ret = pmc_gpe_route_to_gpio(gpe0b); if (ret == -1) return; gpe0b = ret; ret = pmc_gpe_route_to_gpio(gpe0c); if (ret == -1) return; gpe0c = ret; ret = pmc_gpe_route_to_gpio(gpe0d); if (ret == -1) return; gpe0d = ret; misccfg_value = gpe0b << MISCCFG_GPE0_DW0_SHIFT; misccfg_value |= gpe0c << MISCCFG_GPE0_DW1_SHIFT; misccfg_value |= gpe0d << MISCCFG_GPE0_DW2_SHIFT; /* Program GPIO_MISCCFG */ misccfg_mask = ~(MISCCFG_GPE0_DW2_MASK | MISCCFG_GPE0_DW1_MASK | MISCCFG_GPE0_DW0_MASK); for (i = 0; i < ARRAY_SIZE(gpio_communities); i++) { const struct pad_community *comm = &gpio_communities[i]; value = iosf_read(comm->port, GPIO_MISCCFG); value &= misccfg_mask; value |= misccfg_value; iosf_write(comm->port, GPIO_MISCCFG, value); } }