16b99076cSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2680ca6dcSChristopher Bostic /*
3680ca6dcSChristopher Bostic * SCOM FSI Client device driver
4680ca6dcSChristopher Bostic *
5680ca6dcSChristopher Bostic * Copyright (C) IBM Corporation 2016
6680ca6dcSChristopher Bostic */
7680ca6dcSChristopher Bostic
8680ca6dcSChristopher Bostic #include <linux/fsi.h>
9680ca6dcSChristopher Bostic #include <linux/module.h>
10680ca6dcSChristopher Bostic #include <linux/cdev.h>
11680ca6dcSChristopher Bostic #include <linux/delay.h>
12680ca6dcSChristopher Bostic #include <linux/fs.h>
13*c21d322eSEddie James #include <linux/mod_devicetable.h>
14680ca6dcSChristopher Bostic #include <linux/uaccess.h>
15680ca6dcSChristopher Bostic #include <linux/slab.h>
16680ca6dcSChristopher Bostic #include <linux/list.h>
17680ca6dcSChristopher Bostic
186b293258SBenjamin Herrenschmidt #include <uapi/linux/fsi.h>
196b293258SBenjamin Herrenschmidt
20680ca6dcSChristopher Bostic #define FSI_ENGID_SCOM 0x5
21680ca6dcSChristopher Bostic
22680ca6dcSChristopher Bostic /* SCOM engine register set */
23680ca6dcSChristopher Bostic #define SCOM_DATA0_REG 0x00
24680ca6dcSChristopher Bostic #define SCOM_DATA1_REG 0x04
25680ca6dcSChristopher Bostic #define SCOM_CMD_REG 0x08
26f1433044SBenjamin Herrenschmidt #define SCOM_FSI2PIB_RESET_REG 0x18
27f1433044SBenjamin Herrenschmidt #define SCOM_STATUS_REG 0x1C /* Read */
28f1433044SBenjamin Herrenschmidt #define SCOM_PIB_RESET_REG 0x1C /* Write */
29680ca6dcSChristopher Bostic
30f1433044SBenjamin Herrenschmidt /* Command register */
31680ca6dcSChristopher Bostic #define SCOM_WRITE_CMD 0x80000000
32f1433044SBenjamin Herrenschmidt #define SCOM_READ_CMD 0x00000000
33f1433044SBenjamin Herrenschmidt
34f1433044SBenjamin Herrenschmidt /* Status register bits */
35f1433044SBenjamin Herrenschmidt #define SCOM_STATUS_ERR_SUMMARY 0x80000000
36f1433044SBenjamin Herrenschmidt #define SCOM_STATUS_PROTECTION 0x01000000
376b293258SBenjamin Herrenschmidt #define SCOM_STATUS_PARITY 0x04000000
38f1433044SBenjamin Herrenschmidt #define SCOM_STATUS_PIB_ABORT 0x00100000
39f1433044SBenjamin Herrenschmidt #define SCOM_STATUS_PIB_RESP_MASK 0x00007000
40f1433044SBenjamin Herrenschmidt #define SCOM_STATUS_PIB_RESP_SHIFT 12
41f1433044SBenjamin Herrenschmidt
42a5c317daSEddie James #define SCOM_STATUS_FSI2PIB_ERROR (SCOM_STATUS_PROTECTION | \
436b293258SBenjamin Herrenschmidt SCOM_STATUS_PARITY | \
44a5c317daSEddie James SCOM_STATUS_PIB_ABORT)
45a5c317daSEddie James #define SCOM_STATUS_ANY_ERR (SCOM_STATUS_FSI2PIB_ERROR | \
46f1433044SBenjamin Herrenschmidt SCOM_STATUS_PIB_RESP_MASK)
476b293258SBenjamin Herrenschmidt /* SCOM address encodings */
486b293258SBenjamin Herrenschmidt #define XSCOM_ADDR_IND_FLAG BIT_ULL(63)
496b293258SBenjamin Herrenschmidt #define XSCOM_ADDR_INF_FORM1 BIT_ULL(60)
506b293258SBenjamin Herrenschmidt
516b293258SBenjamin Herrenschmidt /* SCOM indirect stuff */
526b293258SBenjamin Herrenschmidt #define XSCOM_ADDR_DIRECT_PART 0x7fffffffull
536b293258SBenjamin Herrenschmidt #define XSCOM_ADDR_INDIRECT_PART 0x000fffff00000000ull
546b293258SBenjamin Herrenschmidt #define XSCOM_DATA_IND_READ BIT_ULL(63)
556b293258SBenjamin Herrenschmidt #define XSCOM_DATA_IND_COMPLETE BIT_ULL(31)
566b293258SBenjamin Herrenschmidt #define XSCOM_DATA_IND_ERR_MASK 0x70000000ull
576b293258SBenjamin Herrenschmidt #define XSCOM_DATA_IND_ERR_SHIFT 28
586b293258SBenjamin Herrenschmidt #define XSCOM_DATA_IND_DATA 0x0000ffffull
596b293258SBenjamin Herrenschmidt #define XSCOM_DATA_IND_FORM1_DATA 0x000fffffffffffffull
606b293258SBenjamin Herrenschmidt #define XSCOM_ADDR_FORM1_LOW 0x000ffffffffull
616b293258SBenjamin Herrenschmidt #define XSCOM_ADDR_FORM1_HI 0xfff00000000ull
626b293258SBenjamin Herrenschmidt #define XSCOM_ADDR_FORM1_HI_SHIFT 20
636b293258SBenjamin Herrenschmidt
646b293258SBenjamin Herrenschmidt /* Retries */
656b293258SBenjamin Herrenschmidt #define SCOM_MAX_IND_RETRIES 10 /* Retries indirect not ready */
66680ca6dcSChristopher Bostic
67680ca6dcSChristopher Bostic struct scom_device {
68680ca6dcSChristopher Bostic struct list_head link;
69680ca6dcSChristopher Bostic struct fsi_device *fsi_dev;
70d8f45876SBenjamin Herrenschmidt struct device dev;
71d8f45876SBenjamin Herrenschmidt struct cdev cdev;
72162c3946SBenjamin Herrenschmidt struct mutex lock;
73d8f45876SBenjamin Herrenschmidt bool dead;
74680ca6dcSChristopher Bostic };
75680ca6dcSChristopher Bostic
__put_scom(struct scom_device * scom_dev,uint64_t value,uint32_t addr,uint32_t * status)766b293258SBenjamin Herrenschmidt static int __put_scom(struct scom_device *scom_dev, uint64_t value,
776b293258SBenjamin Herrenschmidt uint32_t addr, uint32_t *status)
78680ca6dcSChristopher Bostic {
796b293258SBenjamin Herrenschmidt __be32 data, raw_status;
80680ca6dcSChristopher Bostic int rc;
81680ca6dcSChristopher Bostic
82680ca6dcSChristopher Bostic data = cpu_to_be32((value >> 32) & 0xffffffff);
83680ca6dcSChristopher Bostic rc = fsi_device_write(scom_dev->fsi_dev, SCOM_DATA0_REG, &data,
84680ca6dcSChristopher Bostic sizeof(uint32_t));
85680ca6dcSChristopher Bostic if (rc)
866b293258SBenjamin Herrenschmidt return rc;
87680ca6dcSChristopher Bostic
88680ca6dcSChristopher Bostic data = cpu_to_be32(value & 0xffffffff);
89680ca6dcSChristopher Bostic rc = fsi_device_write(scom_dev->fsi_dev, SCOM_DATA1_REG, &data,
90680ca6dcSChristopher Bostic sizeof(uint32_t));
91680ca6dcSChristopher Bostic if (rc)
926b293258SBenjamin Herrenschmidt return rc;
93680ca6dcSChristopher Bostic
94680ca6dcSChristopher Bostic data = cpu_to_be32(SCOM_WRITE_CMD | addr);
95162c3946SBenjamin Herrenschmidt rc = fsi_device_write(scom_dev->fsi_dev, SCOM_CMD_REG, &data,
96680ca6dcSChristopher Bostic sizeof(uint32_t));
976b293258SBenjamin Herrenschmidt if (rc)
98162c3946SBenjamin Herrenschmidt return rc;
996b293258SBenjamin Herrenschmidt rc = fsi_device_read(scom_dev->fsi_dev, SCOM_STATUS_REG, &raw_status,
1006b293258SBenjamin Herrenschmidt sizeof(uint32_t));
1016b293258SBenjamin Herrenschmidt if (rc)
1026b293258SBenjamin Herrenschmidt return rc;
1036b293258SBenjamin Herrenschmidt *status = be32_to_cpu(raw_status);
1046b293258SBenjamin Herrenschmidt
1056b293258SBenjamin Herrenschmidt return 0;
106680ca6dcSChristopher Bostic }
107680ca6dcSChristopher Bostic
__get_scom(struct scom_device * scom_dev,uint64_t * value,uint32_t addr,uint32_t * status)1086b293258SBenjamin Herrenschmidt static int __get_scom(struct scom_device *scom_dev, uint64_t *value,
1096b293258SBenjamin Herrenschmidt uint32_t addr, uint32_t *status)
110680ca6dcSChristopher Bostic {
1116b293258SBenjamin Herrenschmidt __be32 data, raw_status;
112680ca6dcSChristopher Bostic int rc;
113680ca6dcSChristopher Bostic
114162c3946SBenjamin Herrenschmidt
115680ca6dcSChristopher Bostic *value = 0ULL;
116f1433044SBenjamin Herrenschmidt data = cpu_to_be32(SCOM_READ_CMD | addr);
117680ca6dcSChristopher Bostic rc = fsi_device_write(scom_dev->fsi_dev, SCOM_CMD_REG, &data,
118680ca6dcSChristopher Bostic sizeof(uint32_t));
119680ca6dcSChristopher Bostic if (rc)
1206b293258SBenjamin Herrenschmidt return rc;
1216b293258SBenjamin Herrenschmidt rc = fsi_device_read(scom_dev->fsi_dev, SCOM_STATUS_REG, &raw_status,
122680ca6dcSChristopher Bostic sizeof(uint32_t));
123680ca6dcSChristopher Bostic if (rc)
1246b293258SBenjamin Herrenschmidt return rc;
125680ca6dcSChristopher Bostic
1266b293258SBenjamin Herrenschmidt /*
1276b293258SBenjamin Herrenschmidt * Read the data registers even on error, so we don't have
1286b293258SBenjamin Herrenschmidt * to interpret the status register here.
1296b293258SBenjamin Herrenschmidt */
1306b293258SBenjamin Herrenschmidt rc = fsi_device_read(scom_dev->fsi_dev, SCOM_DATA0_REG, &data,
131680ca6dcSChristopher Bostic sizeof(uint32_t));
132680ca6dcSChristopher Bostic if (rc)
1336b293258SBenjamin Herrenschmidt return rc;
1346b293258SBenjamin Herrenschmidt *value |= (uint64_t)be32_to_cpu(data) << 32;
1356b293258SBenjamin Herrenschmidt rc = fsi_device_read(scom_dev->fsi_dev, SCOM_DATA1_REG, &data,
1366b293258SBenjamin Herrenschmidt sizeof(uint32_t));
1376b293258SBenjamin Herrenschmidt if (rc)
1386b293258SBenjamin Herrenschmidt return rc;
1396b293258SBenjamin Herrenschmidt *value |= be32_to_cpu(data);
1406b293258SBenjamin Herrenschmidt *status = be32_to_cpu(raw_status);
141680ca6dcSChristopher Bostic
1426b293258SBenjamin Herrenschmidt return rc;
1436b293258SBenjamin Herrenschmidt }
1446b293258SBenjamin Herrenschmidt
put_indirect_scom_form0(struct scom_device * scom,uint64_t value,uint64_t addr,uint32_t * status)1456b293258SBenjamin Herrenschmidt static int put_indirect_scom_form0(struct scom_device *scom, uint64_t value,
1466b293258SBenjamin Herrenschmidt uint64_t addr, uint32_t *status)
1476b293258SBenjamin Herrenschmidt {
1486b293258SBenjamin Herrenschmidt uint64_t ind_data, ind_addr;
149ab1b7915SJoel Stanley int rc, err;
1506b293258SBenjamin Herrenschmidt
1516b293258SBenjamin Herrenschmidt if (value & ~XSCOM_DATA_IND_DATA)
1526b293258SBenjamin Herrenschmidt return -EINVAL;
1536b293258SBenjamin Herrenschmidt
1546b293258SBenjamin Herrenschmidt ind_addr = addr & XSCOM_ADDR_DIRECT_PART;
1556b293258SBenjamin Herrenschmidt ind_data = (addr & XSCOM_ADDR_INDIRECT_PART) | value;
1566b293258SBenjamin Herrenschmidt rc = __put_scom(scom, ind_data, ind_addr, status);
1576b293258SBenjamin Herrenschmidt if (rc || (*status & SCOM_STATUS_ANY_ERR))
1586b293258SBenjamin Herrenschmidt return rc;
1596b293258SBenjamin Herrenschmidt
1606b293258SBenjamin Herrenschmidt rc = __get_scom(scom, &ind_data, addr, status);
1616b293258SBenjamin Herrenschmidt if (rc || (*status & SCOM_STATUS_ANY_ERR))
1626b293258SBenjamin Herrenschmidt return rc;
1636b293258SBenjamin Herrenschmidt
1646b293258SBenjamin Herrenschmidt err = (ind_data & XSCOM_DATA_IND_ERR_MASK) >> XSCOM_DATA_IND_ERR_SHIFT;
1656b293258SBenjamin Herrenschmidt *status = err << SCOM_STATUS_PIB_RESP_SHIFT;
1666b293258SBenjamin Herrenschmidt
167ab1b7915SJoel Stanley return 0;
1686b293258SBenjamin Herrenschmidt }
1696b293258SBenjamin Herrenschmidt
put_indirect_scom_form1(struct scom_device * scom,uint64_t value,uint64_t addr,uint32_t * status)1706b293258SBenjamin Herrenschmidt static int put_indirect_scom_form1(struct scom_device *scom, uint64_t value,
1716b293258SBenjamin Herrenschmidt uint64_t addr, uint32_t *status)
1726b293258SBenjamin Herrenschmidt {
1736b293258SBenjamin Herrenschmidt uint64_t ind_data, ind_addr;
1746b293258SBenjamin Herrenschmidt
1756b293258SBenjamin Herrenschmidt if (value & ~XSCOM_DATA_IND_FORM1_DATA)
1766b293258SBenjamin Herrenschmidt return -EINVAL;
1776b293258SBenjamin Herrenschmidt
1786b293258SBenjamin Herrenschmidt ind_addr = addr & XSCOM_ADDR_FORM1_LOW;
1796b293258SBenjamin Herrenschmidt ind_data = value | (addr & XSCOM_ADDR_FORM1_HI) << XSCOM_ADDR_FORM1_HI_SHIFT;
1806b293258SBenjamin Herrenschmidt return __put_scom(scom, ind_data, ind_addr, status);
1816b293258SBenjamin Herrenschmidt }
1826b293258SBenjamin Herrenschmidt
get_indirect_scom_form0(struct scom_device * scom,uint64_t * value,uint64_t addr,uint32_t * status)1836b293258SBenjamin Herrenschmidt static int get_indirect_scom_form0(struct scom_device *scom, uint64_t *value,
1846b293258SBenjamin Herrenschmidt uint64_t addr, uint32_t *status)
1856b293258SBenjamin Herrenschmidt {
1866b293258SBenjamin Herrenschmidt uint64_t ind_data, ind_addr;
187ab1b7915SJoel Stanley int rc, err;
1886b293258SBenjamin Herrenschmidt
1896b293258SBenjamin Herrenschmidt ind_addr = addr & XSCOM_ADDR_DIRECT_PART;
1906b293258SBenjamin Herrenschmidt ind_data = (addr & XSCOM_ADDR_INDIRECT_PART) | XSCOM_DATA_IND_READ;
1916b293258SBenjamin Herrenschmidt rc = __put_scom(scom, ind_data, ind_addr, status);
1926b293258SBenjamin Herrenschmidt if (rc || (*status & SCOM_STATUS_ANY_ERR))
1936b293258SBenjamin Herrenschmidt return rc;
1946b293258SBenjamin Herrenschmidt
1956b293258SBenjamin Herrenschmidt rc = __get_scom(scom, &ind_data, addr, status);
1966b293258SBenjamin Herrenschmidt if (rc || (*status & SCOM_STATUS_ANY_ERR))
1976b293258SBenjamin Herrenschmidt return rc;
1986b293258SBenjamin Herrenschmidt
1996b293258SBenjamin Herrenschmidt err = (ind_data & XSCOM_DATA_IND_ERR_MASK) >> XSCOM_DATA_IND_ERR_SHIFT;
2006b293258SBenjamin Herrenschmidt *status = err << SCOM_STATUS_PIB_RESP_SHIFT;
2016b293258SBenjamin Herrenschmidt *value = ind_data & XSCOM_DATA_IND_DATA;
2026b293258SBenjamin Herrenschmidt
2036b293258SBenjamin Herrenschmidt return 0;
2046b293258SBenjamin Herrenschmidt }
2056b293258SBenjamin Herrenschmidt
raw_put_scom(struct scom_device * scom,uint64_t value,uint64_t addr,uint32_t * status)2066b293258SBenjamin Herrenschmidt static int raw_put_scom(struct scom_device *scom, uint64_t value,
2076b293258SBenjamin Herrenschmidt uint64_t addr, uint32_t *status)
2086b293258SBenjamin Herrenschmidt {
2096b293258SBenjamin Herrenschmidt if (addr & XSCOM_ADDR_IND_FLAG) {
2106b293258SBenjamin Herrenschmidt if (addr & XSCOM_ADDR_INF_FORM1)
2116b293258SBenjamin Herrenschmidt return put_indirect_scom_form1(scom, value, addr, status);
2126b293258SBenjamin Herrenschmidt else
2136b293258SBenjamin Herrenschmidt return put_indirect_scom_form0(scom, value, addr, status);
2146b293258SBenjamin Herrenschmidt } else
2156b293258SBenjamin Herrenschmidt return __put_scom(scom, value, addr, status);
2166b293258SBenjamin Herrenschmidt }
2176b293258SBenjamin Herrenschmidt
raw_get_scom(struct scom_device * scom,uint64_t * value,uint64_t addr,uint32_t * status)2186b293258SBenjamin Herrenschmidt static int raw_get_scom(struct scom_device *scom, uint64_t *value,
2196b293258SBenjamin Herrenschmidt uint64_t addr, uint32_t *status)
2206b293258SBenjamin Herrenschmidt {
2216b293258SBenjamin Herrenschmidt if (addr & XSCOM_ADDR_IND_FLAG) {
2226b293258SBenjamin Herrenschmidt if (addr & XSCOM_ADDR_INF_FORM1)
2236b293258SBenjamin Herrenschmidt return -ENXIO;
2246b293258SBenjamin Herrenschmidt return get_indirect_scom_form0(scom, value, addr, status);
2256b293258SBenjamin Herrenschmidt } else
2266b293258SBenjamin Herrenschmidt return __get_scom(scom, value, addr, status);
2276b293258SBenjamin Herrenschmidt }
2286b293258SBenjamin Herrenschmidt
handle_fsi2pib_status(struct scom_device * scom,uint32_t status)2296b293258SBenjamin Herrenschmidt static int handle_fsi2pib_status(struct scom_device *scom, uint32_t status)
2306b293258SBenjamin Herrenschmidt {
2316b293258SBenjamin Herrenschmidt uint32_t dummy = -1;
2326b293258SBenjamin Herrenschmidt
233a5c317daSEddie James if (status & SCOM_STATUS_FSI2PIB_ERROR)
2346b293258SBenjamin Herrenschmidt fsi_device_write(scom->fsi_dev, SCOM_FSI2PIB_RESET_REG, &dummy,
2356b293258SBenjamin Herrenschmidt sizeof(uint32_t));
236a5c317daSEddie James
237a5c317daSEddie James if (status & SCOM_STATUS_PROTECTION)
238a5c317daSEddie James return -EPERM;
239a5c317daSEddie James if (status & SCOM_STATUS_PARITY)
2406b293258SBenjamin Herrenschmidt return -EIO;
241f72ddbe1SJoel Stanley
2426b293258SBenjamin Herrenschmidt if (status & SCOM_STATUS_PIB_ABORT)
2436b293258SBenjamin Herrenschmidt return -EBUSY;
2446b293258SBenjamin Herrenschmidt return 0;
2456b293258SBenjamin Herrenschmidt }
2466b293258SBenjamin Herrenschmidt
handle_pib_status(struct scom_device * scom,uint8_t status)2476b293258SBenjamin Herrenschmidt static int handle_pib_status(struct scom_device *scom, uint8_t status)
2486b293258SBenjamin Herrenschmidt {
2496b293258SBenjamin Herrenschmidt uint32_t dummy = -1;
2506b293258SBenjamin Herrenschmidt
2516b293258SBenjamin Herrenschmidt if (status == SCOM_PIB_SUCCESS)
2526b293258SBenjamin Herrenschmidt return 0;
2536b293258SBenjamin Herrenschmidt if (status == SCOM_PIB_BLOCKED)
2546b293258SBenjamin Herrenschmidt return -EBUSY;
2556b293258SBenjamin Herrenschmidt
2566b293258SBenjamin Herrenschmidt /* Reset the bridge */
2576b293258SBenjamin Herrenschmidt fsi_device_write(scom->fsi_dev, SCOM_FSI2PIB_RESET_REG, &dummy,
2586b293258SBenjamin Herrenschmidt sizeof(uint32_t));
2596b293258SBenjamin Herrenschmidt
2606b293258SBenjamin Herrenschmidt switch(status) {
2616b293258SBenjamin Herrenschmidt case SCOM_PIB_OFFLINE:
2626b293258SBenjamin Herrenschmidt return -ENODEV;
2636b293258SBenjamin Herrenschmidt case SCOM_PIB_BAD_ADDR:
2646b293258SBenjamin Herrenschmidt return -ENXIO;
2656b293258SBenjamin Herrenschmidt case SCOM_PIB_TIMEOUT:
2666b293258SBenjamin Herrenschmidt return -ETIMEDOUT;
2676b293258SBenjamin Herrenschmidt case SCOM_PIB_PARTIAL:
2686b293258SBenjamin Herrenschmidt case SCOM_PIB_CLK_ERR:
2696b293258SBenjamin Herrenschmidt case SCOM_PIB_PARITY_ERR:
2706b293258SBenjamin Herrenschmidt default:
2716b293258SBenjamin Herrenschmidt return -EIO;
2726b293258SBenjamin Herrenschmidt }
2736b293258SBenjamin Herrenschmidt }
2746b293258SBenjamin Herrenschmidt
put_scom(struct scom_device * scom,uint64_t value,uint64_t addr)2756b293258SBenjamin Herrenschmidt static int put_scom(struct scom_device *scom, uint64_t value,
2766b293258SBenjamin Herrenschmidt uint64_t addr)
2776b293258SBenjamin Herrenschmidt {
278f72ddbe1SJoel Stanley uint32_t status;
279f72ddbe1SJoel Stanley int rc;
2806b293258SBenjamin Herrenschmidt
2816b293258SBenjamin Herrenschmidt rc = raw_put_scom(scom, value, addr, &status);
282d46fddd5SJoel Stanley if (rc)
2836b293258SBenjamin Herrenschmidt return rc;
284f72ddbe1SJoel Stanley
2856b293258SBenjamin Herrenschmidt rc = handle_fsi2pib_status(scom, status);
286f72ddbe1SJoel Stanley if (rc)
287f72ddbe1SJoel Stanley return rc;
288f72ddbe1SJoel Stanley
289f72ddbe1SJoel Stanley return handle_pib_status(scom,
2906b293258SBenjamin Herrenschmidt (status & SCOM_STATUS_PIB_RESP_MASK)
2916b293258SBenjamin Herrenschmidt >> SCOM_STATUS_PIB_RESP_SHIFT);
2926b293258SBenjamin Herrenschmidt }
2936b293258SBenjamin Herrenschmidt
get_scom(struct scom_device * scom,uint64_t * value,uint64_t addr)2946b293258SBenjamin Herrenschmidt static int get_scom(struct scom_device *scom, uint64_t *value,
2956b293258SBenjamin Herrenschmidt uint64_t addr)
2966b293258SBenjamin Herrenschmidt {
297f72ddbe1SJoel Stanley uint32_t status;
298f72ddbe1SJoel Stanley int rc;
2996b293258SBenjamin Herrenschmidt
3006b293258SBenjamin Herrenschmidt rc = raw_get_scom(scom, value, addr, &status);
301d46fddd5SJoel Stanley if (rc)
3026b293258SBenjamin Herrenschmidt return rc;
303f72ddbe1SJoel Stanley
3046b293258SBenjamin Herrenschmidt rc = handle_fsi2pib_status(scom, status);
305f72ddbe1SJoel Stanley if (rc)
306f72ddbe1SJoel Stanley return rc;
307f72ddbe1SJoel Stanley
308f72ddbe1SJoel Stanley return handle_pib_status(scom,
3096b293258SBenjamin Herrenschmidt (status & SCOM_STATUS_PIB_RESP_MASK)
3106b293258SBenjamin Herrenschmidt >> SCOM_STATUS_PIB_RESP_SHIFT);
311680ca6dcSChristopher Bostic }
312680ca6dcSChristopher Bostic
scom_read(struct file * filep,char __user * buf,size_t len,loff_t * offset)313680ca6dcSChristopher Bostic static ssize_t scom_read(struct file *filep, char __user *buf, size_t len,
314680ca6dcSChristopher Bostic loff_t *offset)
315680ca6dcSChristopher Bostic {
316d8f45876SBenjamin Herrenschmidt struct scom_device *scom = filep->private_data;
317680ca6dcSChristopher Bostic struct device *dev = &scom->fsi_dev->dev;
318680ca6dcSChristopher Bostic uint64_t val;
3196b293258SBenjamin Herrenschmidt int rc;
320680ca6dcSChristopher Bostic
321680ca6dcSChristopher Bostic if (len != sizeof(uint64_t))
322680ca6dcSChristopher Bostic return -EINVAL;
323680ca6dcSChristopher Bostic
3246b293258SBenjamin Herrenschmidt mutex_lock(&scom->lock);
325d8f45876SBenjamin Herrenschmidt if (scom->dead)
326d8f45876SBenjamin Herrenschmidt rc = -ENODEV;
327d8f45876SBenjamin Herrenschmidt else
328680ca6dcSChristopher Bostic rc = get_scom(scom, &val, *offset);
3296b293258SBenjamin Herrenschmidt mutex_unlock(&scom->lock);
330680ca6dcSChristopher Bostic if (rc) {
331680ca6dcSChristopher Bostic dev_dbg(dev, "get_scom fail:%d\n", rc);
332680ca6dcSChristopher Bostic return rc;
333680ca6dcSChristopher Bostic }
334680ca6dcSChristopher Bostic
335680ca6dcSChristopher Bostic rc = copy_to_user(buf, &val, len);
336680ca6dcSChristopher Bostic if (rc)
337680ca6dcSChristopher Bostic dev_dbg(dev, "copy to user failed:%d\n", rc);
338680ca6dcSChristopher Bostic
339680ca6dcSChristopher Bostic return rc ? rc : len;
340680ca6dcSChristopher Bostic }
341680ca6dcSChristopher Bostic
scom_write(struct file * filep,const char __user * buf,size_t len,loff_t * offset)342680ca6dcSChristopher Bostic static ssize_t scom_write(struct file *filep, const char __user *buf,
343680ca6dcSChristopher Bostic size_t len, loff_t *offset)
344680ca6dcSChristopher Bostic {
345680ca6dcSChristopher Bostic int rc;
346d8f45876SBenjamin Herrenschmidt struct scom_device *scom = filep->private_data;
347680ca6dcSChristopher Bostic struct device *dev = &scom->fsi_dev->dev;
348680ca6dcSChristopher Bostic uint64_t val;
349680ca6dcSChristopher Bostic
350680ca6dcSChristopher Bostic if (len != sizeof(uint64_t))
351680ca6dcSChristopher Bostic return -EINVAL;
352680ca6dcSChristopher Bostic
353680ca6dcSChristopher Bostic rc = copy_from_user(&val, buf, len);
354680ca6dcSChristopher Bostic if (rc) {
355680ca6dcSChristopher Bostic dev_dbg(dev, "copy from user failed:%d\n", rc);
356680ca6dcSChristopher Bostic return -EINVAL;
357680ca6dcSChristopher Bostic }
358680ca6dcSChristopher Bostic
3596b293258SBenjamin Herrenschmidt mutex_lock(&scom->lock);
360d8f45876SBenjamin Herrenschmidt if (scom->dead)
361d8f45876SBenjamin Herrenschmidt rc = -ENODEV;
362d8f45876SBenjamin Herrenschmidt else
363680ca6dcSChristopher Bostic rc = put_scom(scom, val, *offset);
3646b293258SBenjamin Herrenschmidt mutex_unlock(&scom->lock);
365680ca6dcSChristopher Bostic if (rc) {
366680ca6dcSChristopher Bostic dev_dbg(dev, "put_scom failed with:%d\n", rc);
367680ca6dcSChristopher Bostic return rc;
368680ca6dcSChristopher Bostic }
369680ca6dcSChristopher Bostic
370680ca6dcSChristopher Bostic return len;
371680ca6dcSChristopher Bostic }
372680ca6dcSChristopher Bostic
scom_llseek(struct file * file,loff_t offset,int whence)373680ca6dcSChristopher Bostic static loff_t scom_llseek(struct file *file, loff_t offset, int whence)
374680ca6dcSChristopher Bostic {
375680ca6dcSChristopher Bostic switch (whence) {
376680ca6dcSChristopher Bostic case SEEK_CUR:
377680ca6dcSChristopher Bostic break;
378680ca6dcSChristopher Bostic case SEEK_SET:
379680ca6dcSChristopher Bostic file->f_pos = offset;
380680ca6dcSChristopher Bostic break;
381680ca6dcSChristopher Bostic default:
382680ca6dcSChristopher Bostic return -EINVAL;
383680ca6dcSChristopher Bostic }
384680ca6dcSChristopher Bostic
385680ca6dcSChristopher Bostic return offset;
386680ca6dcSChristopher Bostic }
387680ca6dcSChristopher Bostic
raw_convert_status(struct scom_access * acc,uint32_t status)3886b293258SBenjamin Herrenschmidt static void raw_convert_status(struct scom_access *acc, uint32_t status)
3896b293258SBenjamin Herrenschmidt {
3906b293258SBenjamin Herrenschmidt acc->pib_status = (status & SCOM_STATUS_PIB_RESP_MASK) >>
3916b293258SBenjamin Herrenschmidt SCOM_STATUS_PIB_RESP_SHIFT;
3926b293258SBenjamin Herrenschmidt acc->intf_errors = 0;
3936b293258SBenjamin Herrenschmidt
3946b293258SBenjamin Herrenschmidt if (status & SCOM_STATUS_PROTECTION)
3956b293258SBenjamin Herrenschmidt acc->intf_errors |= SCOM_INTF_ERR_PROTECTION;
3966b293258SBenjamin Herrenschmidt else if (status & SCOM_STATUS_PARITY)
3976b293258SBenjamin Herrenschmidt acc->intf_errors |= SCOM_INTF_ERR_PARITY;
3986b293258SBenjamin Herrenschmidt else if (status & SCOM_STATUS_PIB_ABORT)
3996b293258SBenjamin Herrenschmidt acc->intf_errors |= SCOM_INTF_ERR_ABORT;
4006b293258SBenjamin Herrenschmidt else if (status & SCOM_STATUS_ERR_SUMMARY)
4016b293258SBenjamin Herrenschmidt acc->intf_errors |= SCOM_INTF_ERR_UNKNOWN;
4026b293258SBenjamin Herrenschmidt }
4036b293258SBenjamin Herrenschmidt
scom_raw_read(struct scom_device * scom,void __user * argp)4046b293258SBenjamin Herrenschmidt static int scom_raw_read(struct scom_device *scom, void __user *argp)
4056b293258SBenjamin Herrenschmidt {
4066b293258SBenjamin Herrenschmidt struct scom_access acc;
4076b293258SBenjamin Herrenschmidt uint32_t status;
4086b293258SBenjamin Herrenschmidt int rc;
4096b293258SBenjamin Herrenschmidt
4106b293258SBenjamin Herrenschmidt if (copy_from_user(&acc, argp, sizeof(struct scom_access)))
4116b293258SBenjamin Herrenschmidt return -EFAULT;
4126b293258SBenjamin Herrenschmidt
4136b293258SBenjamin Herrenschmidt rc = raw_get_scom(scom, &acc.data, acc.addr, &status);
4146b293258SBenjamin Herrenschmidt if (rc)
4156b293258SBenjamin Herrenschmidt return rc;
4166b293258SBenjamin Herrenschmidt raw_convert_status(&acc, status);
4176b293258SBenjamin Herrenschmidt if (copy_to_user(argp, &acc, sizeof(struct scom_access)))
4186b293258SBenjamin Herrenschmidt return -EFAULT;
4196b293258SBenjamin Herrenschmidt return 0;
4206b293258SBenjamin Herrenschmidt }
4216b293258SBenjamin Herrenschmidt
scom_raw_write(struct scom_device * scom,void __user * argp)4226b293258SBenjamin Herrenschmidt static int scom_raw_write(struct scom_device *scom, void __user *argp)
4236b293258SBenjamin Herrenschmidt {
4246b293258SBenjamin Herrenschmidt u64 prev_data, mask, data;
4256b293258SBenjamin Herrenschmidt struct scom_access acc;
4266b293258SBenjamin Herrenschmidt uint32_t status;
4276b293258SBenjamin Herrenschmidt int rc;
4286b293258SBenjamin Herrenschmidt
4296b293258SBenjamin Herrenschmidt if (copy_from_user(&acc, argp, sizeof(struct scom_access)))
4306b293258SBenjamin Herrenschmidt return -EFAULT;
4316b293258SBenjamin Herrenschmidt
4326b293258SBenjamin Herrenschmidt if (acc.mask) {
4336b293258SBenjamin Herrenschmidt rc = raw_get_scom(scom, &prev_data, acc.addr, &status);
4346b293258SBenjamin Herrenschmidt if (rc)
4356b293258SBenjamin Herrenschmidt return rc;
4366b293258SBenjamin Herrenschmidt if (status & SCOM_STATUS_ANY_ERR)
4376b293258SBenjamin Herrenschmidt goto fail;
4386b293258SBenjamin Herrenschmidt mask = acc.mask;
4396b293258SBenjamin Herrenschmidt } else {
4406b293258SBenjamin Herrenschmidt prev_data = mask = -1ull;
4416b293258SBenjamin Herrenschmidt }
4426b293258SBenjamin Herrenschmidt data = (prev_data & ~mask) | (acc.data & mask);
4436b293258SBenjamin Herrenschmidt rc = raw_put_scom(scom, data, acc.addr, &status);
4446b293258SBenjamin Herrenschmidt if (rc)
4456b293258SBenjamin Herrenschmidt return rc;
4466b293258SBenjamin Herrenschmidt fail:
4476b293258SBenjamin Herrenschmidt raw_convert_status(&acc, status);
4486b293258SBenjamin Herrenschmidt if (copy_to_user(argp, &acc, sizeof(struct scom_access)))
4496b293258SBenjamin Herrenschmidt return -EFAULT;
4506b293258SBenjamin Herrenschmidt return 0;
4516b293258SBenjamin Herrenschmidt }
4526b293258SBenjamin Herrenschmidt
scom_reset(struct scom_device * scom,void __user * argp)4536b293258SBenjamin Herrenschmidt static int scom_reset(struct scom_device *scom, void __user *argp)
4546b293258SBenjamin Herrenschmidt {
4556b293258SBenjamin Herrenschmidt uint32_t flags, dummy = -1;
4566b293258SBenjamin Herrenschmidt int rc = 0;
4576b293258SBenjamin Herrenschmidt
4586b293258SBenjamin Herrenschmidt if (get_user(flags, (__u32 __user *)argp))
4596b293258SBenjamin Herrenschmidt return -EFAULT;
4606b293258SBenjamin Herrenschmidt if (flags & SCOM_RESET_PIB)
4616b293258SBenjamin Herrenschmidt rc = fsi_device_write(scom->fsi_dev, SCOM_PIB_RESET_REG, &dummy,
4626b293258SBenjamin Herrenschmidt sizeof(uint32_t));
4636b293258SBenjamin Herrenschmidt if (!rc && (flags & (SCOM_RESET_PIB | SCOM_RESET_INTF)))
4646b293258SBenjamin Herrenschmidt rc = fsi_device_write(scom->fsi_dev, SCOM_FSI2PIB_RESET_REG, &dummy,
4656b293258SBenjamin Herrenschmidt sizeof(uint32_t));
4666b293258SBenjamin Herrenschmidt return rc;
4676b293258SBenjamin Herrenschmidt }
4686b293258SBenjamin Herrenschmidt
scom_check(struct scom_device * scom,void __user * argp)4696b293258SBenjamin Herrenschmidt static int scom_check(struct scom_device *scom, void __user *argp)
4706b293258SBenjamin Herrenschmidt {
4716b293258SBenjamin Herrenschmidt /* Still need to find out how to get "protected" */
4726b293258SBenjamin Herrenschmidt return put_user(SCOM_CHECK_SUPPORTED, (__u32 __user *)argp);
4736b293258SBenjamin Herrenschmidt }
4746b293258SBenjamin Herrenschmidt
scom_ioctl(struct file * file,unsigned int cmd,unsigned long arg)4756b293258SBenjamin Herrenschmidt static long scom_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
4766b293258SBenjamin Herrenschmidt {
477d8f45876SBenjamin Herrenschmidt struct scom_device *scom = file->private_data;
4786b293258SBenjamin Herrenschmidt void __user *argp = (void __user *)arg;
4796b293258SBenjamin Herrenschmidt int rc = -ENOTTY;
4806b293258SBenjamin Herrenschmidt
4816b293258SBenjamin Herrenschmidt mutex_lock(&scom->lock);
482d8f45876SBenjamin Herrenschmidt if (scom->dead) {
483d8f45876SBenjamin Herrenschmidt mutex_unlock(&scom->lock);
484d8f45876SBenjamin Herrenschmidt return -ENODEV;
485d8f45876SBenjamin Herrenschmidt }
4866b293258SBenjamin Herrenschmidt switch(cmd) {
4876b293258SBenjamin Herrenschmidt case FSI_SCOM_CHECK:
4886b293258SBenjamin Herrenschmidt rc = scom_check(scom, argp);
4896b293258SBenjamin Herrenschmidt break;
4906b293258SBenjamin Herrenschmidt case FSI_SCOM_READ:
4916b293258SBenjamin Herrenschmidt rc = scom_raw_read(scom, argp);
4926b293258SBenjamin Herrenschmidt break;
4936b293258SBenjamin Herrenschmidt case FSI_SCOM_WRITE:
4946b293258SBenjamin Herrenschmidt rc = scom_raw_write(scom, argp);
4956b293258SBenjamin Herrenschmidt break;
4966b293258SBenjamin Herrenschmidt case FSI_SCOM_RESET:
4976b293258SBenjamin Herrenschmidt rc = scom_reset(scom, argp);
4986b293258SBenjamin Herrenschmidt break;
4996b293258SBenjamin Herrenschmidt }
5006b293258SBenjamin Herrenschmidt mutex_unlock(&scom->lock);
5016b293258SBenjamin Herrenschmidt return rc;
5026b293258SBenjamin Herrenschmidt }
5036b293258SBenjamin Herrenschmidt
scom_open(struct inode * inode,struct file * file)504d8f45876SBenjamin Herrenschmidt static int scom_open(struct inode *inode, struct file *file)
505d8f45876SBenjamin Herrenschmidt {
506d8f45876SBenjamin Herrenschmidt struct scom_device *scom = container_of(inode->i_cdev, struct scom_device, cdev);
507d8f45876SBenjamin Herrenschmidt
508d8f45876SBenjamin Herrenschmidt file->private_data = scom;
509d8f45876SBenjamin Herrenschmidt
510d8f45876SBenjamin Herrenschmidt return 0;
511d8f45876SBenjamin Herrenschmidt }
512d8f45876SBenjamin Herrenschmidt
513680ca6dcSChristopher Bostic static const struct file_operations scom_fops = {
514680ca6dcSChristopher Bostic .owner = THIS_MODULE,
515d8f45876SBenjamin Herrenschmidt .open = scom_open,
516680ca6dcSChristopher Bostic .llseek = scom_llseek,
517680ca6dcSChristopher Bostic .read = scom_read,
518680ca6dcSChristopher Bostic .write = scom_write,
5196b293258SBenjamin Herrenschmidt .unlocked_ioctl = scom_ioctl,
520680ca6dcSChristopher Bostic };
521680ca6dcSChristopher Bostic
scom_free(struct device * dev)522d8f45876SBenjamin Herrenschmidt static void scom_free(struct device *dev)
523d8f45876SBenjamin Herrenschmidt {
524d8f45876SBenjamin Herrenschmidt struct scom_device *scom = container_of(dev, struct scom_device, dev);
525d8f45876SBenjamin Herrenschmidt
526d8f45876SBenjamin Herrenschmidt put_device(&scom->fsi_dev->dev);
527d8f45876SBenjamin Herrenschmidt kfree(scom);
528d8f45876SBenjamin Herrenschmidt }
529d8f45876SBenjamin Herrenschmidt
scom_probe(struct device * dev)530680ca6dcSChristopher Bostic static int scom_probe(struct device *dev)
531680ca6dcSChristopher Bostic {
532680ca6dcSChristopher Bostic struct fsi_device *fsi_dev = to_fsi_dev(dev);
533680ca6dcSChristopher Bostic struct scom_device *scom;
534d8f45876SBenjamin Herrenschmidt int rc, didx;
535680ca6dcSChristopher Bostic
536d8f45876SBenjamin Herrenschmidt scom = kzalloc(sizeof(*scom), GFP_KERNEL);
537680ca6dcSChristopher Bostic if (!scom)
538680ca6dcSChristopher Bostic return -ENOMEM;
539d8f45876SBenjamin Herrenschmidt dev_set_drvdata(dev, scom);
540162c3946SBenjamin Herrenschmidt mutex_init(&scom->lock);
541680ca6dcSChristopher Bostic
542d8f45876SBenjamin Herrenschmidt /* Grab a reference to the device (parent of our cdev), we'll drop it later */
543d8f45876SBenjamin Herrenschmidt if (!get_device(dev)) {
544d8f45876SBenjamin Herrenschmidt kfree(scom);
545d8f45876SBenjamin Herrenschmidt return -ENODEV;
546d8f45876SBenjamin Herrenschmidt }
547aa1221b2SBenjamin Herrenschmidt scom->fsi_dev = fsi_dev;
548d8f45876SBenjamin Herrenschmidt
549d8f45876SBenjamin Herrenschmidt /* Create chardev for userspace access */
550d8f45876SBenjamin Herrenschmidt scom->dev.type = &fsi_cdev_type;
551d8f45876SBenjamin Herrenschmidt scom->dev.parent = dev;
552d8f45876SBenjamin Herrenschmidt scom->dev.release = scom_free;
553d8f45876SBenjamin Herrenschmidt device_initialize(&scom->dev);
554d8f45876SBenjamin Herrenschmidt
555d8f45876SBenjamin Herrenschmidt /* Allocate a minor in the FSI space */
556d8f45876SBenjamin Herrenschmidt rc = fsi_get_new_minor(fsi_dev, fsi_dev_scom, &scom->dev.devt, &didx);
557d8f45876SBenjamin Herrenschmidt if (rc)
558d8f45876SBenjamin Herrenschmidt goto err;
559d8f45876SBenjamin Herrenschmidt
560d8f45876SBenjamin Herrenschmidt dev_set_name(&scom->dev, "scom%d", didx);
561d8f45876SBenjamin Herrenschmidt cdev_init(&scom->cdev, &scom_fops);
562d8f45876SBenjamin Herrenschmidt rc = cdev_device_add(&scom->cdev, &scom->dev);
563d8f45876SBenjamin Herrenschmidt if (rc) {
564d8f45876SBenjamin Herrenschmidt dev_err(dev, "Error %d creating char device %s\n",
565d8f45876SBenjamin Herrenschmidt rc, dev_name(&scom->dev));
566d8f45876SBenjamin Herrenschmidt goto err_free_minor;
567d8f45876SBenjamin Herrenschmidt }
568d8f45876SBenjamin Herrenschmidt
569d8f45876SBenjamin Herrenschmidt return 0;
570d8f45876SBenjamin Herrenschmidt err_free_minor:
571d8f45876SBenjamin Herrenschmidt fsi_free_minor(scom->dev.devt);
572d8f45876SBenjamin Herrenschmidt err:
573d8f45876SBenjamin Herrenschmidt put_device(&scom->dev);
574d8f45876SBenjamin Herrenschmidt return rc;
575680ca6dcSChristopher Bostic }
576680ca6dcSChristopher Bostic
scom_remove(struct device * dev)577680ca6dcSChristopher Bostic static int scom_remove(struct device *dev)
578680ca6dcSChristopher Bostic {
579d8f45876SBenjamin Herrenschmidt struct scom_device *scom = dev_get_drvdata(dev);
580680ca6dcSChristopher Bostic
581d8f45876SBenjamin Herrenschmidt mutex_lock(&scom->lock);
582d8f45876SBenjamin Herrenschmidt scom->dead = true;
583d8f45876SBenjamin Herrenschmidt mutex_unlock(&scom->lock);
584d8f45876SBenjamin Herrenschmidt cdev_device_del(&scom->cdev, &scom->dev);
585d8f45876SBenjamin Herrenschmidt fsi_free_minor(scom->dev.devt);
586d8f45876SBenjamin Herrenschmidt put_device(&scom->dev);
587680ca6dcSChristopher Bostic
588680ca6dcSChristopher Bostic return 0;
589680ca6dcSChristopher Bostic }
590680ca6dcSChristopher Bostic
591*c21d322eSEddie James static const struct of_device_id scom_of_ids[] = {
592*c21d322eSEddie James { .compatible = "ibm,fsi2pib" },
593*c21d322eSEddie James { }
594*c21d322eSEddie James };
595*c21d322eSEddie James MODULE_DEVICE_TABLE(of, scom_of_ids);
596*c21d322eSEddie James
59759165631SRikard Falkeborn static const struct fsi_device_id scom_ids[] = {
598680ca6dcSChristopher Bostic {
599680ca6dcSChristopher Bostic .engine_type = FSI_ENGID_SCOM,
600680ca6dcSChristopher Bostic .version = FSI_VERSION_ANY,
601680ca6dcSChristopher Bostic },
602680ca6dcSChristopher Bostic { 0 }
603680ca6dcSChristopher Bostic };
604680ca6dcSChristopher Bostic
605680ca6dcSChristopher Bostic static struct fsi_driver scom_drv = {
606680ca6dcSChristopher Bostic .id_table = scom_ids,
607680ca6dcSChristopher Bostic .drv = {
608680ca6dcSChristopher Bostic .name = "scom",
609680ca6dcSChristopher Bostic .bus = &fsi_bus_type,
610*c21d322eSEddie James .of_match_table = scom_of_ids,
611680ca6dcSChristopher Bostic .probe = scom_probe,
612680ca6dcSChristopher Bostic .remove = scom_remove,
613680ca6dcSChristopher Bostic }
614680ca6dcSChristopher Bostic };
615680ca6dcSChristopher Bostic
scom_init(void)616680ca6dcSChristopher Bostic static int scom_init(void)
617680ca6dcSChristopher Bostic {
618680ca6dcSChristopher Bostic return fsi_driver_register(&scom_drv);
619680ca6dcSChristopher Bostic }
620680ca6dcSChristopher Bostic
scom_exit(void)621680ca6dcSChristopher Bostic static void scom_exit(void)
622680ca6dcSChristopher Bostic {
623680ca6dcSChristopher Bostic fsi_driver_unregister(&scom_drv);
624680ca6dcSChristopher Bostic }
625680ca6dcSChristopher Bostic
626680ca6dcSChristopher Bostic module_init(scom_init);
627680ca6dcSChristopher Bostic module_exit(scom_exit);
628680ca6dcSChristopher Bostic MODULE_LICENSE("GPL");
629