1// SPDX-License-Identifier: GPL-2.0+
2// Copyright IBM Corp 2019
3
4#include <linux/bitops.h>
5#include <linux/device.h>
6#include <linux/export.h>
7#include <linux/hwmon-sysfs.h>
8#include <linux/kernel.h>
9#include <linux/sysfs.h>
10
11#include "common.h"
12
13/* OCC status register */
14#define OCC_STAT_MASTER			BIT(7)
15#define OCC_STAT_ACTIVE			BIT(0)
16
17/* OCC extended status register */
18#define OCC_EXT_STAT_DVFS_OT		BIT(7)
19#define OCC_EXT_STAT_DVFS_POWER		BIT(6)
20#define OCC_EXT_STAT_MEM_THROTTLE	BIT(5)
21#define OCC_EXT_STAT_QUICK_DROP		BIT(4)
22
23static ssize_t occ_sysfs_show(struct device *dev,
24			      struct device_attribute *attr, char *buf)
25{
26	int rc;
27	int val = 0;
28	struct occ *occ = dev_get_drvdata(dev);
29	struct occ_poll_response_header *header;
30	struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr);
31
32	rc = occ_update_response(occ);
33	if (rc)
34		return rc;
35
36	header = (struct occ_poll_response_header *)occ->resp.data;
37
38	switch (sattr->index) {
39	case 0:
40		val = !!(header->status & OCC_STAT_MASTER);
41		break;
42	case 1:
43		val = !!(header->status & OCC_STAT_ACTIVE);
44		break;
45	case 2:
46		val = !!(header->ext_status & OCC_EXT_STAT_DVFS_OT);
47		break;
48	case 3:
49		val = !!(header->ext_status & OCC_EXT_STAT_DVFS_POWER);
50		break;
51	case 4:
52		val = !!(header->ext_status & OCC_EXT_STAT_MEM_THROTTLE);
53		break;
54	case 5:
55		val = !!(header->ext_status & OCC_EXT_STAT_QUICK_DROP);
56		break;
57	case 6:
58		val = header->occ_state;
59		break;
60	case 7:
61		if (header->status & OCC_STAT_MASTER)
62			val = hweight8(header->occs_present);
63		else
64			val = 1;
65		break;
66	default:
67		return -EINVAL;
68	}
69
70	return snprintf(buf, PAGE_SIZE - 1, "%d\n", val);
71}
72
73static ssize_t occ_error_show(struct device *dev,
74			      struct device_attribute *attr, char *buf)
75{
76	struct occ *occ = dev_get_drvdata(dev);
77
78	occ_update_response(occ);
79
80	return snprintf(buf, PAGE_SIZE - 1, "%d\n", occ->error);
81}
82
83static SENSOR_DEVICE_ATTR(occ_master, 0444, occ_sysfs_show, NULL, 0);
84static SENSOR_DEVICE_ATTR(occ_active, 0444, occ_sysfs_show, NULL, 1);
85static SENSOR_DEVICE_ATTR(occ_dvfs_overtemp, 0444, occ_sysfs_show, NULL, 2);
86static SENSOR_DEVICE_ATTR(occ_dvfs_power, 0444, occ_sysfs_show, NULL, 3);
87static SENSOR_DEVICE_ATTR(occ_mem_throttle, 0444, occ_sysfs_show, NULL, 4);
88static SENSOR_DEVICE_ATTR(occ_quick_pwr_drop, 0444, occ_sysfs_show, NULL, 5);
89static SENSOR_DEVICE_ATTR(occ_state, 0444, occ_sysfs_show, NULL, 6);
90static SENSOR_DEVICE_ATTR(occs_present, 0444, occ_sysfs_show, NULL, 7);
91static DEVICE_ATTR_RO(occ_error);
92
93static struct attribute *occ_attributes[] = {
94	&sensor_dev_attr_occ_master.dev_attr.attr,
95	&sensor_dev_attr_occ_active.dev_attr.attr,
96	&sensor_dev_attr_occ_dvfs_overtemp.dev_attr.attr,
97	&sensor_dev_attr_occ_dvfs_power.dev_attr.attr,
98	&sensor_dev_attr_occ_mem_throttle.dev_attr.attr,
99	&sensor_dev_attr_occ_quick_pwr_drop.dev_attr.attr,
100	&sensor_dev_attr_occ_state.dev_attr.attr,
101	&sensor_dev_attr_occs_present.dev_attr.attr,
102	&dev_attr_occ_error.attr,
103	NULL
104};
105
106static const struct attribute_group occ_sysfs = {
107	.attrs = occ_attributes,
108};
109
110void occ_sysfs_poll_done(struct occ *occ)
111{
112	const char *name;
113	struct occ_poll_response_header *header =
114		(struct occ_poll_response_header *)occ->resp.data;
115
116	/*
117	 * On the first poll response, we haven't yet created the sysfs
118	 * attributes, so don't make any notify calls.
119	 */
120	if (!occ->hwmon)
121		goto done;
122
123	if ((header->status & OCC_STAT_MASTER) !=
124	    (occ->prev_stat & OCC_STAT_MASTER)) {
125		name = sensor_dev_attr_occ_master.dev_attr.attr.name;
126		sysfs_notify(&occ->bus_dev->kobj, NULL, name);
127	}
128
129	if ((header->status & OCC_STAT_ACTIVE) !=
130	    (occ->prev_stat & OCC_STAT_ACTIVE)) {
131		name = sensor_dev_attr_occ_active.dev_attr.attr.name;
132		sysfs_notify(&occ->bus_dev->kobj, NULL, name);
133	}
134
135	if ((header->ext_status & OCC_EXT_STAT_DVFS_OT) !=
136	    (occ->prev_ext_stat & OCC_EXT_STAT_DVFS_OT)) {
137		name = sensor_dev_attr_occ_dvfs_overtemp.dev_attr.attr.name;
138		sysfs_notify(&occ->bus_dev->kobj, NULL, name);
139	}
140
141	if ((header->ext_status & OCC_EXT_STAT_DVFS_POWER) !=
142	    (occ->prev_ext_stat & OCC_EXT_STAT_DVFS_POWER)) {
143		name = sensor_dev_attr_occ_dvfs_power.dev_attr.attr.name;
144		sysfs_notify(&occ->bus_dev->kobj, NULL, name);
145	}
146
147	if ((header->ext_status & OCC_EXT_STAT_MEM_THROTTLE) !=
148	    (occ->prev_ext_stat & OCC_EXT_STAT_MEM_THROTTLE)) {
149		name = sensor_dev_attr_occ_mem_throttle.dev_attr.attr.name;
150		sysfs_notify(&occ->bus_dev->kobj, NULL, name);
151	}
152
153	if ((header->ext_status & OCC_EXT_STAT_QUICK_DROP) !=
154	    (occ->prev_ext_stat & OCC_EXT_STAT_QUICK_DROP)) {
155		name = sensor_dev_attr_occ_quick_pwr_drop.dev_attr.attr.name;
156		sysfs_notify(&occ->bus_dev->kobj, NULL, name);
157	}
158
159	if ((header->status & OCC_STAT_MASTER) &&
160	    header->occs_present != occ->prev_occs_present) {
161		name = sensor_dev_attr_occs_present.dev_attr.attr.name;
162		sysfs_notify(&occ->bus_dev->kobj, NULL, name);
163	}
164
165	if (occ->error && occ->error != occ->prev_error) {
166		name = dev_attr_occ_error.attr.name;
167		sysfs_notify(&occ->bus_dev->kobj, NULL, name);
168	}
169
170	/* no notifications for OCC state; doesn't indicate error condition */
171
172done:
173	occ->prev_error = occ->error;
174	occ->prev_stat = header->status;
175	occ->prev_ext_stat = header->ext_status;
176	occ->prev_occs_present = header->occs_present;
177}
178
179int occ_setup_sysfs(struct occ *occ)
180{
181	return sysfs_create_group(&occ->bus_dev->kobj, &occ_sysfs);
182}
183
184void occ_shutdown(struct occ *occ)
185{
186	sysfs_remove_group(&occ->bus_dev->kobj, &occ_sysfs);
187}
188EXPORT_SYMBOL_GPL(occ_shutdown);
189