| /* |
| * This file is part of the coreboot project. |
| * |
| * Copyright (C) 2015 Intel Corp. |
| * (Written by Alexandru Gagniuc <alexandrux.gagniuc@intel.com> 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 <assert.h> |
| #include <gpio.h> |
| #include <soc/gpio.h> |
| #include <soc/iosf.h> |
| #include <soc/itss.h> |
| #include <soc/pm.h> |
| |
| /* 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); |
| } |
| } |