// SPDX-License-Identifier: GPL-2.0-only
/*
 *  cobalt I2C functions
 *
 *  Derived from cx18-i2c.c
 *
 *  Copyright 2012-2015 Cisco Systems, Inc. and/or its affiliates.
 *  All rights reserved.
 */

#include "cobalt-driver.h"
#include "cobalt-i2c.h"

struct cobalt_i2c_regs {
	/* Clock prescaler register lo-byte */
	u8 prerlo;
	u8 dummy0[3];
	/* Clock prescaler register high-byte */
	u8 prerhi;
	u8 dummy1[3];
	/* Control register */
	u8 ctr;
	u8 dummy2[3];
	/* Transmit/Receive register */
	u8 txr_rxr;
	u8 dummy3[3];
	/* Command and Status register */
	u8 cr_sr;
	u8 dummy4[3];
};

/* CTR[7:0] - Control register */

/* I2C Core enable bit */
#define M00018_CTR_BITMAP_EN_MSK	(1 << 7)

/* I2C Core interrupt enable bit */
#define M00018_CTR_BITMAP_IEN_MSK	(1 << 6)

/* CR[7:0] - Command register */

/* I2C start condition */
#define M00018_CR_BITMAP_STA_MSK	(1 << 7)

/* I2C stop condition */
#define M00018_CR_BITMAP_STO_MSK	(1 << 6)

/* I2C read from slave */
#define M00018_CR_BITMAP_RD_MSK		(1 << 5)

/* I2C write to slave */
#define M00018_CR_BITMAP_WR_MSK		(1 << 4)

/* I2C ack */
#define M00018_CR_BITMAP_ACK_MSK	(1 << 3)

/* I2C Interrupt ack */
#define M00018_CR_BITMAP_IACK_MSK	(1 << 0)

/* SR[7:0] - Status register */

/* Receive acknowledge from slave */
#define M00018_SR_BITMAP_RXACK_MSK	(1 << 7)

/* Busy, I2C bus busy (as defined by start / stop bits) */
#define M00018_SR_BITMAP_BUSY_MSK	(1 << 6)

/* Arbitration lost - core lost arbitration */
#define M00018_SR_BITMAP_AL_MSK		(1 << 5)

/* Transfer in progress */
#define M00018_SR_BITMAP_TIP_MSK	(1 << 1)

/* Interrupt flag */
#define M00018_SR_BITMAP_IF_MSK		(1 << 0)

/* Frequency, in Hz */
#define I2C_FREQUENCY			400000
#define ALT_CPU_FREQ			83333333

static struct cobalt_i2c_regs __iomem *
cobalt_i2c_regs(struct cobalt *cobalt, unsigned idx)
{
	switch (idx) {
	case 0:
	default:
		return (struct cobalt_i2c_regs __iomem *)
			(cobalt->bar1 + COBALT_I2C_0_BASE);
	case 1:
		return (struct cobalt_i2c_regs __iomem *)
			(cobalt->bar1 + COBALT_I2C_1_BASE);
	case 2:
		return (struct cobalt_i2c_regs __iomem *)
			(cobalt->bar1 + COBALT_I2C_2_BASE);
	case 3:
		return (struct cobalt_i2c_regs __iomem *)
			(cobalt->bar1 + COBALT_I2C_3_BASE);
	case 4:
		return (struct cobalt_i2c_regs __iomem *)
			(cobalt->bar1 + COBALT_I2C_HSMA_BASE);
	}
}

/* Do low-level i2c byte transfer.
 * Returns -1 in case of an error or 0 otherwise.
 */
