1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * inv_mpu_acpi: ACPI processing for creating client devices |
4 | * Copyright (c) 2015, Intel Corporation. |
5 | */ |
6 | |
7 | #ifdef CONFIG_ACPI |
8 | |
9 | #include <linux/kernel.h> |
10 | #include <linux/i2c.h> |
11 | #include <linux/dmi.h> |
12 | #include <linux/acpi.h> |
13 | #include "inv_mpu_iio.h" |
14 | |
15 | enum inv_mpu_product_name { |
16 | INV_MPU_NOT_MATCHED, |
17 | INV_MPU_ASUS_T100TA, |
18 | }; |
19 | |
20 | static enum inv_mpu_product_name matched_product_name; |
21 | |
22 | static int __init asus_t100_matched(const struct dmi_system_id *d) |
23 | { |
24 | matched_product_name = INV_MPU_ASUS_T100TA; |
25 | |
26 | return 0; |
27 | } |
28 | |
29 | static const struct dmi_system_id inv_mpu_dev_list[] = { |
30 | { |
31 | .callback = asus_t100_matched, |
32 | .ident = "Asus Transformer Book T100" , |
33 | .matches = { |
34 | DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC" ), |
35 | DMI_MATCH(DMI_PRODUCT_NAME, "T100TA" ), |
36 | DMI_MATCH(DMI_PRODUCT_VERSION, "1.0" ), |
37 | }, |
38 | }, |
39 | /* Add more matching tables here..*/ |
40 | {} |
41 | }; |
42 | |
43 | static int asus_acpi_get_sensor_info(struct acpi_device *adev, |
44 | struct i2c_client *client, |
45 | struct i2c_board_info *info) |
46 | { |
47 | struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL}; |
48 | int i; |
49 | acpi_status status; |
50 | union acpi_object *cpm; |
51 | int ret; |
52 | |
53 | status = acpi_evaluate_object(object: adev->handle, pathname: "CNF0" , NULL, return_object_buffer: &buffer); |
54 | if (ACPI_FAILURE(status)) |
55 | return -ENODEV; |
56 | |
57 | cpm = buffer.pointer; |
58 | for (i = 0; i < cpm->package.count; ++i) { |
59 | union acpi_object *elem; |
60 | int j; |
61 | |
62 | elem = &cpm->package.elements[i]; |
63 | for (j = 0; j < elem->package.count; ++j) { |
64 | union acpi_object *sub_elem; |
65 | |
66 | sub_elem = &elem->package.elements[j]; |
67 | if (sub_elem->type == ACPI_TYPE_STRING) |
68 | strscpy(info->type, sub_elem->string.pointer, |
69 | sizeof(info->type)); |
70 | else if (sub_elem->type == ACPI_TYPE_INTEGER) { |
71 | if (sub_elem->integer.value != client->addr) { |
72 | info->addr = sub_elem->integer.value; |
73 | break; /* Not a MPU6500 primary */ |
74 | } |
75 | } |
76 | } |
77 | } |
78 | ret = cpm->package.count; |
79 | kfree(objp: buffer.pointer); |
80 | |
81 | return ret; |
82 | } |
83 | |
84 | static int acpi_i2c_check_resource(struct acpi_resource *ares, void *data) |
85 | { |
86 | struct acpi_resource_i2c_serialbus *sb; |
87 | u32 *addr = data; |
88 | |
89 | if (i2c_acpi_get_i2c_resource(ares, i2c: &sb)) { |
90 | if (*addr) |
91 | *addr |= (sb->slave_address << 16); |
92 | else |
93 | *addr = sb->slave_address; |
94 | } |
95 | |
96 | /* Tell the ACPI core that we already copied this address */ |
97 | return 1; |
98 | } |
99 | |
100 | static int inv_mpu_process_acpi_config(struct i2c_client *client, |
101 | unsigned short *primary_addr, |
102 | unsigned short *secondary_addr) |
103 | { |
104 | struct acpi_device *adev = ACPI_COMPANION(&client->dev); |
105 | const struct acpi_device_id *id; |
106 | u32 i2c_addr = 0; |
107 | LIST_HEAD(resources); |
108 | int ret; |
109 | |
110 | id = acpi_match_device(ids: client->dev.driver->acpi_match_table, |
111 | dev: &client->dev); |
112 | if (!id) |
113 | return -ENODEV; |
114 | |
115 | ret = acpi_dev_get_resources(adev, list: &resources, |
116 | preproc: acpi_i2c_check_resource, preproc_data: &i2c_addr); |
117 | if (ret < 0) |
118 | return ret; |
119 | |
120 | acpi_dev_free_resource_list(list: &resources); |
121 | *primary_addr = i2c_addr & 0x0000ffff; |
122 | *secondary_addr = (i2c_addr & 0xffff0000) >> 16; |
123 | |
124 | return 0; |
125 | } |
126 | |
127 | int inv_mpu_acpi_create_mux_client(struct i2c_client *client) |
128 | { |
129 | struct inv_mpu6050_state *st = iio_priv(indio_dev: dev_get_drvdata(dev: &client->dev)); |
130 | struct acpi_device *adev = ACPI_COMPANION(&client->dev); |
131 | |
132 | st->mux_client = NULL; |
133 | if (adev) { |
134 | struct i2c_board_info info; |
135 | struct i2c_client *mux_client; |
136 | int ret = -1; |
137 | |
138 | memset(&info, 0, sizeof(info)); |
139 | |
140 | dmi_check_system(list: inv_mpu_dev_list); |
141 | switch (matched_product_name) { |
142 | case INV_MPU_ASUS_T100TA: |
143 | ret = asus_acpi_get_sensor_info(adev, client, |
144 | info: &info); |
145 | break; |
146 | /* Add more matched product processing here */ |
147 | default: |
148 | break; |
149 | } |
150 | |
151 | if (ret < 0) { |
152 | /* No matching DMI, so create device on INV6XX type */ |
153 | unsigned short primary, secondary; |
154 | |
155 | ret = inv_mpu_process_acpi_config(client, primary_addr: &primary, |
156 | secondary_addr: &secondary); |
157 | if (!ret && secondary) { |
158 | char *name; |
159 | |
160 | info.addr = secondary; |
161 | strscpy(info.type, dev_name(&adev->dev), |
162 | sizeof(info.type)); |
163 | name = strchr(info.type, ':'); |
164 | if (name) |
165 | *name = '\0'; |
166 | strlcat(p: info.type, q: "-client" , |
167 | avail: sizeof(info.type)); |
168 | } else |
169 | return 0; /* no secondary addr, which is OK */ |
170 | } |
171 | mux_client = i2c_new_client_device(adap: st->muxc->adapter[0], info: &info); |
172 | if (IS_ERR(ptr: mux_client)) |
173 | return PTR_ERR(ptr: mux_client); |
174 | st->mux_client = mux_client; |
175 | } |
176 | |
177 | return 0; |
178 | } |
179 | |
180 | void inv_mpu_acpi_delete_mux_client(struct i2c_client *client) |
181 | { |
182 | struct inv_mpu6050_state *st = iio_priv(indio_dev: dev_get_drvdata(dev: &client->dev)); |
183 | |
184 | i2c_unregister_device(client: st->mux_client); |
185 | } |
186 | #else |
187 | |
188 | #include "inv_mpu_iio.h" |
189 | |
190 | int inv_mpu_acpi_create_mux_client(struct i2c_client *client) |
191 | { |
192 | return 0; |
193 | } |
194 | |
195 | void inv_mpu_acpi_delete_mux_client(struct i2c_client *client) |
196 | { |
197 | } |
198 | #endif |
199 | |