xref: /freebsd/sys/dev/drm2/drm_dp_iic_helper.c (revision 18250ec6c089c0c50cbd9fd87d78e03ff89916df)
1592ffb21SWarner Losh /*
2592ffb21SWarner Losh  * Copyright © 2009 Keith Packard
3592ffb21SWarner Losh  *
4592ffb21SWarner Losh  * Permission to use, copy, modify, distribute, and sell this software and its
5592ffb21SWarner Losh  * documentation for any purpose is hereby granted without fee, provided that
6592ffb21SWarner Losh  * the above copyright notice appear in all copies and that both that copyright
7592ffb21SWarner Losh  * notice and this permission notice appear in supporting documentation, and
8592ffb21SWarner Losh  * that the name of the copyright holders not be used in advertising or
9592ffb21SWarner Losh  * publicity pertaining to distribution of the software without specific,
10592ffb21SWarner Losh  * written prior permission.  The copyright holders make no representations
11592ffb21SWarner Losh  * about the suitability of this software for any purpose.  It is provided "as
12592ffb21SWarner Losh  * is" without express or implied warranty.
13592ffb21SWarner Losh  *
14592ffb21SWarner Losh  * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
15592ffb21SWarner Losh  * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
16592ffb21SWarner Losh  * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
17592ffb21SWarner Losh  * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
18592ffb21SWarner Losh  * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
19592ffb21SWarner Losh  * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
20592ffb21SWarner Losh  * OF THIS SOFTWARE.
21592ffb21SWarner Losh  */
22592ffb21SWarner Losh 
23592ffb21SWarner Losh #include <sys/types.h>
24592ffb21SWarner Losh #include <sys/kobj.h>
25592ffb21SWarner Losh #include <sys/bus.h>
26592ffb21SWarner Losh #include <dev/iicbus/iic.h>
27592ffb21SWarner Losh #include "iicbus_if.h"
28592ffb21SWarner Losh #include <dev/iicbus/iiconf.h>
29592ffb21SWarner Losh #include <dev/drm2/drmP.h>
30592ffb21SWarner Losh #include <dev/drm2/drm_dp_helper.h>
31592ffb21SWarner Losh 
32592ffb21SWarner Losh static int
iic_dp_aux_transaction(device_t idev,int mode,uint8_t write_byte,uint8_t * read_byte)33592ffb21SWarner Losh iic_dp_aux_transaction(device_t idev, int mode, uint8_t write_byte,
34592ffb21SWarner Losh     uint8_t *read_byte)
35592ffb21SWarner Losh {
36592ffb21SWarner Losh 	struct iic_dp_aux_data *aux_data;
37592ffb21SWarner Losh 	int ret;
38592ffb21SWarner Losh 
39592ffb21SWarner Losh 	aux_data = device_get_softc(idev);
40592ffb21SWarner Losh 	ret = (*aux_data->aux_ch)(idev, mode, write_byte, read_byte);
41592ffb21SWarner Losh 	if (ret < 0)
42592ffb21SWarner Losh 		return (ret);
43592ffb21SWarner Losh 	return (0);
44592ffb21SWarner Losh }
45592ffb21SWarner Losh 
46592ffb21SWarner Losh /*
47592ffb21SWarner Losh  * I2C over AUX CH
48592ffb21SWarner Losh  */
49592ffb21SWarner Losh 
50592ffb21SWarner Losh /*
51592ffb21SWarner Losh  * Send the address. If the I2C link is running, this 'restarts'
52592ffb21SWarner Losh  * the connection with the new address, this is used for doing
53592ffb21SWarner Losh  * a write followed by a read (as needed for DDC)
54592ffb21SWarner Losh  */
55592ffb21SWarner Losh static int
iic_dp_aux_address(device_t idev,u16 address,bool reading)56592ffb21SWarner Losh iic_dp_aux_address(device_t idev, u16 address, bool reading)
57592ffb21SWarner Losh {
58592ffb21SWarner Losh 	struct iic_dp_aux_data *aux_data;
59592ffb21SWarner Losh 	int mode, ret;
60592ffb21SWarner Losh 
61592ffb21SWarner Losh 	aux_data = device_get_softc(idev);
62592ffb21SWarner Losh 	mode = MODE_I2C_START;
63592ffb21SWarner Losh 	if (reading)
64592ffb21SWarner Losh 		mode |= MODE_I2C_READ;
65592ffb21SWarner Losh 	else
66592ffb21SWarner Losh 		mode |= MODE_I2C_WRITE;
67592ffb21SWarner Losh 	aux_data->address = address;
68592ffb21SWarner Losh 	aux_data->running = true;
69592ffb21SWarner Losh 	ret = iic_dp_aux_transaction(idev, mode, 0, NULL);
70592ffb21SWarner Losh 	return (ret);
71592ffb21SWarner Losh }
72592ffb21SWarner Losh 
73592ffb21SWarner Losh /*
74592ffb21SWarner Losh  * Stop the I2C transaction. This closes out the link, sending
75592ffb21SWarner Losh  * a bare address packet with the MOT bit turned off
76592ffb21SWarner Losh  */
77592ffb21SWarner Losh static void
iic_dp_aux_stop(device_t idev,bool reading)78592ffb21SWarner Losh iic_dp_aux_stop(device_t idev, bool reading)
79592ffb21SWarner Losh {
80592ffb21SWarner Losh 	struct iic_dp_aux_data *aux_data;
81592ffb21SWarner Losh 	int mode;
82592ffb21SWarner Losh 
83592ffb21SWarner Losh 	aux_data = device_get_softc(idev);
84592ffb21SWarner Losh 	mode = MODE_I2C_STOP;
85592ffb21SWarner Losh 	if (reading)
86592ffb21SWarner Losh 		mode |= MODE_I2C_READ;
87592ffb21SWarner Losh 	else
88592ffb21SWarner Losh 		mode |= MODE_I2C_WRITE;
89592ffb21SWarner Losh 	if (aux_data->running) {
90592ffb21SWarner Losh 		(void)iic_dp_aux_transaction(idev, mode, 0, NULL);
91592ffb21SWarner Losh 		aux_data->running = false;
92592ffb21SWarner Losh 	}
93592ffb21SWarner Losh }
94592ffb21SWarner Losh 
95592ffb21SWarner Losh /*
96592ffb21SWarner Losh  * Write a single byte to the current I2C address, the
97592ffb21SWarner Losh  * the I2C link must be running or this returns -EIO
98592ffb21SWarner Losh  */
99592ffb21SWarner Losh static int
iic_dp_aux_put_byte(device_t idev,u8 byte)100592ffb21SWarner Losh iic_dp_aux_put_byte(device_t idev, u8 byte)
101592ffb21SWarner Losh {
102592ffb21SWarner Losh 	struct iic_dp_aux_data *aux_data;
103592ffb21SWarner Losh 	int ret;
104592ffb21SWarner Losh 
105592ffb21SWarner Losh 	aux_data = device_get_softc(idev);
106592ffb21SWarner Losh 
107592ffb21SWarner Losh 	if (!aux_data->running)
108592ffb21SWarner Losh 		return (-EIO);
109592ffb21SWarner Losh 
110592ffb21SWarner Losh 	ret = iic_dp_aux_transaction(idev, MODE_I2C_WRITE, byte, NULL);
111592ffb21SWarner Losh 	return (ret);
112592ffb21SWarner Losh }
113592ffb21SWarner Losh 
114592ffb21SWarner Losh /*
115592ffb21SWarner Losh  * Read a single byte from the current I2C address, the
116592ffb21SWarner Losh  * I2C link must be running or this returns -EIO
117592ffb21SWarner Losh  */
118592ffb21SWarner Losh static int
iic_dp_aux_get_byte(device_t idev,u8 * byte_ret)119592ffb21SWarner Losh iic_dp_aux_get_byte(device_t idev, u8 *byte_ret)
120592ffb21SWarner Losh {
121592ffb21SWarner Losh 	struct iic_dp_aux_data *aux_data;
122592ffb21SWarner Losh 	int ret;
123592ffb21SWarner Losh 
124592ffb21SWarner Losh 	aux_data = device_get_softc(idev);
125592ffb21SWarner Losh 
126592ffb21SWarner Losh 	if (!aux_data->running)
127592ffb21SWarner Losh 		return (-EIO);
128592ffb21SWarner Losh 
129592ffb21SWarner Losh 	ret = iic_dp_aux_transaction(idev, MODE_I2C_READ, 0, byte_ret);
130592ffb21SWarner Losh 	return (ret);
131592ffb21SWarner Losh }
132592ffb21SWarner Losh 
133592ffb21SWarner Losh static int
iic_dp_aux_xfer(device_t idev,struct iic_msg * msgs,uint32_t num)134592ffb21SWarner Losh iic_dp_aux_xfer(device_t idev, struct iic_msg *msgs, uint32_t num)
135592ffb21SWarner Losh {
136592ffb21SWarner Losh 	u8 *buf;
137592ffb21SWarner Losh 	int b, m, ret;
138592ffb21SWarner Losh 	u16 len;
139592ffb21SWarner Losh 	bool reading;
140592ffb21SWarner Losh 
141592ffb21SWarner Losh 	ret = 0;
142592ffb21SWarner Losh 	reading = false;
143592ffb21SWarner Losh 
144592ffb21SWarner Losh 	for (m = 0; m < num; m++) {
145592ffb21SWarner Losh 		len = msgs[m].len;
146592ffb21SWarner Losh 		buf = msgs[m].buf;
147592ffb21SWarner Losh 		reading = (msgs[m].flags & IIC_M_RD) != 0;
148592ffb21SWarner Losh 		ret = iic_dp_aux_address(idev, msgs[m].slave >> 1, reading);
149592ffb21SWarner Losh 		if (ret < 0)
150592ffb21SWarner Losh 			break;
151592ffb21SWarner Losh 		if (reading) {
152592ffb21SWarner Losh 			for (b = 0; b < len; b++) {
153592ffb21SWarner Losh 				ret = iic_dp_aux_get_byte(idev, &buf[b]);
154592ffb21SWarner Losh 				if (ret != 0)
155592ffb21SWarner Losh 					break;
156592ffb21SWarner Losh 			}
157592ffb21SWarner Losh 		} else {
158592ffb21SWarner Losh 			for (b = 0; b < len; b++) {
159592ffb21SWarner Losh 				ret = iic_dp_aux_put_byte(idev, buf[b]);
160592ffb21SWarner Losh 				if (ret < 0)
161592ffb21SWarner Losh 					break;
162592ffb21SWarner Losh 			}
163592ffb21SWarner Losh 		}
164592ffb21SWarner Losh 		if (ret != 0)
165592ffb21SWarner Losh 			break;
166592ffb21SWarner Losh 	}
167592ffb21SWarner Losh 	iic_dp_aux_stop(idev, reading);
168592ffb21SWarner Losh 	DRM_DEBUG_KMS("dp_aux_xfer return %d\n", ret);
169592ffb21SWarner Losh 	return (-ret);
170592ffb21SWarner Losh }
171592ffb21SWarner Losh 
172592ffb21SWarner Losh static void
iic_dp_aux_reset_bus(device_t idev)173592ffb21SWarner Losh iic_dp_aux_reset_bus(device_t idev)
174592ffb21SWarner Losh {
175592ffb21SWarner Losh 
176592ffb21SWarner Losh 	(void)iic_dp_aux_address(idev, 0, false);
177592ffb21SWarner Losh 	(void)iic_dp_aux_stop(idev, false);
178592ffb21SWarner Losh }
179592ffb21SWarner Losh 
180592ffb21SWarner Losh static int
iic_dp_aux_reset(device_t idev,u_char speed,u_char addr,u_char * oldaddr)181592ffb21SWarner Losh iic_dp_aux_reset(device_t idev, u_char speed, u_char addr, u_char *oldaddr)
182592ffb21SWarner Losh {
183592ffb21SWarner Losh 
184592ffb21SWarner Losh 	iic_dp_aux_reset_bus(idev);
185592ffb21SWarner Losh 	return (0);
186592ffb21SWarner Losh }
187592ffb21SWarner Losh 
188592ffb21SWarner Losh static int
iic_dp_aux_prepare_bus(device_t idev)189592ffb21SWarner Losh iic_dp_aux_prepare_bus(device_t idev)
190592ffb21SWarner Losh {
191592ffb21SWarner Losh 
192592ffb21SWarner Losh 	/* adapter->retries = 3; */
193592ffb21SWarner Losh 	iic_dp_aux_reset_bus(idev);
194592ffb21SWarner Losh 	return (0);
195592ffb21SWarner Losh }
196592ffb21SWarner Losh 
197592ffb21SWarner Losh static int
iic_dp_aux_probe(device_t idev)198592ffb21SWarner Losh iic_dp_aux_probe(device_t idev)
199592ffb21SWarner Losh {
200592ffb21SWarner Losh 
201592ffb21SWarner Losh 	return (BUS_PROBE_DEFAULT);
202592ffb21SWarner Losh }
203592ffb21SWarner Losh 
204592ffb21SWarner Losh static int
iic_dp_aux_attach(device_t idev)205592ffb21SWarner Losh iic_dp_aux_attach(device_t idev)
206592ffb21SWarner Losh {
207592ffb21SWarner Losh 	struct iic_dp_aux_data *aux_data;
208592ffb21SWarner Losh 
209592ffb21SWarner Losh 	aux_data = device_get_softc(idev);
2105b56413dSWarner Losh 	aux_data->port = device_add_child(idev, "iicbus", DEVICE_UNIT_ANY);
211592ffb21SWarner Losh 	if (aux_data->port == NULL)
212592ffb21SWarner Losh 		return (ENXIO);
213592ffb21SWarner Losh 	device_quiet(aux_data->port);
214*18250ec6SJohn Baldwin 	bus_attach_children(idev);
215592ffb21SWarner Losh 	return (0);
216592ffb21SWarner Losh }
217592ffb21SWarner Losh 
218592ffb21SWarner Losh int
iic_dp_aux_add_bus(device_t dev,const char * name,int (* ch)(device_t idev,int mode,uint8_t write_byte,uint8_t * read_byte),void * priv,device_t * bus,device_t * adapter)219592ffb21SWarner Losh iic_dp_aux_add_bus(device_t dev, const char *name,
220592ffb21SWarner Losh     int (*ch)(device_t idev, int mode, uint8_t write_byte, uint8_t *read_byte),
221592ffb21SWarner Losh     void *priv, device_t *bus, device_t *adapter)
222592ffb21SWarner Losh {
223592ffb21SWarner Losh 	device_t ibus;
224592ffb21SWarner Losh 	struct iic_dp_aux_data *data;
225592ffb21SWarner Losh 	int idx, error;
226592ffb21SWarner Losh 	static int dp_bus_counter;
227592ffb21SWarner Losh 
228c6df6f53SWarner Losh 	bus_topo_lock();
229592ffb21SWarner Losh 
230592ffb21SWarner Losh 	idx = atomic_fetchadd_int(&dp_bus_counter, 1);
231592ffb21SWarner Losh 	ibus = device_add_child(dev, "drm_iic_dp_aux", idx);
232592ffb21SWarner Losh 	if (ibus == NULL) {
233c6df6f53SWarner Losh 		bus_topo_unlock();
234592ffb21SWarner Losh 		DRM_ERROR("drm_iic_dp_aux bus %d creation error\n", idx);
235592ffb21SWarner Losh 		return (-ENXIO);
236592ffb21SWarner Losh 	}
237592ffb21SWarner Losh 	device_quiet(ibus);
238592ffb21SWarner Losh 	error = device_probe_and_attach(ibus);
239592ffb21SWarner Losh 	if (error != 0) {
240592ffb21SWarner Losh 		device_delete_child(dev, ibus);
241c6df6f53SWarner Losh 		bus_topo_unlock();
242592ffb21SWarner Losh 		DRM_ERROR("drm_iic_dp_aux bus %d attach failed, %d\n",
243592ffb21SWarner Losh 		    idx, error);
244592ffb21SWarner Losh 		return (-error);
245592ffb21SWarner Losh 	}
246592ffb21SWarner Losh 	data = device_get_softc(ibus);
247592ffb21SWarner Losh 	data->running = false;
248592ffb21SWarner Losh 	data->address = 0;
249592ffb21SWarner Losh 	data->aux_ch = ch;
250592ffb21SWarner Losh 	data->priv = priv;
251592ffb21SWarner Losh 	error = iic_dp_aux_prepare_bus(ibus);
252592ffb21SWarner Losh 	if (error == 0) {
253592ffb21SWarner Losh 		*bus = ibus;
254592ffb21SWarner Losh 		*adapter = data->port;
255592ffb21SWarner Losh 	}
256c6df6f53SWarner Losh 	bus_topo_unlock();
257592ffb21SWarner Losh 	return (-error);
258592ffb21SWarner Losh }
259592ffb21SWarner Losh 
260592ffb21SWarner Losh static device_method_t drm_iic_dp_aux_methods[] = {
261592ffb21SWarner Losh 	DEVMETHOD(device_probe,		iic_dp_aux_probe),
262592ffb21SWarner Losh 	DEVMETHOD(device_attach,	iic_dp_aux_attach),
263592ffb21SWarner Losh 	DEVMETHOD(device_detach,	bus_generic_detach),
264592ffb21SWarner Losh 	DEVMETHOD(iicbus_reset,		iic_dp_aux_reset),
265592ffb21SWarner Losh 	DEVMETHOD(iicbus_transfer,	iic_dp_aux_xfer),
266592ffb21SWarner Losh 	DEVMETHOD_END
267592ffb21SWarner Losh };
26842d1b888SJohn Baldwin 
269592ffb21SWarner Losh static driver_t drm_iic_dp_aux_driver = {
270592ffb21SWarner Losh 	"drm_iic_dp_aux",
271592ffb21SWarner Losh 	drm_iic_dp_aux_methods,
272592ffb21SWarner Losh 	sizeof(struct iic_dp_aux_data)
273592ffb21SWarner Losh };
27442d1b888SJohn Baldwin 
27542d1b888SJohn Baldwin DRIVER_MODULE_ORDERED(drm_iic_dp_aux, drmn, drm_iic_dp_aux_driver, 0, 0,
27642d1b888SJohn Baldwin     SI_ORDER_SECOND);
277