summaryrefslogtreecommitdiff
path: root/src/drivers/i2c/ptn3460/ptn3460.c
blob: 7dee2222a05a7d1c32b16e5458f52d9e1560d90b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
/* 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) */