#include <linux/string_helpers.h>
#include "aux.h"
#include "pad.h"
static int
nvkm_i2c_aux_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
struct nvkm_i2c_aux *aux = container_of(adap, typeof(*aux), i2c);
struct i2c_msg *msg = msgs;
int ret, mcnt = num;
ret = nvkm_i2c_aux_acquire(aux);
if (ret)
return ret;
while (mcnt--) {
u8 remaining = msg->len;
u8 *ptr = msg->buf;
while (remaining) {
u8 cnt, retries, cmd;
if (msg->flags & I2C_M_RD)
cmd = 1;
else
cmd = 0;
if (mcnt || remaining > 16)
cmd |= 4;
for (retries = 0, cnt = 0;
retries < 32 && !cnt;
retries++) {
cnt = min_t(u8, remaining, 16);
ret = aux->func->xfer(aux, true, cmd,
msg->addr, ptr, &cnt);
if (ret < 0)
goto out;
}
if (!cnt) {
AUX_TRACE(aux, "no data after 32 retries");
ret = -EIO;
goto out;
}
ptr += cnt;
remaining -= cnt;
}
msg++;
}
ret = num;
out:
nvkm_i2c_aux_release(aux);
return ret;
}
static u32
nvkm_i2c_aux_i2c_func(struct i2c_adapter *adap)
{
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
}
static const struct i2c_algorithm
nvkm_i2c_aux_i2c_algo = {
.master_xfer = nvkm_i2c_aux_i2c_xfer,
.functionality = nvkm_i2c_aux_i2c_func
};
void
nvkm_i2c_aux_monitor(struct nvkm_i2c_aux *aux, bool monitor)
{
struct nvkm_i2c_pad *pad = aux->pad;
AUX_TRACE(aux, "monitor: %s", str_yes_no(monitor));
if (monitor)
nvkm_i2c_pad_mode(pad, NVKM_I2C_PAD_AUX);
else
nvkm_i2c_pad_mode(pad, NVKM_I2C_PAD_OFF);
}
void
nvkm_i2c_aux_release(struct nvkm_i2c_aux *aux)
{
struct nvkm_i2c_pad *pad = aux->pad;
AUX_TRACE(aux, "release");
nvkm_i2c_pad_release(pad);
mutex_unlock(&aux->mutex);
}
int
nvkm_i2c_aux_acquire(struct nvkm_i2c_aux *aux)
{
struct nvkm_i2c_pad *pad = aux->pad;
int ret;
AUX_TRACE(aux, "acquire");
mutex_lock(&aux->mutex);
if (aux->enabled)
ret = nvkm_i2c_pad_acquire(pad, NVKM_I2C_PAD_AUX);
else
ret = -EIO;
if (ret)
mutex_unlock(&aux->mutex);
return ret;
}
int
nvkm_i2c_aux_xfer(struct nvkm_i2c_aux *aux, bool retry, u8 type,
u32 addr, u8 *data, u8 *size)
{
if (!*size && !aux->func->address_only) {
AUX_ERR(aux, "address-only transaction dropped");
return -ENOSYS;
}
return aux->func->xfer(aux, retry, type, addr, data, size);
}
int
nvkm_i2c_aux_lnk_ctl(struct nvkm_i2c_aux *aux, int nr, int bw, bool ef)
{
if (aux->func->lnk_ctl)
return aux->func->lnk_ctl(aux, nr, bw, ef);
return -ENODEV;
}
void
nvkm_i2c_aux_del(struct nvkm_i2c_aux **paux)
{
struct nvkm_i2c_aux *aux = *paux;
if (aux && !WARN_ON(!aux->func)) {
AUX_TRACE(aux, "dtor");
list_del(&aux->head);
i2c_del_adapter(&aux->i2c);
kfree(*paux);
*paux = NULL;
}
}
void
nvkm_i2c_aux_init(struct nvkm_i2c_aux *aux)
{
AUX_TRACE(aux, "init");
mutex_lock(&aux->mutex);
aux->enabled = true;
mutex_unlock(&aux->mutex);
}
void
nvkm_i2c_aux_fini(struct nvkm_i2c_aux *aux)
{
AUX_TRACE(aux, "fini");
mutex_lock(&aux->mutex);
aux->enabled = false;
mutex_unlock(&aux->mutex);
}
int
nvkm_i2c_aux_ctor(const struct nvkm_i2c_aux_func *func,
struct nvkm_i2c_pad *pad, int id,
struct nvkm_i2c_aux *aux)
{
struct nvkm_device *device = pad->i2c->subdev.device;
aux->func = func;
aux->pad = pad;
aux->id = id;
mutex_init(&aux->mutex);
list_add_tail(&aux->head, &pad->i2c->aux);
AUX_TRACE(aux, "ctor");
snprintf(aux->i2c.name, sizeof(aux->i2c.name), "nvkm-%s-aux-%04x",
dev_name(device->dev), id);
aux->i2c.owner = THIS_MODULE;
aux->i2c.dev.parent = device->dev;
aux->i2c.algo = &nvkm_i2c_aux_i2c_algo;
return i2c_add_adapter(&aux->i2c);
}
int
nvkm_i2c_aux_new_(const struct nvkm_i2c_aux_func *func,
struct nvkm_i2c_pad *pad, int id,
struct nvkm_i2c_aux **paux)
{
if (!(*paux = kzalloc(sizeof(**paux), GFP_KERNEL)))
return -ENOMEM;
return nvkm_i2c_aux_ctor(func, pad, id, *paux);
}