blob: 93acb1834e9875260c2c233a2145e9f3bf791fdd [file] [log] [blame]
/* 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 = {
.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) */