153e89e3eSEddie James // SPDX-License-Identifier: GPL-2.0
253e89e3eSEddie James /* Copyright (C) IBM Corporation 2023 */
353e89e3eSEddie James
453e89e3eSEddie James #include <linux/device.h>
553e89e3eSEddie James #include <linux/fsi.h>
653e89e3eSEddie James #include <linux/i2c.h>
753e89e3eSEddie James #include <linux/module.h>
853e89e3eSEddie James #include <linux/mod_devicetable.h>
953e89e3eSEddie James #include <linux/mutex.h>
1053e89e3eSEddie James
1153e89e3eSEddie James #include "fsi-master-i2cr.h"
1253e89e3eSEddie James
1353e89e3eSEddie James #define CREATE_TRACE_POINTS
1453e89e3eSEddie James #include <trace/events/fsi_master_i2cr.h>
1553e89e3eSEddie James
1653e89e3eSEddie James #define I2CR_ADDRESS_CFAM(a) ((a) >> 2)
1753e89e3eSEddie James #define I2CR_INITIAL_PARITY true
1853e89e3eSEddie James
1953e89e3eSEddie James #define I2CR_STATUS_CMD 0x60002
2053e89e3eSEddie James #define I2CR_STATUS_ERR BIT_ULL(61)
2153e89e3eSEddie James #define I2CR_ERROR_CMD 0x60004
2253e89e3eSEddie James #define I2CR_LOG_CMD 0x60008
2353e89e3eSEddie James
2453e89e3eSEddie James static const u8 i2cr_cfam[] = {
2553e89e3eSEddie James 0xc0, 0x02, 0x0d, 0xa6,
2653e89e3eSEddie James 0x80, 0x01, 0x10, 0x02,
2753e89e3eSEddie James 0x80, 0x01, 0x10, 0x02,
2853e89e3eSEddie James 0x80, 0x01, 0x10, 0x02,
2953e89e3eSEddie James 0x80, 0x01, 0x80, 0x52,
3053e89e3eSEddie James 0x80, 0x01, 0x10, 0x02,
3153e89e3eSEddie James 0x80, 0x01, 0x10, 0x02,
3253e89e3eSEddie James 0x80, 0x01, 0x10, 0x02,
3353e89e3eSEddie James 0x80, 0x01, 0x10, 0x02,
3453e89e3eSEddie James 0x80, 0x01, 0x22, 0x2d,
3553e89e3eSEddie James 0x00, 0x00, 0x00, 0x00,
3653e89e3eSEddie James 0xde, 0xad, 0xc0, 0xde
3753e89e3eSEddie James };
3853e89e3eSEddie James
i2cr_check_parity32(u32 v,bool parity)3953e89e3eSEddie James static bool i2cr_check_parity32(u32 v, bool parity)
4053e89e3eSEddie James {
4153e89e3eSEddie James u32 i;
4253e89e3eSEddie James
4353e89e3eSEddie James for (i = 0; i < 32; ++i) {
4453e89e3eSEddie James if (v & (1u << i))
4553e89e3eSEddie James parity = !parity;
4653e89e3eSEddie James }
4753e89e3eSEddie James
4853e89e3eSEddie James return parity;
4953e89e3eSEddie James }
5053e89e3eSEddie James
i2cr_check_parity64(u64 v)5153e89e3eSEddie James static bool i2cr_check_parity64(u64 v)
5253e89e3eSEddie James {
5353e89e3eSEddie James u32 i;
5453e89e3eSEddie James bool parity = I2CR_INITIAL_PARITY;
5553e89e3eSEddie James
5653e89e3eSEddie James for (i = 0; i < 64; ++i) {
5753e89e3eSEddie James if (v & (1llu << i))
5853e89e3eSEddie James parity = !parity;
5953e89e3eSEddie James }
6053e89e3eSEddie James
6153e89e3eSEddie James return parity;
6253e89e3eSEddie James }
6353e89e3eSEddie James
i2cr_get_command(u32 address,bool parity)6453e89e3eSEddie James static u32 i2cr_get_command(u32 address, bool parity)
6553e89e3eSEddie James {
6653e89e3eSEddie James address <<= 1;
6753e89e3eSEddie James
6853e89e3eSEddie James if (i2cr_check_parity32(address, parity))
6953e89e3eSEddie James address |= 1;
7053e89e3eSEddie James
7153e89e3eSEddie James return address;
7253e89e3eSEddie James }
7353e89e3eSEddie James
i2cr_transfer(struct i2c_client * client,u32 command,u64 * data)7453e89e3eSEddie James static int i2cr_transfer(struct i2c_client *client, u32 command, u64 *data)
7553e89e3eSEddie James {
7653e89e3eSEddie James struct i2c_msg msgs[2];
7753e89e3eSEddie James int ret;
7853e89e3eSEddie James
7953e89e3eSEddie James msgs[0].addr = client->addr;
8053e89e3eSEddie James msgs[0].flags = 0;
8153e89e3eSEddie James msgs[0].len = sizeof(command);
8253e89e3eSEddie James msgs[0].buf = (__u8 *)&command;
8353e89e3eSEddie James msgs[1].addr = client->addr;
8453e89e3eSEddie James msgs[1].flags = I2C_M_RD;
8553e89e3eSEddie James msgs[1].len = sizeof(*data);
8653e89e3eSEddie James msgs[1].buf = (__u8 *)data;
8753e89e3eSEddie James
8853e89e3eSEddie James ret = i2c_transfer(client->adapter, msgs, 2);
8953e89e3eSEddie James if (ret == 2)
9053e89e3eSEddie James return 0;
9153e89e3eSEddie James
9253e89e3eSEddie James trace_i2cr_i2c_error(client, command, ret);
9353e89e3eSEddie James
9453e89e3eSEddie James if (ret < 0)
9553e89e3eSEddie James return ret;
9653e89e3eSEddie James
9753e89e3eSEddie James return -EIO;
9853e89e3eSEddie James }
9953e89e3eSEddie James
i2cr_check_status(struct i2c_client * client)10053e89e3eSEddie James static int i2cr_check_status(struct i2c_client *client)
10153e89e3eSEddie James {
10253e89e3eSEddie James u64 status;
10353e89e3eSEddie James int ret;
10453e89e3eSEddie James
10553e89e3eSEddie James ret = i2cr_transfer(client, I2CR_STATUS_CMD, &status);
10653e89e3eSEddie James if (ret)
10753e89e3eSEddie James return ret;
10853e89e3eSEddie James
10953e89e3eSEddie James if (status & I2CR_STATUS_ERR) {
11053e89e3eSEddie James u32 buf[3] = { 0, 0, 0 };
11153e89e3eSEddie James u64 error;
11253e89e3eSEddie James u64 log;
11353e89e3eSEddie James
11453e89e3eSEddie James i2cr_transfer(client, I2CR_ERROR_CMD, &error);
11553e89e3eSEddie James i2cr_transfer(client, I2CR_LOG_CMD, &log);
11653e89e3eSEddie James
11753e89e3eSEddie James trace_i2cr_status_error(client, status, error, log);
11853e89e3eSEddie James
11953e89e3eSEddie James buf[0] = I2CR_STATUS_CMD;
12053e89e3eSEddie James i2c_master_send(client, (const char *)buf, sizeof(buf));
12153e89e3eSEddie James
12253e89e3eSEddie James buf[0] = I2CR_ERROR_CMD;
12353e89e3eSEddie James i2c_master_send(client, (const char *)buf, sizeof(buf));
12453e89e3eSEddie James
12553e89e3eSEddie James buf[0] = I2CR_LOG_CMD;
12653e89e3eSEddie James i2c_master_send(client, (const char *)buf, sizeof(buf));
12753e89e3eSEddie James
12853e89e3eSEddie James dev_err(&client->dev, "status:%016llx error:%016llx log:%016llx\n", status, error,
12953e89e3eSEddie James log);
13053e89e3eSEddie James return -EREMOTEIO;
13153e89e3eSEddie James }
13253e89e3eSEddie James
13353e89e3eSEddie James trace_i2cr_status(client, status);
13453e89e3eSEddie James return 0;
13553e89e3eSEddie James }
13653e89e3eSEddie James
fsi_master_i2cr_read(struct fsi_master_i2cr * i2cr,u32 addr,u64 * data)13753e89e3eSEddie James int fsi_master_i2cr_read(struct fsi_master_i2cr *i2cr, u32 addr, u64 *data)
13853e89e3eSEddie James {
13953e89e3eSEddie James u32 command = i2cr_get_command(addr, I2CR_INITIAL_PARITY);
14053e89e3eSEddie James int ret;
14153e89e3eSEddie James
14253e89e3eSEddie James mutex_lock(&i2cr->lock);
14353e89e3eSEddie James
14453e89e3eSEddie James ret = i2cr_transfer(i2cr->client, command, data);
14553e89e3eSEddie James if (ret)
14653e89e3eSEddie James goto unlock;
14753e89e3eSEddie James
14853e89e3eSEddie James ret = i2cr_check_status(i2cr->client);
14953e89e3eSEddie James if (ret)
15053e89e3eSEddie James goto unlock;
15153e89e3eSEddie James
15253e89e3eSEddie James trace_i2cr_read(i2cr->client, command, data);
15353e89e3eSEddie James
15453e89e3eSEddie James unlock:
15553e89e3eSEddie James mutex_unlock(&i2cr->lock);
15653e89e3eSEddie James return ret;
15753e89e3eSEddie James }
15853e89e3eSEddie James EXPORT_SYMBOL_GPL(fsi_master_i2cr_read);
15953e89e3eSEddie James
fsi_master_i2cr_write(struct fsi_master_i2cr * i2cr,u32 addr,u64 data)16053e89e3eSEddie James int fsi_master_i2cr_write(struct fsi_master_i2cr *i2cr, u32 addr, u64 data)
16153e89e3eSEddie James {
16253e89e3eSEddie James u32 buf[3] = { 0 };
16353e89e3eSEddie James int ret;
16453e89e3eSEddie James
16553e89e3eSEddie James buf[0] = i2cr_get_command(addr, i2cr_check_parity64(data));
16653e89e3eSEddie James memcpy(&buf[1], &data, sizeof(data));
16753e89e3eSEddie James
16853e89e3eSEddie James mutex_lock(&i2cr->lock);
16953e89e3eSEddie James
17053e89e3eSEddie James ret = i2c_master_send(i2cr->client, (const char *)buf, sizeof(buf));
17153e89e3eSEddie James if (ret == sizeof(buf)) {
17253e89e3eSEddie James ret = i2cr_check_status(i2cr->client);
17353e89e3eSEddie James if (!ret)
17453e89e3eSEddie James trace_i2cr_write(i2cr->client, buf[0], data);
17553e89e3eSEddie James } else {
17653e89e3eSEddie James trace_i2cr_i2c_error(i2cr->client, buf[0], ret);
17753e89e3eSEddie James
17853e89e3eSEddie James if (ret >= 0)
17953e89e3eSEddie James ret = -EIO;
18053e89e3eSEddie James }
18153e89e3eSEddie James
18253e89e3eSEddie James mutex_unlock(&i2cr->lock);
18353e89e3eSEddie James return ret;
18453e89e3eSEddie James }
18553e89e3eSEddie James EXPORT_SYMBOL_GPL(fsi_master_i2cr_write);
18653e89e3eSEddie James
i2cr_read(struct fsi_master * master,int link,uint8_t id,uint32_t addr,void * val,size_t size)18753e89e3eSEddie James static int i2cr_read(struct fsi_master *master, int link, uint8_t id, uint32_t addr, void *val,
18853e89e3eSEddie James size_t size)
18953e89e3eSEddie James {
19053e89e3eSEddie James struct fsi_master_i2cr *i2cr = container_of(master, struct fsi_master_i2cr, master);
19153e89e3eSEddie James u64 data;
19253e89e3eSEddie James size_t i;
19353e89e3eSEddie James int ret;
19453e89e3eSEddie James
19553e89e3eSEddie James if (link || id || (addr & 0xffff0000) || !(size == 1 || size == 2 || size == 4))
19653e89e3eSEddie James return -EINVAL;
19753e89e3eSEddie James
19853e89e3eSEddie James /*
19953e89e3eSEddie James * The I2CR doesn't have CFAM or FSI slave address space - only the
20053e89e3eSEddie James * engines. In order for this to work with the FSI core, we need to
20153e89e3eSEddie James * emulate at minimum the CFAM config table so that the appropriate
20253e89e3eSEddie James * engines are discovered.
20353e89e3eSEddie James */
20453e89e3eSEddie James if (addr < 0xc00) {
20553e89e3eSEddie James if (addr > sizeof(i2cr_cfam) - 4)
20653e89e3eSEddie James addr = (addr & 0x3) + (sizeof(i2cr_cfam) - 4);
20753e89e3eSEddie James
20853e89e3eSEddie James memcpy(val, &i2cr_cfam[addr], size);
20953e89e3eSEddie James return 0;
21053e89e3eSEddie James }
21153e89e3eSEddie James
21253e89e3eSEddie James ret = fsi_master_i2cr_read(i2cr, I2CR_ADDRESS_CFAM(addr), &data);
21353e89e3eSEddie James if (ret)
21453e89e3eSEddie James return ret;
21553e89e3eSEddie James
21653e89e3eSEddie James /*
21753e89e3eSEddie James * FSI core expects up to 4 bytes BE back, while I2CR replied with LE
21853e89e3eSEddie James * bytes on the wire.
21953e89e3eSEddie James */
22053e89e3eSEddie James for (i = 0; i < size; ++i)
22153e89e3eSEddie James ((u8 *)val)[i] = ((u8 *)&data)[7 - i];
22253e89e3eSEddie James
22353e89e3eSEddie James return 0;
22453e89e3eSEddie James }
22553e89e3eSEddie James
i2cr_write(struct fsi_master * master,int link,uint8_t id,uint32_t addr,const void * val,size_t size)22653e89e3eSEddie James static int i2cr_write(struct fsi_master *master, int link, uint8_t id, uint32_t addr,
22753e89e3eSEddie James const void *val, size_t size)
22853e89e3eSEddie James {
22953e89e3eSEddie James struct fsi_master_i2cr *i2cr = container_of(master, struct fsi_master_i2cr, master);
23053e89e3eSEddie James u64 data = 0;
23153e89e3eSEddie James size_t i;
23253e89e3eSEddie James
23353e89e3eSEddie James if (link || id || (addr & 0xffff0000) || !(size == 1 || size == 2 || size == 4))
23453e89e3eSEddie James return -EINVAL;
23553e89e3eSEddie James
23653e89e3eSEddie James /* I2CR writes to CFAM or FSI slave address are a successful no-op. */
23753e89e3eSEddie James if (addr < 0xc00)
23853e89e3eSEddie James return 0;
23953e89e3eSEddie James
24053e89e3eSEddie James /*
24153e89e3eSEddie James * FSI core passes up to 4 bytes BE, while the I2CR expects LE bytes on
24253e89e3eSEddie James * the wire.
24353e89e3eSEddie James */
24453e89e3eSEddie James for (i = 0; i < size; ++i)
24553e89e3eSEddie James ((u8 *)&data)[7 - i] = ((u8 *)val)[i];
24653e89e3eSEddie James
24753e89e3eSEddie James return fsi_master_i2cr_write(i2cr, I2CR_ADDRESS_CFAM(addr), data);
24853e89e3eSEddie James }
24953e89e3eSEddie James
i2cr_release(struct device * dev)25053e89e3eSEddie James static void i2cr_release(struct device *dev)
25153e89e3eSEddie James {
25253e89e3eSEddie James struct fsi_master_i2cr *i2cr = to_fsi_master_i2cr(to_fsi_master(dev));
25353e89e3eSEddie James
25453e89e3eSEddie James of_node_put(dev->of_node);
25553e89e3eSEddie James
25653e89e3eSEddie James kfree(i2cr);
25753e89e3eSEddie James }
25853e89e3eSEddie James
i2cr_probe(struct i2c_client * client)25953e89e3eSEddie James static int i2cr_probe(struct i2c_client *client)
26053e89e3eSEddie James {
26153e89e3eSEddie James struct fsi_master_i2cr *i2cr;
26253e89e3eSEddie James int ret;
26353e89e3eSEddie James
26453e89e3eSEddie James i2cr = kzalloc(sizeof(*i2cr), GFP_KERNEL);
26553e89e3eSEddie James if (!i2cr)
26653e89e3eSEddie James return -ENOMEM;
26753e89e3eSEddie James
26853e89e3eSEddie James /* Only one I2CR on any given I2C bus (fixed I2C device address) */
26953e89e3eSEddie James i2cr->master.idx = client->adapter->nr;
27053e89e3eSEddie James dev_set_name(&i2cr->master.dev, "i2cr%d", i2cr->master.idx);
27153e89e3eSEddie James i2cr->master.dev.parent = &client->dev;
27253e89e3eSEddie James i2cr->master.dev.of_node = of_node_get(dev_of_node(&client->dev));
27353e89e3eSEddie James i2cr->master.dev.release = i2cr_release;
27453e89e3eSEddie James
27553e89e3eSEddie James i2cr->master.n_links = 1;
27653e89e3eSEddie James i2cr->master.read = i2cr_read;
27753e89e3eSEddie James i2cr->master.write = i2cr_write;
27853e89e3eSEddie James
27953e89e3eSEddie James mutex_init(&i2cr->lock);
28053e89e3eSEddie James i2cr->client = client;
28153e89e3eSEddie James
28253e89e3eSEddie James ret = fsi_master_register(&i2cr->master);
28353e89e3eSEddie James if (ret)
28453e89e3eSEddie James return ret;
28553e89e3eSEddie James
28653e89e3eSEddie James i2c_set_clientdata(client, i2cr);
28753e89e3eSEddie James return 0;
28853e89e3eSEddie James }
28953e89e3eSEddie James
i2cr_remove(struct i2c_client * client)29053e89e3eSEddie James static void i2cr_remove(struct i2c_client *client)
29153e89e3eSEddie James {
29253e89e3eSEddie James struct fsi_master_i2cr *i2cr = i2c_get_clientdata(client);
29353e89e3eSEddie James
29453e89e3eSEddie James fsi_master_unregister(&i2cr->master);
29553e89e3eSEddie James }
29653e89e3eSEddie James
29753e89e3eSEddie James static const struct of_device_id i2cr_ids[] = {
29853e89e3eSEddie James { .compatible = "ibm,i2cr-fsi-master" },
29953e89e3eSEddie James { }
30053e89e3eSEddie James };
30153e89e3eSEddie James MODULE_DEVICE_TABLE(of, i2cr_ids);
30253e89e3eSEddie James
30353e89e3eSEddie James static struct i2c_driver i2cr_driver = {
304*b587cb72SUwe Kleine-König .probe = i2cr_probe,
30553e89e3eSEddie James .remove = i2cr_remove,
30653e89e3eSEddie James .driver = {
30753e89e3eSEddie James .name = "fsi-master-i2cr",
30853e89e3eSEddie James .of_match_table = i2cr_ids,
30953e89e3eSEddie James },
31053e89e3eSEddie James };
31153e89e3eSEddie James
31253e89e3eSEddie James module_i2c_driver(i2cr_driver)
31353e89e3eSEddie James
31453e89e3eSEddie James MODULE_AUTHOR("Eddie James <eajames@linux.ibm.com>");
31553e89e3eSEddie James MODULE_DESCRIPTION("IBM I2C Responder virtual FSI master driver");
31653e89e3eSEddie James MODULE_LICENSE("GPL");
317