1/*	$NetBSD: nouveau_nvkm_subdev_i2c_aux.c,v 1.5 2021/12/18 23:45:40 riastradh Exp $	*/
2
3/*
4 * Copyright 2009 Red Hat Inc.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
19 * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 * OTHER DEALINGS IN THE SOFTWARE.
23 *
24 * Authors: Ben Skeggs
25 */
26#include <sys/cdefs.h>
27__KERNEL_RCSID(0, "$NetBSD: nouveau_nvkm_subdev_i2c_aux.c,v 1.5 2021/12/18 23:45:40 riastradh Exp $");
28
29#include "aux.h"
30#include "pad.h"
31
32#include <linux/nbsd-namespace.h>
33
34static int
35nvkm_i2c_aux_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
36{
37	struct nvkm_i2c_aux *aux = container_of(adap, typeof(*aux), i2c);
38	struct i2c_msg *msg = msgs;
39	int ret, mcnt = num;
40
41	ret = nvkm_i2c_aux_acquire(aux);
42	if (ret)
43		return ret;
44
45	while (mcnt--) {
46		u8 remaining = msg->len;
47		u8 *ptr = msg->buf;
48
49		while (remaining) {
50			u8 cnt, retries, cmd;
51
52			if (msg->flags & I2C_M_RD)
53				cmd = 1;
54			else
55				cmd = 0;
56
57			if (mcnt || remaining > 16)
58				cmd |= 4; /* MOT */
59
60			for (retries = 0, cnt = 0;
61			     retries < 32 && !cnt;
62			     retries++) {
63				cnt = min_t(u8, remaining, 16);
64				ret = aux->func->xfer(aux, true, cmd,
65						      msg->addr, ptr, &cnt);
66				if (ret < 0)
67					goto out;
68			}
69			if (!cnt) {
70				AUX_TRACE(aux, "no data after 32 retries");
71				ret = -EIO;
72				goto out;
73			}
74
75			ptr += cnt;
76			remaining -= cnt;
77		}
78
79		msg++;
80	}
81
82	ret = num;
83out:
84	nvkm_i2c_aux_release(aux);
85	return ret;
86}
87
88static u32
89nvkm_i2c_aux_i2c_func(struct i2c_adapter *adap)
90{
91	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
92}
93
94static const struct i2c_algorithm
95nvkm_i2c_aux_i2c_algo = {
96	.master_xfer = nvkm_i2c_aux_i2c_xfer,
97	.functionality = nvkm_i2c_aux_i2c_func
98};
99
100void
101nvkm_i2c_aux_monitor(struct nvkm_i2c_aux *aux, bool monitor)
102{
103	struct nvkm_i2c_pad *pad = aux->pad;
104	AUX_TRACE(aux, "monitor: %s", monitor ? "yes" : "no");
105	if (monitor)
106		nvkm_i2c_pad_mode(pad, NVKM_I2C_PAD_AUX);
107	else
108		nvkm_i2c_pad_mode(pad, NVKM_I2C_PAD_OFF);
109}
110
111void
112nvkm_i2c_aux_release(struct nvkm_i2c_aux *aux)
113{
114	struct nvkm_i2c_pad *pad = aux->pad;
115	AUX_TRACE(aux, "release");
116	nvkm_i2c_pad_release(pad);
117	mutex_unlock(&aux->mutex);
118}
119
120int
121nvkm_i2c_aux_acquire(struct nvkm_i2c_aux *aux)
122{
123	struct nvkm_i2c_pad *pad = aux->pad;
124	int ret;
125
126	AUX_TRACE(aux, "acquire");
127	mutex_lock(&aux->mutex);
128
129	if (aux->enabled)
130		ret = nvkm_i2c_pad_acquire(pad, NVKM_I2C_PAD_AUX);
131	else
132		ret = -EIO;
133
134	if (ret)
135		mutex_unlock(&aux->mutex);
136	return ret;
137}
138
139int
140nvkm_i2c_aux_xfer(struct nvkm_i2c_aux *aux, bool retry, u8 type,
141		  u32 addr, u8 *data, u8 *size)
142{
143	if (!*size && !aux->func->address_only) {
144		AUX_ERR(aux, "address-only transaction dropped");
145		return -ENOSYS;
146	}
147	return aux->func->xfer(aux, retry, type, addr, data, size);
148}
149
150int
151nvkm_i2c_aux_lnk_ctl(struct nvkm_i2c_aux *aux, int nr, int bw, bool ef)
152{
153	if (aux->func->lnk_ctl)
154		return aux->func->lnk_ctl(aux, nr, bw, ef);
155	return -ENODEV;
156}
157
158void
159nvkm_i2c_aux_del(struct nvkm_i2c_aux **paux)
160{
161	struct nvkm_i2c_aux *aux = *paux;
162	if (aux && !WARN_ON(!aux->func)) {
163		AUX_TRACE(aux, "dtor");
164		list_del(&aux->head);
165		i2c_del_adapter(&aux->i2c);
166		mutex_destroy(&aux->mutex);
167		kfree(*paux);
168		*paux = NULL;
169	}
170}
171
172void
173nvkm_i2c_aux_init(struct nvkm_i2c_aux *aux)
174{
175	AUX_TRACE(aux, "init");
176	mutex_lock(&aux->mutex);
177	aux->enabled = true;
178	mutex_unlock(&aux->mutex);
179}
180
181void
182nvkm_i2c_aux_fini(struct nvkm_i2c_aux *aux)
183{
184	AUX_TRACE(aux, "fini");
185	mutex_lock(&aux->mutex);
186	aux->enabled = false;
187	mutex_unlock(&aux->mutex);
188}
189
190int
191nvkm_i2c_aux_ctor(const struct nvkm_i2c_aux_func *func,
192		  struct nvkm_i2c_pad *pad, int id,
193		  struct nvkm_i2c_aux *aux)
194{
195	struct nvkm_device *device = pad->i2c->subdev.device;
196
197	aux->func = func;
198	aux->pad = pad;
199	aux->id = id;
200	mutex_init(&aux->mutex);
201	list_add_tail(&aux->head, &pad->i2c->aux);
202	AUX_TRACE(aux, "ctor");
203
204	snprintf(aux->i2c.name, sizeof(aux->i2c.name), "nvkm-%s-aux-%04x",
205		 dev_name(device->dev), id);
206	aux->i2c.owner = THIS_MODULE;
207	aux->i2c.dev.parent = device->dev;
208	aux->i2c.algo = &nvkm_i2c_aux_i2c_algo;
209	return i2c_add_adapter(&aux->i2c);
210}
211
212int
213nvkm_i2c_aux_new_(const struct nvkm_i2c_aux_func *func,
214		  struct nvkm_i2c_pad *pad, int id,
215		  struct nvkm_i2c_aux **paux)
216{
217	if (!(*paux = kzalloc(sizeof(**paux), GFP_KERNEL)))
218		return -ENOMEM;
219	return nvkm_i2c_aux_ctor(func, pad, id, *paux);
220}
221