Linux 6.9-rc1
[linux-2.6-microblaze.git] / drivers / hwmon / occ / p9_sbe.c
1 // SPDX-License-Identifier: GPL-2.0+
2 // Copyright IBM Corp 2019
3
4 #include <linux/device.h>
5 #include <linux/errno.h>
6 #include <linux/slab.h>
7 #include <linux/fsi-occ.h>
8 #include <linux/mm.h>
9 #include <linux/module.h>
10 #include <linux/mod_devicetable.h>
11 #include <linux/mutex.h>
12 #include <linux/platform_device.h>
13 #include <linux/string.h>
14 #include <linux/sysfs.h>
15
16 #include "common.h"
17
18 #define OCC_CHECKSUM_RETRIES    3
19
20 struct p9_sbe_occ {
21         struct occ occ;
22         bool sbe_error;
23         void *ffdc;
24         size_t ffdc_len;
25         size_t ffdc_size;
26         struct mutex sbe_error_lock;    /* lock access to ffdc data */
27         struct device *sbe;
28 };
29
30 #define to_p9_sbe_occ(x)        container_of((x), struct p9_sbe_occ, occ)
31
32 static ssize_t ffdc_read(struct file *filp, struct kobject *kobj,
33                          struct bin_attribute *battr, char *buf, loff_t pos,
34                          size_t count)
35 {
36         ssize_t rc = 0;
37         struct occ *occ = dev_get_drvdata(kobj_to_dev(kobj));
38         struct p9_sbe_occ *ctx = to_p9_sbe_occ(occ);
39
40         mutex_lock(&ctx->sbe_error_lock);
41         if (ctx->sbe_error) {
42                 rc = memory_read_from_buffer(buf, count, &pos, ctx->ffdc,
43                                              ctx->ffdc_len);
44                 if (pos >= ctx->ffdc_len)
45                         ctx->sbe_error = false;
46         }
47         mutex_unlock(&ctx->sbe_error_lock);
48
49         return rc;
50 }
51 static BIN_ATTR_RO(ffdc, OCC_MAX_RESP_WORDS * 4);
52
53 static bool p9_sbe_occ_save_ffdc(struct p9_sbe_occ *ctx, const void *resp,
54                                  size_t resp_len)
55 {
56         bool notify = false;
57
58         mutex_lock(&ctx->sbe_error_lock);
59         if (!ctx->sbe_error) {
60                 if (resp_len > ctx->ffdc_size) {
61                         kvfree(ctx->ffdc);
62                         ctx->ffdc = kvmalloc(resp_len, GFP_KERNEL);
63                         if (!ctx->ffdc) {
64                                 ctx->ffdc_len = 0;
65                                 ctx->ffdc_size = 0;
66                                 goto done;
67                         }
68
69                         ctx->ffdc_size = resp_len;
70                 }
71
72                 notify = true;
73                 ctx->sbe_error = true;
74                 ctx->ffdc_len = resp_len;
75                 memcpy(ctx->ffdc, resp, resp_len);
76         }
77
78 done:
79         mutex_unlock(&ctx->sbe_error_lock);
80         return notify;
81 }
82
83 static int p9_sbe_occ_send_cmd(struct occ *occ, u8 *cmd, size_t len,
84                                void *resp, size_t resp_len)
85 {
86         size_t original_resp_len = resp_len;
87         struct p9_sbe_occ *ctx = to_p9_sbe_occ(occ);
88         int rc, i;
89
90         for (i = 0; i < OCC_CHECKSUM_RETRIES; ++i) {
91                 rc = fsi_occ_submit(ctx->sbe, cmd, len, resp, &resp_len);
92                 if (rc >= 0)
93                         break;
94                 if (resp_len) {
95                         if (p9_sbe_occ_save_ffdc(ctx, resp, resp_len))
96                                 sysfs_notify(&occ->bus_dev->kobj, NULL,
97                                              bin_attr_ffdc.attr.name);
98                         return rc;
99                 }
100                 if (rc != -EBADE)
101                         return rc;
102                 resp_len = original_resp_len;
103         }
104
105         switch (((struct occ_response *)resp)->return_status) {
106         case OCC_RESP_CMD_IN_PRG:
107                 rc = -ETIMEDOUT;
108                 break;
109         case OCC_RESP_SUCCESS:
110                 rc = 0;
111                 break;
112         case OCC_RESP_CMD_INVAL:
113         case OCC_RESP_CMD_LEN_INVAL:
114         case OCC_RESP_DATA_INVAL:
115         case OCC_RESP_CHKSUM_ERR:
116                 rc = -EINVAL;
117                 break;
118         case OCC_RESP_INT_ERR:
119         case OCC_RESP_BAD_STATE:
120         case OCC_RESP_CRIT_EXCEPT:
121         case OCC_RESP_CRIT_INIT:
122         case OCC_RESP_CRIT_WATCHDOG:
123         case OCC_RESP_CRIT_OCB:
124         case OCC_RESP_CRIT_HW:
125                 rc = -EREMOTEIO;
126                 break;
127         default:
128                 rc = -EPROTO;
129         }
130
131         return rc;
132 }
133
134 static int p9_sbe_occ_probe(struct platform_device *pdev)
135 {
136         int rc;
137         struct occ *occ;
138         struct p9_sbe_occ *ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx),
139                                               GFP_KERNEL);
140         if (!ctx)
141                 return -ENOMEM;
142
143         mutex_init(&ctx->sbe_error_lock);
144
145         ctx->sbe = pdev->dev.parent;
146         occ = &ctx->occ;
147         occ->bus_dev = &pdev->dev;
148         platform_set_drvdata(pdev, occ);
149
150         occ->powr_sample_time_us = 500;
151         occ->poll_cmd_data = 0x20;              /* P9 OCC poll data */
152         occ->send_cmd = p9_sbe_occ_send_cmd;
153
154         rc = occ_setup(occ);
155         if (rc == -ESHUTDOWN)
156                 rc = -ENODEV;   /* Host is shutdown, don't spew errors */
157
158         if (!rc) {
159                 rc = device_create_bin_file(occ->bus_dev, &bin_attr_ffdc);
160                 if (rc) {
161                         dev_warn(occ->bus_dev,
162                                  "failed to create SBE error ffdc file\n");
163                         rc = 0;
164                 }
165         }
166
167         return rc;
168 }
169
170 static void p9_sbe_occ_remove(struct platform_device *pdev)
171 {
172         struct occ *occ = platform_get_drvdata(pdev);
173         struct p9_sbe_occ *ctx = to_p9_sbe_occ(occ);
174
175         device_remove_bin_file(occ->bus_dev, &bin_attr_ffdc);
176
177         ctx->sbe = NULL;
178         occ_shutdown(occ);
179
180         kvfree(ctx->ffdc);
181 }
182
183 static const struct of_device_id p9_sbe_occ_of_match[] = {
184         { .compatible = "ibm,p9-occ-hwmon" },
185         { .compatible = "ibm,p10-occ-hwmon" },
186         {}
187 };
188 MODULE_DEVICE_TABLE(of, p9_sbe_occ_of_match);
189
190 static struct platform_driver p9_sbe_occ_driver = {
191         .driver = {
192                 .name = "occ-hwmon",
193                 .of_match_table = p9_sbe_occ_of_match,
194         },
195         .probe  = p9_sbe_occ_probe,
196         .remove_new = p9_sbe_occ_remove,
197 };
198
199 module_platform_driver(p9_sbe_occ_driver);
200
201 MODULE_AUTHOR("Eddie James <eajames@linux.ibm.com>");
202 MODULE_DESCRIPTION("BMC P9 OCC hwmon driver");
203 MODULE_LICENSE("GPL");