1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * dptf_pch_fivr: DPTF PCH FIVR Participant driver |
4 | * Copyright (c) 2020, Intel Corporation. |
5 | */ |
6 | |
7 | #include <linux/acpi.h> |
8 | #include <linux/kernel.h> |
9 | #include <linux/module.h> |
10 | #include <linux/platform_device.h> |
11 | |
12 | struct pch_fivr_resp { |
13 | u64 status; |
14 | u64 result; |
15 | }; |
16 | |
17 | static int pch_fivr_read(acpi_handle handle, char *method, struct pch_fivr_resp *fivr_resp) |
18 | { |
19 | struct acpi_buffer resp = { sizeof(struct pch_fivr_resp), fivr_resp}; |
20 | struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; |
21 | struct acpi_buffer format = { sizeof("NN" ), "NN" }; |
22 | union acpi_object *obj; |
23 | acpi_status status; |
24 | int ret = -EFAULT; |
25 | |
26 | status = acpi_evaluate_object(object: handle, pathname: method, NULL, return_object_buffer: &buffer); |
27 | if (ACPI_FAILURE(status)) |
28 | return ret; |
29 | |
30 | obj = buffer.pointer; |
31 | if (!obj || obj->type != ACPI_TYPE_PACKAGE) |
32 | goto release_buffer; |
33 | |
34 | status = acpi_extract_package(package: obj, format: &format, buffer: &resp); |
35 | if (ACPI_FAILURE(status)) |
36 | goto release_buffer; |
37 | |
38 | if (fivr_resp->status) |
39 | goto release_buffer; |
40 | |
41 | ret = 0; |
42 | |
43 | release_buffer: |
44 | kfree(objp: buffer.pointer); |
45 | return ret; |
46 | } |
47 | |
48 | /* |
49 | * Presentation of attributes which are defined for INTC10xx |
50 | * They are: |
51 | * freq_mhz_low_clock : Set PCH FIVR switching freq for |
52 | * FIVR clock 19.2MHz and 24MHz |
53 | * freq_mhz_high_clock : Set PCH FIVR switching freq for |
54 | * FIVR clock 38.4MHz |
55 | */ |
56 | #define PCH_FIVR_SHOW(name, method) \ |
57 | static ssize_t name##_show(struct device *dev,\ |
58 | struct device_attribute *attr,\ |
59 | char *buf)\ |
60 | {\ |
61 | struct acpi_device *acpi_dev = dev_get_drvdata(dev);\ |
62 | struct pch_fivr_resp fivr_resp;\ |
63 | int status;\ |
64 | \ |
65 | status = pch_fivr_read(acpi_dev->handle, #method, &fivr_resp);\ |
66 | if (status)\ |
67 | return status;\ |
68 | \ |
69 | return sprintf(buf, "%llu\n", fivr_resp.result);\ |
70 | } |
71 | |
72 | #define PCH_FIVR_STORE(name, method) \ |
73 | static ssize_t name##_store(struct device *dev,\ |
74 | struct device_attribute *attr,\ |
75 | const char *buf, size_t count)\ |
76 | {\ |
77 | struct acpi_device *acpi_dev = dev_get_drvdata(dev);\ |
78 | acpi_status status;\ |
79 | u32 val;\ |
80 | \ |
81 | if (kstrtouint(buf, 0, &val) < 0)\ |
82 | return -EINVAL;\ |
83 | \ |
84 | status = acpi_execute_simple_method(acpi_dev->handle, #method, val);\ |
85 | if (ACPI_SUCCESS(status))\ |
86 | return count;\ |
87 | \ |
88 | return -EINVAL;\ |
89 | } |
90 | |
91 | PCH_FIVR_SHOW(freq_mhz_low_clock, GFC0) |
92 | PCH_FIVR_SHOW(freq_mhz_high_clock, GFC1) |
93 | PCH_FIVR_SHOW(ssc_clock_info, GEMI) |
94 | PCH_FIVR_SHOW(fivr_switching_freq_mhz, GFCS) |
95 | PCH_FIVR_SHOW(fivr_switching_fault_status, GFFS) |
96 | PCH_FIVR_STORE(freq_mhz_low_clock, RFC0) |
97 | PCH_FIVR_STORE(freq_mhz_high_clock, RFC1) |
98 | |
99 | static DEVICE_ATTR_RW(freq_mhz_low_clock); |
100 | static DEVICE_ATTR_RW(freq_mhz_high_clock); |
101 | static DEVICE_ATTR_RO(ssc_clock_info); |
102 | static DEVICE_ATTR_RO(fivr_switching_freq_mhz); |
103 | static DEVICE_ATTR_RO(fivr_switching_fault_status); |
104 | |
105 | static struct attribute *fivr_attrs[] = { |
106 | &dev_attr_freq_mhz_low_clock.attr, |
107 | &dev_attr_freq_mhz_high_clock.attr, |
108 | &dev_attr_ssc_clock_info.attr, |
109 | &dev_attr_fivr_switching_freq_mhz.attr, |
110 | &dev_attr_fivr_switching_fault_status.attr, |
111 | NULL |
112 | }; |
113 | |
114 | static const struct attribute_group pch_fivr_attribute_group = { |
115 | .attrs = fivr_attrs, |
116 | .name = "pch_fivr_switch_frequency" |
117 | }; |
118 | |
119 | static int pch_fivr_add(struct platform_device *pdev) |
120 | { |
121 | struct acpi_device *acpi_dev; |
122 | unsigned long long ptype; |
123 | acpi_status status; |
124 | int result; |
125 | |
126 | acpi_dev = ACPI_COMPANION(&(pdev->dev)); |
127 | if (!acpi_dev) |
128 | return -ENODEV; |
129 | |
130 | status = acpi_evaluate_integer(handle: acpi_dev->handle, pathname: "PTYP" , NULL, data: &ptype); |
131 | if (ACPI_FAILURE(status) || ptype != 0x05) |
132 | return -ENODEV; |
133 | |
134 | result = sysfs_create_group(kobj: &pdev->dev.kobj, |
135 | grp: &pch_fivr_attribute_group); |
136 | if (result) |
137 | return result; |
138 | |
139 | platform_set_drvdata(pdev, data: acpi_dev); |
140 | |
141 | return 0; |
142 | } |
143 | |
144 | static void pch_fivr_remove(struct platform_device *pdev) |
145 | { |
146 | sysfs_remove_group(kobj: &pdev->dev.kobj, grp: &pch_fivr_attribute_group); |
147 | } |
148 | |
149 | static const struct acpi_device_id pch_fivr_device_ids[] = { |
150 | {"INTC1045" , 0}, |
151 | {"INTC1049" , 0}, |
152 | {"INTC1064" , 0}, |
153 | {"INTC10A3" , 0}, |
154 | {"" , 0}, |
155 | }; |
156 | MODULE_DEVICE_TABLE(acpi, pch_fivr_device_ids); |
157 | |
158 | static struct platform_driver pch_fivr_driver = { |
159 | .probe = pch_fivr_add, |
160 | .remove_new = pch_fivr_remove, |
161 | .driver = { |
162 | .name = "dptf_pch_fivr" , |
163 | .acpi_match_table = pch_fivr_device_ids, |
164 | }, |
165 | }; |
166 | |
167 | module_platform_driver(pch_fivr_driver); |
168 | |
169 | MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>" ); |
170 | MODULE_LICENSE("GPL v2" ); |
171 | MODULE_DESCRIPTION("ACPI DPTF PCH FIVR driver" ); |
172 | |