/* SPDX-License-Identifier: GPL-2.0-only */ #include <console/console.h> #include <device/device.h> #include <device/i2c_bus.h> #include <types.h> #include <bootstate.h> #include "ptn3460.h" /** * \brief This function selects one of 7 EDID-tables inside PTN3460 * which should be emulated on display port and turn emulation ON * @param *dev Pointer to the relevant I2C controller * @param edid_num Number of EDID to emulate (0..6) * @return PTN_SUCCESS or error code */ static int ptn_select_edid(struct device *dev, uint8_t edid_num) { int status = 0; u8 val; if (edid_num > PTN_MAX_EDID_NUM) return PTN_INVALID_EDID; val = (edid_num << 1) | PTN_ENABLE_EMULATION; status = i2c_dev_writeb_at(dev, PTN_CONFIG_OFF + 4, val); return status ? (PTN_BUS_ERROR | status) : PTN_SUCCESS; } /** * \brief This function writes one EDID data structure to PTN3460 * @param *dev Pointer to the relevant I2C controller * @param edid_num Number of EDID that must be written (0..6) * @param *data Pointer to a buffer where data to write is stored in * @return PTN_SUCCESS on success or error code */ static int ptn3460_write_edid(struct device *dev, u8 edid_num, u8 *data) { int status; int i; if (edid_num > PTN_MAX_EDID_NUM) return PTN_INVALID_EDID; /* First enable access to the desired EDID table */ status = i2c_dev_writeb_at(dev, PTN_CONFIG_OFF + 5, edid_num); if (status) return (PTN_BUS_ERROR | status); /* Now we can simply write EDID data to ptn3460 */ for (i = 0; i < PTN_EDID_LEN; i++) { status = i2c_dev_writeb_at(dev, PTN_EDID_OFF + i, data[i]); if (status) return (PTN_BUS_ERROR | status); } return PTN_SUCCESS; } /** * \brief This function sets up the DP2LVDS-converter to be used with the * appropriate EDID data * @param *dev Pointer to the I2C controller where PTN3460 is attached */ static void ptn3460_init(struct device *dev) { struct ptn_3460_config cfg; uint8_t edid_data[PTN_EDID_LEN], edid_tab, *ptr = (uint8_t *)&cfg; int i, val; /* Guard against re-initialization of the device */ static bool init_done = false; if (init_done) { printk(BIOS_DEBUG, "Skipping PTN3460 init as it's already initialized\n"); return; } /* Mainboard provides EDID data. */ if (mainboard_ptn3460_get_edid(edid_data) != CB_SUCCESS) { printk(BIOS_ERR, "PTN3460 error: Unable to get EDID data from mainboard.\n"); return; } /* Mainboard decides which EDID table has to be used. */ edid_tab = mainboard_ptn3460_select_edid_table(); if (edid_tab > PTN_MAX_EDID_NUM) { printk(BIOS_ERR, "PTN3460 error: invalid EDID table (%d) selected.\n", edid_tab); return; } /* Write EDID data into PTN. */ val = ptn3460_write_edid(dev, edid_tab, edid_data); if (val != PTN_SUCCESS) { printk(BIOS_ERR, "PTN3460 error: writing EDID data into device failed.\n"); return; } /* Activate the selected EDID block. */ ptn_select_edid(dev, edid_tab); /* Read out PTN configuration data. */ for (i = 0; i < sizeof(struct ptn_3460_config); i++) { val = i2c_dev_readb_at(dev, PTN_CONFIG_OFF + i); if (val < 0) { printk(BIOS_ERR, "PTN3460 error: Unable to read config data from device.\n"); return; } *ptr++ = (uint8_t)val; /* fill config structure via ptr */ } /* Mainboard can modify the configuration data. Write back configuration data to PTN3460 if modified by mainboard */ if (mainboard_ptn3460_config(&cfg) == CB_SUCCESS) { ptr = (uint8_t *)&cfg; for (i = 0; i < sizeof(struct ptn_3460_config); i++) { val = i2c_dev_writeb_at(dev, PTN_CONFIG_OFF + i, *ptr++); if (val < 0) { printk(BIOS_ERR, "PTN3460 error: Unable to write config data.\n"); return; } } } init_done = true; } __weak enum cb_err mainboard_ptn3460_get_edid(uint8_t edid_data[PTN_EDID_LEN]) { return CB_ERR; } __weak uint8_t mainboard_ptn3460_select_edid_table(void) { return 0; } __weak enum cb_err mainboard_ptn3460_config(struct ptn_3460_config *cfg_ptr) { return CB_ERR; } static struct device_operations ptn3460_ops = { .read_resources = noop_read_resources, .set_resources = noop_set_resources, .init = ptn3460_init, }; static void ptn3460_enable(struct device *dev) { dev->ops = &ptn3460_ops; } struct chip_operations drivers_i2c_ptn3460_ops = { CHIP_NAME("PTN3460") .enable_dev = ptn3460_enable }; #if CONFIG(PTN3460_EARLY_INIT) /** * \brief This function provides a callback for the boot state machine to initialize the * PTN3460 DP-to-LVDS bridge before graphics initialization in order for the bootsplash * logo to be shown. * @param *unused Unused argument for the callback. */ static void ptn3460_early_init(void *unused) { struct device *ptn_dev; printk(BIOS_DEBUG, "Attempting PTN3460 early init.\n"); ptn_dev = dev_find_slot_on_smbus(0, CONFIG_PTN3460_EARLY_ADDR); if (!ptn_dev) { printk(BIOS_ERR, "Failed to find the PTN3460 device!\n"); return; } /* Initialize the I2C controller before it is used. */ if (ptn_dev->bus && ptn_dev->bus->dev->ops && ptn_dev->bus->dev->ops->init) ptn_dev->bus->dev->ops->init(ptn_dev->bus->dev); ptn3460_init(ptn_dev); } BOOT_STATE_INIT_ENTRY(BS_DEV_INIT, BS_ON_ENTRY, ptn3460_early_init, NULL); #endif /* CONFIG(PTN3460_EARLY_INIT) */