static int cobalt_tx_bytes(struct cobalt_i2c_regs __iomem *regs,
		struct i2c_adapter *adap, bool start, bool stop,
		u8 *data, u16 len)
{
	unsigned long start_time;
	int status;
	int cmd;
	int i;

	for (i = 0; i < len; i++) {
		/* Setup data */
		iowrite8(data[i], &regs->txr_rxr);

		/* Setup command */
		if (i == 0 && start) {
			/* Write + Start */
			cmd = M00018_CR_BITMAP_WR_MSK |
			      M00018_CR_BITMAP_STA_MSK;
		} else if (i == len - 1 && stop) {
			/* Write + Stop */
			cmd = M00018_CR_BITMAP_WR_MSK |
			      M00018_CR_BITMAP_STO_MSK;
		} else {
			/* Write only */
			cmd = M00018_CR_BITMAP_WR_MSK;
		}

		/* Execute command */
		iowrite8(cmd, &regs->cr_sr);

		/* Wait for transfer to complete (TIP = 0) */
		start_time = jiffies;
		status = ioread8(&regs->cr_sr);
		while (status & M00018_SR_BITMAP_TIP_MSK) {
			if (time_after(jiffies, start_time + adap->timeout))
				return -ETIMEDOUT;
			cond_resched();
			status = ioread8(&regs->cr_sr);
		}

		/* Verify ACK */
		if (status & M00018_SR_BITMAP_RXACK_MSK) {
			/* NO ACK! */
			return -EIO;
		}

		/* Verify arbitration */
		if (status & M00018_SR_BITMAP_AL_MSK) {
			/* Arbitration lost! */
			return -EIO;
		}
	}
	return 0;
}

/* Do low-level i2c byte read.
 * Returns -1 in case of an error or 0 otherwise.
 */
static int cobalt_rx_bytes(struct cobalt_i2c_regs __iomem *regs,
		struct i2c_adapter *adap, bool start, bool stop,
		u8 *data, u16 len)
{
	unsigned long start_time;
	int status;
	int cmd;
	int i;

	for (i = 0; i < len; i++) {
		/* Setup command */
		if (i == 0 && start) {
			/* Read + Start */
			cmd = M00018_CR_BITMAP_RD_MSK |
			      M00018_CR_BITMAP_STA_MSK;
		} else if (i == len - 1 && stop) {
			/* Read + Stop */
			cmd = M00018_CR_BITMAP_RD_MSK |
			      M00018_CR_BITMAP_STO_MSK;
		} else {
			/* Read only */
			cmd = M00018_CR_BITMAP_RD_MSK;
		}

		/* Last byte to read, no ACK */
		if (i == len - 1)
			cmd |= M00018_CR_BITMAP_ACK_MSK;

		/* Execute command */
		iowrite8(cmd, &regs->cr_sr);

		/* Wait for transfer to complete (TIP = 0) */
		start_time = jiffies;
		status = ioread8(&regs->cr_sr);
		while (status & M00018_SR_BITMAP_TIP_MSK) {
			if (time_after(jiffies, start_time + adap->timeout))
				return -ETIMEDOUT;
			cond_resched();
			status = ioread8(&regs->cr_sr);
		}

		/* Verify arbitration */
		if (status & M00018_SR_BITMAP_AL_MSK) {
			/* Arbitration lost! */
			return -EIO;
		}

		/* Store data */
		data[i] = ioread8(&regs->txr_rxr);
	}
	return 0;
}

/* Generate stop condition on i2c bus.
 * The m00018 stop isn't doing the right thing (wrong timing).
 * So instead send a start condition, 8 zeroes and a stop condition.
 */
static int cobalt_stop(struct cobalt_i2c_regs __iomem *regs,
		struct i2c_adapter *adap)
{
	u8 data = 0;

	return cobalt_tx_bytes(regs, adap, true, true, &data, 1);
}

static int cobalt_xfer(struct i2c_adapter *adap,
			struct i2c_msg msgs[], int num)
{
	struct cobalt_i2c_data *data = adap->algo_data;
	struct cobalt_i2c_regs __iomem *regs = data->regs;
	struct i2c_msg *pmsg;
	unsigned short flags;
	int ret = 0;
	int i, j;

