root/drivers/fsi/fsi-master-i2cr.c
// SPDX-License-Identifier: GPL-2.0
/* Copyright (C) IBM Corporation 2023 */

#include <linux/device.h>
#include <linux/fsi.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/mutex.h>

#include "fsi-master-i2cr.h"

#define CREATE_TRACE_POINTS
#include <trace/events/fsi_master_i2cr.h>

#define I2CR_ADDRESS_CFAM(a)    ((a) >> 2)
#define I2CR_INITIAL_PARITY     true

#define I2CR_STATUS_CMD         0x60002
#define  I2CR_STATUS_ERR         BIT_ULL(61)
#define I2CR_ERROR_CMD          0x60004
#define I2CR_LOG_CMD            0x60008

static const u8 i2cr_cfam[] = {
        0xc0, 0x02, 0x0d, 0xa6,
        0x80, 0x01, 0x10, 0x02,
        0x80, 0x01, 0x10, 0x02,
        0x80, 0x01, 0x10, 0x02,
        0x80, 0x01, 0x80, 0x52,
        0x80, 0x01, 0x10, 0x02,
        0x80, 0x01, 0x10, 0x02,
        0x80, 0x01, 0x10, 0x02,
        0x80, 0x01, 0x10, 0x02,
        0x80, 0x01, 0x22, 0x2d,
        0x00, 0x00, 0x00, 0x00,
        0xde, 0xad, 0xc0, 0xde
};

static bool i2cr_check_parity32(u32 v, bool parity)
{
        u32 i;

        for (i = 0; i < 32; ++i) {
                if (v & (1u << i))
                        parity = !parity;
        }

        return parity;
}

static bool i2cr_check_parity64(u64 v)
{
        u32 i;
        bool parity = I2CR_INITIAL_PARITY;

        for (i = 0; i < 64; ++i) {
                if (v & (1llu << i))
                        parity = !parity;
        }

        return parity;
}

static u32 i2cr_get_command(u32 address, bool parity)
{
        address <<= 1;

        if (i2cr_check_parity32(address, parity))
                address |= 1;

        return address;
}

static int i2cr_transfer(struct i2c_client *client, u32 command, u64 *data)
{
        struct i2c_msg msgs[2];
        int ret;

        msgs[0].addr = client->addr;
        msgs[0].flags = 0;
        msgs[0].len = sizeof(command);
        msgs[0].buf = (__u8 *)&command;
        msgs[1].addr = client->addr;
        msgs[1].flags = I2C_M_RD;
        msgs[1].len = sizeof(*data);
        msgs[1].buf = (__u8 *)data;

        ret = i2c_transfer(client->adapter, msgs, 2);
        if (ret == 2)
                return 0;

        trace_i2cr_i2c_error(client, command, ret);

        if (ret < 0)
                return ret;

        return -EIO;
}

static int i2cr_check_status(struct i2c_client *client)
{
        u64 status;
        int ret;

        ret = i2cr_transfer(client, I2CR_STATUS_CMD, &status);
        if (ret)
                return ret;

        if (status & I2CR_STATUS_ERR) {
                u32 buf[3] = { 0, 0, 0 };
                u64 error;
                u64 log;

                i2cr_transfer(client, I2CR_ERROR_CMD, &error);
                i2cr_transfer(client, I2CR_LOG_CMD, &log);

                trace_i2cr_status_error(client, status, error, log);

                buf[0] = I2CR_STATUS_CMD;
                i2c_master_send(client, (const char *)buf, sizeof(buf));

                buf[0] = I2CR_ERROR_CMD;
                i2c_master_send(client, (const char *)buf, sizeof(buf));

                buf[0] = I2CR_LOG_CMD;
                i2c_master_send(client, (const char *)buf, sizeof(buf));

                dev_err(&client->dev, "status:%016llx error:%016llx log:%016llx\n", status, error,
                        log);
                return -EREMOTEIO;
        }

        trace_i2cr_status(client, status);
        return 0;
}

int fsi_master_i2cr_read(struct fsi_master_i2cr *i2cr, u32 addr, u64 *data)
{
        u32 command = i2cr_get_command(addr, I2CR_INITIAL_PARITY);
        int ret;

        mutex_lock(&i2cr->lock);

        ret = i2cr_transfer(i2cr->client, command, data);
        if (ret)
                goto unlock;

        ret = i2cr_check_status(i2cr->client);
        if (ret)
                goto unlock;

        trace_i2cr_read(i2cr->client, command, data);

unlock:
        mutex_unlock(&i2cr->lock);
        return ret;
}
EXPORT_SYMBOL_GPL(fsi_master_i2cr_read);

