mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
255 lines
5.6 KiB
255 lines
5.6 KiB
// SPDX-License-Identifier: GPL-2.0+ |
|
// Copyright IBM Corp 2019 |
|
|
|
#include <linux/device.h> |
|
#include <linux/errno.h> |
|
#include <linux/fsi-occ.h> |
|
#include <linux/i2c.h> |
|
#include <linux/jiffies.h> |
|
#include <linux/module.h> |
|
#include <linux/sched.h> |
|
#include <asm/unaligned.h> |
|
|
|
#include "common.h" |
|
|
|
#define OCC_TIMEOUT_MS 1000 |
|
#define OCC_CMD_IN_PRG_WAIT_MS 50 |
|
|
|
/* OCB (on-chip control bridge - interface to OCC) registers */ |
|
#define OCB_DATA1 0x6B035 |
|
#define OCB_ADDR 0x6B070 |
|
#define OCB_DATA3 0x6B075 |
|
|
|
/* OCC SRAM address space */ |
|
#define OCC_SRAM_ADDR_CMD 0xFFFF6000 |
|
#define OCC_SRAM_ADDR_RESP 0xFFFF7000 |
|
|
|
#define OCC_DATA_ATTN 0x20010000 |
|
|
|
struct p8_i2c_occ { |
|
struct occ occ; |
|
struct i2c_client *client; |
|
}; |
|
|
|
#define to_p8_i2c_occ(x) container_of((x), struct p8_i2c_occ, occ) |
|
|
|
static int p8_i2c_occ_getscom(struct i2c_client *client, u32 address, u8 *data) |
|
{ |
|
ssize_t rc; |
|
__be64 buf; |
|
struct i2c_msg msgs[2]; |
|
|
|
/* p8 i2c slave requires shift */ |
|
address <<= 1; |
|
|
|
msgs[0].addr = client->addr; |
|
msgs[0].flags = client->flags & I2C_M_TEN; |
|
msgs[0].len = sizeof(u32); |
|
/* address is a scom address; bus-endian */ |
|
msgs[0].buf = (char *)&address; |
|
|
|
/* data from OCC is big-endian */ |
|
msgs[1].addr = client->addr; |
|
msgs[1].flags = (client->flags & I2C_M_TEN) | I2C_M_RD; |
|
msgs[1].len = sizeof(u64); |
|
msgs[1].buf = (char *)&buf; |
|
|
|
rc = i2c_transfer(client->adapter, msgs, 2); |
|
if (rc < 0) |
|
return rc; |
|
|
|
*(u64 *)data = be64_to_cpu(buf); |
|
|
|
return 0; |
|
} |
|
|
|
static int p8_i2c_occ_putscom(struct i2c_client *client, u32 address, u8 *data) |
|
{ |
|
u32 buf[3]; |
|
ssize_t rc; |
|
|
|
/* p8 i2c slave requires shift */ |
|
address <<= 1; |
|
|
|
/* address is bus-endian; data passed through from user as-is */ |
|
buf[0] = address; |
|
memcpy(&buf[1], &data[4], sizeof(u32)); |
|
memcpy(&buf[2], data, sizeof(u32)); |
|
|
|
rc = i2c_master_send(client, (const char *)buf, sizeof(buf)); |
|
if (rc < 0) |
|
return rc; |
|
else if (rc != sizeof(buf)) |
|
return -EIO; |
|
|
|
return 0; |
|
} |
|
|
|
static int p8_i2c_occ_putscom_u32(struct i2c_client *client, u32 address, |
|
u32 data0, u32 data1) |
|
{ |
|
u8 buf[8]; |
|
|
|
memcpy(buf, &data0, 4); |
|
memcpy(buf + 4, &data1, 4); |
|
|
|
return p8_i2c_occ_putscom(client, address, buf); |
|
} |
|
|
|
static int p8_i2c_occ_putscom_be(struct i2c_client *client, u32 address, |
|
u8 *data) |
|
{ |
|
__be32 data0, data1; |
|
|
|
memcpy(&data0, data, 4); |
|
memcpy(&data1, data + 4, 4); |
|
|
|
return p8_i2c_occ_putscom_u32(client, address, be32_to_cpu(data0), |
|
be32_to_cpu(data1)); |
|
} |
|
|
|
static int p8_i2c_occ_send_cmd(struct occ *occ, u8 *cmd) |
|
{ |
|
int i, rc; |
|
unsigned long start; |
|
u16 data_length; |
|
const unsigned long timeout = msecs_to_jiffies(OCC_TIMEOUT_MS); |
|
const long wait_time = msecs_to_jiffies(OCC_CMD_IN_PRG_WAIT_MS); |
|
struct p8_i2c_occ *ctx = to_p8_i2c_occ(occ); |
|
struct i2c_client *client = ctx->client; |
|
struct occ_response *resp = &occ->resp; |
|
|
|
start = jiffies; |
|
|
|
/* set sram address for command */ |
|
rc = p8_i2c_occ_putscom_u32(client, OCB_ADDR, OCC_SRAM_ADDR_CMD, 0); |
|
if (rc) |
|
return rc; |
|
|
|
/* write command (expected to already be BE), we need bus-endian... */ |
|
rc = p8_i2c_occ_putscom_be(client, OCB_DATA3, cmd); |
|
if (rc) |
|
return rc; |
|
|
|
/* trigger OCC attention */ |
|
rc = p8_i2c_occ_putscom_u32(client, OCB_DATA1, OCC_DATA_ATTN, 0); |
|
if (rc) |
|
return rc; |
|
|
|
do { |
|
/* set sram address for response */ |
|
rc = p8_i2c_occ_putscom_u32(client, OCB_ADDR, |
|
OCC_SRAM_ADDR_RESP, 0); |
|
if (rc) |
|
return rc; |
|
|
|
rc = p8_i2c_occ_getscom(client, OCB_DATA3, (u8 *)resp); |
|
if (rc) |
|
return rc; |
|
|
|
/* wait for OCC */ |
|
if (resp->return_status == OCC_RESP_CMD_IN_PRG) { |
|
rc = -EALREADY; |
|
|
|
if (time_after(jiffies, start + timeout)) |
|
break; |
|
|
|
set_current_state(TASK_INTERRUPTIBLE); |
|
schedule_timeout(wait_time); |
|
} |
|
} while (rc); |
|
|
|
/* check the OCC response */ |
|
switch (resp->return_status) { |
|
case OCC_RESP_CMD_IN_PRG: |
|
rc = -ETIMEDOUT; |
|
break; |
|
case OCC_RESP_SUCCESS: |
|
rc = 0; |
|
break; |
|
case OCC_RESP_CMD_INVAL: |
|
case OCC_RESP_CMD_LEN_INVAL: |
|
case OCC_RESP_DATA_INVAL: |
|
case OCC_RESP_CHKSUM_ERR: |
|
rc = -EINVAL; |
|
break; |
|
case OCC_RESP_INT_ERR: |
|
case OCC_RESP_BAD_STATE: |
|
case OCC_RESP_CRIT_EXCEPT: |
|
case OCC_RESP_CRIT_INIT: |
|
case OCC_RESP_CRIT_WATCHDOG: |
|
case OCC_RESP_CRIT_OCB: |
|
case OCC_RESP_CRIT_HW: |
|
rc = -EREMOTEIO; |
|
break; |
|
default: |
|
rc = -EPROTO; |
|
} |
|
|
|
if (rc < 0) |
|
return rc; |
|
|
|
data_length = get_unaligned_be16(&resp->data_length); |
|
if (data_length > OCC_RESP_DATA_BYTES) |
|
return -EMSGSIZE; |
|
|
|
/* fetch the rest of the response data */ |
|
for (i = 8; i < data_length + 7; i += 8) { |
|
rc = p8_i2c_occ_getscom(client, OCB_DATA3, ((u8 *)resp) + i); |
|
if (rc) |
|
return rc; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int p8_i2c_occ_probe(struct i2c_client *client) |
|
{ |
|
struct occ *occ; |
|
struct p8_i2c_occ *ctx = devm_kzalloc(&client->dev, sizeof(*ctx), |
|
GFP_KERNEL); |
|
if (!ctx) |
|
return -ENOMEM; |
|
|
|
ctx->client = client; |
|
occ = &ctx->occ; |
|
occ->bus_dev = &client->dev; |
|
dev_set_drvdata(&client->dev, occ); |
|
|
|
occ->powr_sample_time_us = 250; |
|
occ->poll_cmd_data = 0x10; /* P8 OCC poll data */ |
|
occ->send_cmd = p8_i2c_occ_send_cmd; |
|
|
|
return occ_setup(occ, "p8_occ"); |
|
} |
|
|
|
static int p8_i2c_occ_remove(struct i2c_client *client) |
|
{ |
|
struct occ *occ = dev_get_drvdata(&client->dev); |
|
|
|
occ_shutdown(occ); |
|
|
|
return 0; |
|
} |
|
|
|
static const struct of_device_id p8_i2c_occ_of_match[] = { |
|
{ .compatible = "ibm,p8-occ-hwmon" }, |
|
{} |
|
}; |
|
MODULE_DEVICE_TABLE(of, p8_i2c_occ_of_match); |
|
|
|
static struct i2c_driver p8_i2c_occ_driver = { |
|
.class = I2C_CLASS_HWMON, |
|
.driver = { |
|
.name = "occ-hwmon", |
|
.of_match_table = p8_i2c_occ_of_match, |
|
}, |
|
.probe_new = p8_i2c_occ_probe, |
|
.remove = p8_i2c_occ_remove, |
|
}; |
|
|
|
module_i2c_driver(p8_i2c_occ_driver); |
|
|
|
MODULE_AUTHOR("Eddie James <[email protected]>"); |
|
MODULE_DESCRIPTION("BMC P8 OCC hwmon driver"); |
|
MODULE_LICENSE("GPL");
|
|
|