	for (i = 0; i < num; i++) {
		int stop = (i == num - 1);

		pmsg = &msgs[i];
		flags = pmsg->flags;

		if (!(pmsg->flags & I2C_M_NOSTART)) {
			u8 addr = pmsg->addr << 1;

			if (flags & I2C_M_RD)
				addr |= 1;
			if (flags & I2C_M_REV_DIR_ADDR)
				addr ^= 1;
			for (j = 0; j < adap->retries; j++) {
				ret = cobalt_tx_bytes(regs, adap, true, false,
						      &addr, 1);
				if (!ret)
					break;
				cobalt_stop(regs, adap);
			}
			if (ret < 0)
				return ret;
			ret = 0;
		}
		if (pmsg->flags & I2C_M_RD) {
			/* read bytes into buffer */
			ret = cobalt_rx_bytes(regs, adap, false, stop,
					pmsg->buf, pmsg->len);
			if (ret < 0)
				goto bailout;
		} else {
			/* write bytes from buffer */
			ret = cobalt_tx_bytes(regs, adap, false, stop,
					pmsg->buf, pmsg->len);
			if (ret < 0)
				goto bailout;
		}
	}
	ret = i;

bailout:
	if (ret < 0)
		cobalt_stop(regs, adap);
	return ret;
}

static u32 cobalt_func(struct i2c_adapter *adap)
{
	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
}

/* template for i2c-bit-algo */
static const struct i2c_adapter cobalt_i2c_adap_template = {
	.name = "cobalt i2c driver",
	.algo = NULL,                   /* set by i2c-algo-bit */
	.algo_data = NULL,              /* filled from template */
	.owner = THIS_MODULE,
};

static const struct i2c_algorithm cobalt_algo = {
	.master_xfer	= cobalt_xfer,
	.functionality	= cobalt_func,
};

/* init + register i2c algo-bit adapter */
int cobalt_i2c_init(struct cobalt *cobalt)
{
	int i, err;
	int status;
	int prescale;
	unsigned long start_time;

	cobalt_dbg(1, "i2c init\n");

	/* Define I2C clock prescaler */
	prescale = ((ALT_CPU_FREQ) / (5 * I2C_FREQUENCY)) - 1;

	for (i = 0; i < COBALT_NUM_ADAPTERS; i++) {
		struct cobalt_i2c_regs __iomem *regs =
			cobalt_i2c_regs(cobalt, i);
		struct i2c_adapter *adap = &cobalt->i2c_adap[i];

		/* Disable I2C */
		iowrite8(M00018_CTR_BITMAP_EN_MSK, &regs->cr_sr);
		iowrite8(0, &regs->ctr);
		iowrite8(0, &regs->cr_sr);

		start_time = jiffies;
		do {
			if (time_after(jiffies, start_time + HZ)) {
				if (cobalt_ignore_err) {
					adap->dev.parent = NULL;
					return 0;
				}
				return -ETIMEDOUT;
			}
			status = ioread8(&regs->cr_sr);
		} while (status & M00018_SR_BITMAP_TIP_MSK);

		/* Disable I2C */
		iowrite8(0, &regs->ctr);
		iowrite8(0, &regs->cr_sr);

		/* Calculate i2c prescaler */
		iowrite8(prescale & 0xff, &regs->prerlo);
		iowrite8((prescale >> 8) & 0xff, &regs->prerhi);
		/* Enable I2C, interrupts disabled */
		iowrite8(M00018_CTR_BITMAP_EN_MSK, &regs->ctr);
		/* Setup algorithm for adapter */
		cobalt->i2c_data[i].cobalt = cobalt;
		cobalt->i2c_data[i].regs = regs;
		*adap = cobalt_i2c_adap_template;
		adap->algo = &cobalt_algo;
		adap->algo_data = &cobalt->i2c_data[i];
		adap->retries = 3;
		sprintf(adap->name + strlen(adap->name),
				" #%d-%d", cobalt->instance, i);
		i2c_set_adapdata(adap, &cobalt->v4l2_dev);
		adap->dev.parent = &cobalt->pci_dev->dev;
		err = i2c_add_adapter(adap);
		if (err) {
			if (cobalt_ignore_err) {
				adap->dev.parent = NULL;
				return 0;
			}
			while (i--)
				i2c_del_adapter(&cobalt->i2c_adap[i]);
			return err;
		}
		cobalt_info("registered bus %s\n", adap->name);
	}
	return 0;
}

void cobalt_i2c_exit(struct cobalt *cobalt)
{
	int i;

	cobalt_dbg(1, "i2c exit\n");

	for (i = 0; i < COBALT_NUM_ADAPTERS; i++) {
		cobalt_err("unregistered bus %s\n", cobalt->i2c_adap[i].name);
		i2c_del_adapter(&cobalt->i2c_adap[i]);
	}
}
