Duncan Laurie has uploaded this change for review. ( https://review.coreboot.org/c/coreboot/+/31439
Change subject: drivers/intel/gma: Add DP AUX interface and I2C bus ......................................................................
drivers/intel/gma: Add DP AUX interface and I2C bus
In order to support reading the EDID of the internal panel this driver ports the kernel i915 Display Port AUX channel protocol and provides an I2C bus interface to it.
A helper function is also provided for reading the EDID at the common I2C slave address.
The bottom word of the AUX channel control register contains SOC-specific bits which are implemented for soc/intel/common in a subsequent commit.
Change-Id: I4aa7e687aea397d6d70c0398e0f6958611db6410 Signed-off-by: Duncan Laurie dlaurie@google.com --- M src/drivers/intel/gma/Kconfig M src/drivers/intel/gma/Makefile.inc M src/drivers/intel/gma/i915.h A src/drivers/intel/gma/intel_dp_aux.c 4 files changed, 386 insertions(+), 0 deletions(-)
git pull ssh://review.coreboot.org:29418/coreboot refs/changes/39/31439/1
diff --git a/src/drivers/intel/gma/Kconfig b/src/drivers/intel/gma/Kconfig index a5c8495..63ca09f 100644 --- a/src/drivers/intel/gma/Kconfig +++ b/src/drivers/intel/gma/Kconfig @@ -32,6 +32,13 @@ bool default n
+config INTEL_DP_AUX + bool + default n + help + Select this option to enable the AUX channel driver which provides + an I2C bus for reading the EDID from the eDP port. + config INTEL_GMA_SSC_ALTERNATE_REF bool default n diff --git a/src/drivers/intel/gma/Makefile.inc b/src/drivers/intel/gma/Makefile.inc index 274955a..5a5fb8d 100644 --- a/src/drivers/intel/gma/Makefile.inc +++ b/src/drivers/intel/gma/Makefile.inc @@ -13,6 +13,7 @@ ## GNU General Public License for more details. ##
+ramstage-$(CONFIG_INTEL_DP_AUX) += intel_dp_aux.c ramstage-$(CONFIG_INTEL_DDI) += intel_ddi.c ramstage-$(CONFIG_INTEL_EDID) += edid.c vbt.c ifeq ($(CONFIG_VGA_ROM_RUN),y) diff --git a/src/drivers/intel/gma/i915.h b/src/drivers/intel/gma/i915.h index 0ddb2de..de56076 100644 --- a/src/drivers/intel/gma/i915.h +++ b/src/drivers/intel/gma/i915.h @@ -107,4 +107,53 @@ generate_fake_intel_oprom(const struct i915_gpu_controller_info *conf, struct device *dev, const char *idstr);
+/* intel_dp_aux.c */ + +/** + * enum dp_aux_ch - Available AUX channels. + */ +enum dp_aux_ch { + AUX_CH_EDP, + AUX_CH_DDI1, + AUX_CH_DDI2, + AUX_CH_DDI3, +}; + +/** + * intel_dp_aux_ctl_soc() - Retrieve SOC specific bits for AUX control. + * + * Return: Mask of bits to set in AUX control register. + */ +uint32_t intel_dp_aux_ctl_soc(void); + +/** + * intel_dp_aux_ch() - Execute a DP AUX transaction. + * @dev: Graphics device. + * @aux_ch: Port for this transaction from %dp_aux_ch. + * @send_buf: Transmit data buffer. + * @send_bytes: Number of bytes to send. + * @recv_buf: Receive data buffer. + * @recv_size: Size of receive data buffer. + * + * Return: number of bytes received, or negative error code. + */ +ssize_t intel_dp_aux_ch(struct device *dev, enum dp_aux_ch aux_ch, + uint8_t *send_buf, size_t send_bytes, + uint8_t *recv_buf, size_t recv_size); + +/** + * intel_edp_aux_edid() - Read EDID from eDP AUX channel. + * @dev: Graphics device. + * @buf: Buffer to store EDID. + * @len: Size of provided buffer. + * + * Return: 0 to indicate success, negative error code on failure. + */ +int intel_edp_aux_edid(struct device *dev, uint8_t *buf, size_t len); + + +/* I2C controller for eDP AUX channel */ +struct i2c_bus_operations; +extern const struct i2c_bus_operations intel_edp_aux_i2c_bus_ops; + #endif diff --git a/src/drivers/intel/gma/intel_dp_aux.c b/src/drivers/intel/gma/intel_dp_aux.c new file mode 100644 index 0000000..906ce0e --- /dev/null +++ b/src/drivers/intel/gma/intel_dp_aux.c @@ -0,0 +1,329 @@ +/* + * Copyright 2008 Intel Corporation + * Copyright 2019 Google LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * Authors: + * Keith Packard keithp@keithp.com + */ + +/* + * This driver implements a Display Port AUX channel interface for the Intel + * i915 GPU. This work is based on the kernel i915 driver. There are SOC + * specific bits in part of the AUX channel control register, and this driver + * expects the SOC to implement a function to fill out those bits properly. + * + * An I2C controller driver is created that allows tunneling I2C traffic over + * the AUX channel for the eDP port. This allows reading the built-in panel + * EDID and a helper function is added to do that using the common EDID I2C + * slave address. + * + * The AUX channel interface does require the panel power to be enabled + * previously by coreboot or FSP. FSP will turn on the panel power it if + * the UPD for PanelPowerEnable is set (which is the default) or if the GOP + * driver is executed. + */ + +#include <console/console.h> +#include <delay.h> +#include <device/i2c_bus.h> +#include <device/pci.h> +#include <device/resource.h> +#include <drivers/intel/gma/i915.h> +#include <drivers/intel/gma/i915_reg.h> +#include <drivers/intel/gma/drm_dp_helper.h> +#include <stdint.h> +#include <string.h> +#include <timer.h> + +/** + * enum intel_dp_aux_info - Intel DP AUX driver information. + * @DP_AUX_RETRY: Number of retry attempts. Minimum in DP spec is 3. + * @DP_AUX_HEADER_SIZE: Number of bytes in transaction header. + * @DP_AUX_I2C_SLAVE_EDID: Common I2C slave address for EDID. + * @DP_AUX_SEND_BUSY_TIMEOUT: Time in milliseconds to wait for completion. + * @DP_AUX_DATA_REG_COUNT: Number of data registers in each channel. + * @DP_AUX_DATA_REG_SIZE: Total size of data registers including header. + * @DP_AUX_DATA_XFER_MAX: Maximum number of data bytes in a transaction. + */ +enum intel_dp_aux_info { + DP_AUX_RETRY = 5, + DP_AUX_HEADER_SIZE = 4, + DP_AUX_I2C_SLAVE_EDID = 0x50, + DP_AUX_SEND_BUSY_TIMEOUT_MS = 1000, + DP_AUX_DATA_REG_COUNT = 5, + DP_AUX_DATA_REG_SIZE = DP_AUX_DATA_REG_COUNT * sizeof(uint32_t), + DP_AUX_DATA_XFER_MAX = DP_AUX_DATA_REG_SIZE - DP_AUX_HEADER_SIZE, +}; + +/** + * enum intel_dp_aux_type - AUX channel transaction type. + * @AUX_TYPE_I2C_WRITE: I2C write transaction. + * @AUX_TYPE_I2C_READ: I2C read transaction. + */ +enum intel_dp_aux_type { + AUX_TYPE_I2C_WRITE, + AUX_TYPE_I2C_READ, +}; + +/* Map AUX channel to control register. */ +static uint32_t dp_aux_ch_ctl[] = { + [AUX_CH_EDP] = DPA_AUX_CH_CTL, + [AUX_CH_DDI1] = DPB_AUX_CH_CTL, + [AUX_CH_DDI2] = DPC_AUX_CH_CTL, + [AUX_CH_DDI3] = DPD_AUX_CH_CTL, +}; + +/* Map AUX channel to data register start. */ +static uint32_t dp_aux_ch_data[] = { + [AUX_CH_EDP] = DPA_AUX_CH_DATA1, + [AUX_CH_DDI1] = DPB_AUX_CH_DATA1, + [AUX_CH_DDI2] = DPC_AUX_CH_DATA1, + [AUX_CH_DDI3] = DPD_AUX_CH_DATA1, +}; + +/* Store the base address of the GMA region. */ +static uintptr_t base_address; + +__weak uint32_t intel_dp_aux_ctl_soc(void) +{ + return 0; +} + +static uint32_t i915_read32(uint32_t reg) +{ + return read32((void *)(base_address + reg)); +} + +static void i915_write32(uint32_t reg, uint32_t val) +{ + write32((void *)(base_address + reg), val); +} + +static uint32_t pack_aux(uint8_t *src, size_t src_bytes) +{ + uint32_t v = 0; + size_t i; + + if (src_bytes > 4) + src_bytes = 4; + for (i = 0; i < src_bytes; i++) + v |= ((uint32_t) src[i]) << ((3 - i) * 8); + return v; +} + +static void unpack_aux(uint32_t src, uint8_t *dst, int dst_bytes) +{ + size_t i; + + if (dst_bytes > 4) + dst_bytes = 4; + for (i = 0; i < dst_bytes; i++) + dst[i] = src >> ((3 - i) * 8); +} + +static int intel_dp_wait_busy(enum dp_aux_ch aux_ch) +{ + uint32_t status; + struct stopwatch sw; + + stopwatch_init_msecs_expire(&sw, DP_AUX_SEND_BUSY_TIMEOUT_MS); + do { + status = i915_read32(dp_aux_ch_ctl[aux_ch]); + if (!(status & DP_AUX_CH_CTL_SEND_BUSY)) + break; + mdelay(1); + } while (!stopwatch_expired(&sw)); + + return status; +} + +ssize_t intel_dp_aux_ch(struct device *dev, enum dp_aux_ch aux_ch, + uint8_t *send_buf, size_t send_bytes, + uint8_t *recv_buf, size_t recv_size) +{ + size_t recv_bytes, try, i; + uint32_t status = 0; + + /* Initialize base address */ + if (!base_address) { + struct resource *res = find_resource(dev, PCI_BASE_ADDRESS_0); + base_address = (uintptr_t)res->base; + } + + /* Try to wait for any previous AUX channel activity */ + for (try = 0; try < DP_AUX_RETRY; try++) { + status = intel_dp_wait_busy(aux_ch); + if (!(status & DP_AUX_CH_CTL_SEND_BUSY)) + break; + } + if (try == DP_AUX_RETRY) { + printk(BIOS_DEBUG, "%s: not started status 0x%08x\n", + __func__, status); + return -1; + } + + /* Must try at least 3 times according to DP spec */ + for (try = 0; try < DP_AUX_RETRY; try++) { + /* Load the send data into the aux channel data registers */ + for (i = 0; i < send_bytes; i += 4) + i915_write32(dp_aux_ch_data[aux_ch] + i, + pack_aux(send_buf + i, send_bytes - i)); + + /* Send the command and wait for it to complete */ + i915_write32(dp_aux_ch_ctl[aux_ch], + intel_dp_aux_ctl_soc() | /* SOC specific bits */ + DP_AUX_CH_CTL_SEND_BUSY | + DP_AUX_CH_CTL_TIME_OUT_1600us | + DP_AUX_CH_CTL_DONE | + DP_AUX_CH_CTL_TIME_OUT_ERROR | + DP_AUX_CH_CTL_RECEIVE_ERROR | + (send_bytes << DP_AUX_CH_CTL_MESSAGE_SIZE_SHIFT)); + + status = intel_dp_wait_busy(aux_ch); + if (status & DP_AUX_CH_CTL_SEND_BUSY) + continue; + + /* Clear done status and any errors */ + i915_write32(dp_aux_ch_ctl[aux_ch], + status | + DP_AUX_CH_CTL_DONE | + DP_AUX_CH_CTL_TIME_OUT_ERROR | + DP_AUX_CH_CTL_RECEIVE_ERROR); + + if (status & DP_AUX_CH_CTL_DONE) + break; + if (status & DP_AUX_CH_CTL_RECEIVE_ERROR) + udelay(500); + } + + if ((status & DP_AUX_CH_CTL_DONE) == 0) { + printk(BIOS_DEBUG, "%s: not done status 0x%08x\n", + __func__, status); + return -1; + } + if (status & DP_AUX_CH_CTL_RECEIVE_ERROR) { + printk(BIOS_DEBUG, "%s: receive error status 0x%08x\n", + __func__, status); + return -1; + } + if (status & DP_AUX_CH_CTL_TIME_OUT_ERROR) { + printk(BIOS_DEBUG, "%s: timeout status 0x%08x\n", + __func__, status); + return -1; + } + + /* Unload any bytes sent back from the other side */ + recv_bytes = ((status & DP_AUX_CH_CTL_MESSAGE_SIZE_MASK) >> + DP_AUX_CH_CTL_MESSAGE_SIZE_SHIFT); + if (recv_bytes > recv_size) + recv_bytes = recv_size; + + for (i = 0; i < recv_bytes; i += 4) + unpack_aux(i915_read32(dp_aux_ch_data[aux_ch] + i), + recv_buf + i, recv_bytes - i); + + return recv_bytes; +} + +static ssize_t intel_dp_aux_i2c_transfer(struct device *dev, + enum dp_aux_ch aux_ch, + const struct i2c_msg *msg, + size_t offset, size_t len) +{ + uint8_t txbuf[DP_AUX_DATA_REG_SIZE] = {}; + uint8_t rxbuf[DP_AUX_DATA_REG_SIZE] = {}; + uint8_t type; + size_t txsize, rxsize; + ssize_t bytes; + + if (!len) + return -1; + + if (msg->flags & I2C_M_RD) { + type = AUX_TYPE_I2C_READ; + txsize = DP_AUX_HEADER_SIZE; + rxsize = len + 1; + } else { + type = AUX_TYPE_I2C_WRITE; + txsize = DP_AUX_HEADER_SIZE + len; + rxsize = 2; + memcpy(txbuf + DP_AUX_HEADER_SIZE, msg->buf + offset, len); + } + + /* Fill DP AUX header */ + txbuf[0] = (type << 4) | ((msg->slave >> 16) & 0xf); + txbuf[1] = (msg->slave >> 8) & 0xff; + txbuf[2] = msg->slave & 0xff; + txbuf[3] = len - 1; + + bytes = intel_dp_aux_ch(dev, aux_ch, txbuf, txsize, rxbuf, rxsize); + if (bytes > 0 && msg->flags & I2C_M_RD) + memcpy(msg->buf + offset, rxbuf + 1, bytes - 1); + + return bytes; +} + +static int intel_edp_aux_i2c(struct device *dev, const struct i2c_msg *msg, + size_t count) +{ + /* Split different messages into separate transactions */ + while (count--) { + size_t offset = 0; + + /* Split into chunks to fit in available data registers */ + while (offset < msg->len) { + size_t txlen = MIN(msg->len - offset, + DP_AUX_DATA_XFER_MAX); + + if (intel_dp_aux_i2c_transfer(dev, AUX_CH_EDP, + msg, offset, txlen) < 0) + return -1; + + offset += txlen; + } + msg++; + } + return 0; +} + +const struct i2c_bus_operations intel_edp_aux_i2c_bus_ops = { + .transfer = intel_edp_aux_i2c +}; + +int intel_edp_aux_edid(struct device *dev, uint8_t *buf, size_t len) +{ + uint8_t start = 0; + struct i2c_msg msgs[] = { + { + .slave = DP_AUX_I2C_SLAVE_EDID, + .len = sizeof(start), + .buf = &start + }, { + .slave = DP_AUX_I2C_SLAVE_EDID, + .flags = I2C_M_RD, + .len = len, + .buf = buf + } + }; + + return intel_edp_aux_i2c(dev, msgs, ARRAY_SIZE(msgs)); +}