int fsi_master_i2cr_write(struct fsi_master_i2cr *i2cr, u32 addr, u64 data)
{
        u32 buf[3] = { 0 };
        int ret;

        buf[0] = i2cr_get_command(addr, i2cr_check_parity64(data));
        memcpy(&buf[1], &data, sizeof(data));

        mutex_lock(&i2cr->lock);

        ret = i2c_master_send(i2cr->client, (const char *)buf, sizeof(buf));
        if (ret == sizeof(buf)) {
                ret = i2cr_check_status(i2cr->client);
                if (!ret)
                        trace_i2cr_write(i2cr->client, buf[0], data);
        } else {
                trace_i2cr_i2c_error(i2cr->client, buf[0], ret);

                if (ret >= 0)
                        ret = -EIO;
        }

        mutex_unlock(&i2cr->lock);
        return ret;
}
EXPORT_SYMBOL_GPL(fsi_master_i2cr_write);

static int i2cr_read(struct fsi_master *master, int link, uint8_t id, uint32_t addr, void *val,
                     size_t size)
{
        struct fsi_master_i2cr *i2cr = container_of(master, struct fsi_master_i2cr, master);
        u64 data;
        size_t i;
        int ret;

        if (link || id || (addr & 0xffff0000) || !(size == 1 || size == 2 || size == 4))
                return -EINVAL;

        /*
         * The I2CR doesn't have CFAM or FSI slave address space - only the
         * engines. In order for this to work with the FSI core, we need to
         * emulate at minimum the CFAM config table so that the appropriate
         * engines are discovered.
         */
        if (addr < 0xc00) {
                if (addr > sizeof(i2cr_cfam) - 4)
                        addr = (addr & 0x3) + (sizeof(i2cr_cfam) - 4);

                memcpy(val, &i2cr_cfam[addr], size);
                return 0;
        }

        ret = fsi_master_i2cr_read(i2cr, I2CR_ADDRESS_CFAM(addr), &data);
        if (ret)
                return ret;

        /*
         * FSI core expects up to 4 bytes BE back, while I2CR replied with LE
         * bytes on the wire.
         */
        for (i = 0; i < size; ++i)
                ((u8 *)val)[i] = ((u8 *)&data)[7 - i];

        return 0;
}

static int i2cr_write(struct fsi_master *master, int link, uint8_t id, uint32_t addr,
                      const void *val, size_t size)
{
        struct fsi_master_i2cr *i2cr = container_of(master, struct fsi_master_i2cr, master);
        u64 data = 0;
        size_t i;

        if (link || id || (addr & 0xffff0000) || !(size == 1 || size == 2 || size == 4))
                return -EINVAL;

        /* I2CR writes to CFAM or FSI slave address are a successful no-op. */
        if (addr < 0xc00)
                return 0;

        /*
         * FSI core passes up to 4 bytes BE, while the I2CR expects LE bytes on
         * the wire.
         */
        for (i = 0; i < size; ++i)
                ((u8 *)&data)[7 - i] = ((u8 *)val)[i];

        return fsi_master_i2cr_write(i2cr, I2CR_ADDRESS_CFAM(addr), data);
}

static void i2cr_release(struct device *dev)
{
        struct fsi_master_i2cr *i2cr = to_fsi_master_i2cr(to_fsi_master(dev));

        of_node_put(dev->of_node);

        kfree(i2cr);
}

static int i2cr_probe(struct i2c_client *client)
{
        struct fsi_master_i2cr *i2cr;
        int ret;

        i2cr = kzalloc_obj(*i2cr);
        if (!i2cr)
                return -ENOMEM;

        /* Only one I2CR on any given I2C bus (fixed I2C device address) */
        i2cr->master.idx = client->adapter->nr;
        dev_set_name(&i2cr->master.dev, "i2cr%d", i2cr->master.idx);
        i2cr->master.dev.parent = &client->dev;
        i2cr->master.dev.of_node = of_node_get(dev_of_node(&client->dev));
        i2cr->master.dev.release = i2cr_release;

        i2cr->master.n_links = 1;
        i2cr->master.read = i2cr_read;
        i2cr->master.write = i2cr_write;

        mutex_init(&i2cr->lock);
        i2cr->client = client;

        ret = fsi_master_register(&i2cr->master);
        if (ret)
                return ret;

        i2c_set_clientdata(client, i2cr);
        return 0;
}

static void i2cr_remove(struct i2c_client *client)
{
        struct fsi_master_i2cr *i2cr = i2c_get_clientdata(client);

        fsi_master_unregister(&i2cr->master);
}

static const struct of_device_id i2cr_ids[] = {
        { .compatible = "ibm,i2cr-fsi-master" },
        { }
};
MODULE_DEVICE_TABLE(of, i2cr_ids);

static struct i2c_driver i2cr_driver = {
        .probe = i2cr_probe,
        .remove = i2cr_remove,
        .driver = {
                .name = "fsi-master-i2cr",
                .of_match_table = i2cr_ids,
        },
};

module_i2c_driver(i2cr_driver)

MODULE_AUTHOR("Eddie James <eajames@linux.ibm.com>");
MODULE_DESCRIPTION("IBM I2C Responder virtual FSI master driver");
MODULE_LICENSE("GPL");