Commit | Line | Data |
---|---|---|
a35c5d1a SP |
1 | /* |
2 | * inv_mpu_acpi: ACPI processing for creating client devices | |
3 | * Copyright (c) 2015, Intel Corporation. | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify it | |
6 | * under the terms and conditions of the GNU General Public License, | |
7 | * version 2, as published by the Free Software Foundation. | |
8 | * | |
9 | * This program is distributed in the hope it will be useful, but WITHOUT | |
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
12 | * more details. | |
13 | */ | |
14 | ||
15 | #ifdef CONFIG_ACPI | |
16 | ||
17 | #include <linux/kernel.h> | |
18 | #include <linux/i2c.h> | |
19 | #include <linux/dmi.h> | |
20 | #include <linux/acpi.h> | |
21 | #include "inv_mpu_iio.h" | |
22 | ||
23 | enum inv_mpu_product_name { | |
24 | INV_MPU_NOT_MATCHED, | |
25 | INV_MPU_ASUS_T100TA, | |
26 | }; | |
27 | ||
28 | static enum inv_mpu_product_name matched_product_name; | |
29 | ||
30 | static int __init asus_t100_matched(const struct dmi_system_id *d) | |
31 | { | |
32 | matched_product_name = INV_MPU_ASUS_T100TA; | |
33 | ||
34 | return 0; | |
35 | } | |
36 | ||
37 | static const struct dmi_system_id inv_mpu_dev_list[] = { | |
38 | { | |
39 | .callback = asus_t100_matched, | |
40 | .ident = "Asus Transformer Book T100", | |
41 | .matches = { | |
42 | DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC"), | |
43 | DMI_MATCH(DMI_PRODUCT_NAME, "T100TA"), | |
44 | DMI_MATCH(DMI_PRODUCT_VERSION, "1.0"), | |
45 | }, | |
46 | }, | |
47 | /* Add more matching tables here..*/ | |
48 | {} | |
49 | }; | |
50 | ||
51 | static int asus_acpi_get_sensor_info(struct acpi_device *adev, | |
52 | struct i2c_client *client, | |
53 | struct i2c_board_info *info) | |
54 | { | |
55 | struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL}; | |
56 | int i; | |
57 | acpi_status status; | |
58 | union acpi_object *cpm; | |
6e85dbe4 | 59 | int ret; |
a35c5d1a SP |
60 | |
61 | status = acpi_evaluate_object(adev->handle, "CNF0", NULL, &buffer); | |
62 | if (ACPI_FAILURE(status)) | |
63 | return -ENODEV; | |
64 | ||
65 | cpm = buffer.pointer; | |
66 | for (i = 0; i < cpm->package.count; ++i) { | |
67 | union acpi_object *elem; | |
68 | int j; | |
69 | ||
0e791375 | 70 | elem = &cpm->package.elements[i]; |
a35c5d1a SP |
71 | for (j = 0; j < elem->package.count; ++j) { |
72 | union acpi_object *sub_elem; | |
73 | ||
0e791375 | 74 | sub_elem = &elem->package.elements[j]; |
a35c5d1a SP |
75 | if (sub_elem->type == ACPI_TYPE_STRING) |
76 | strlcpy(info->type, sub_elem->string.pointer, | |
77 | sizeof(info->type)); | |
78 | else if (sub_elem->type == ACPI_TYPE_INTEGER) { | |
79 | if (sub_elem->integer.value != client->addr) { | |
80 | info->addr = sub_elem->integer.value; | |
81 | break; /* Not a MPU6500 primary */ | |
82 | } | |
83 | } | |
84 | } | |
85 | } | |
6e85dbe4 | 86 | ret = cpm->package.count; |
a35c5d1a SP |
87 | kfree(buffer.pointer); |
88 | ||
6e85dbe4 | 89 | return ret; |
a35c5d1a SP |
90 | } |
91 | ||
92 | static int acpi_i2c_check_resource(struct acpi_resource *ares, void *data) | |
93 | { | |
94 | u32 *addr = data; | |
95 | ||
96 | if (ares->type == ACPI_RESOURCE_TYPE_SERIAL_BUS) { | |
97 | struct acpi_resource_i2c_serialbus *sb; | |
98 | ||
99 | sb = &ares->data.i2c_serial_bus; | |
100 | if (sb->type == ACPI_RESOURCE_SERIAL_TYPE_I2C) { | |
101 | if (*addr) | |
102 | *addr |= (sb->slave_address << 16); | |
103 | else | |
104 | *addr = sb->slave_address; | |
105 | } | |
106 | } | |
107 | ||
108 | /* Tell the ACPI core that we already copied this address */ | |
109 | return 1; | |
110 | } | |
111 | ||
112 | static int inv_mpu_process_acpi_config(struct i2c_client *client, | |
113 | unsigned short *primary_addr, | |
114 | unsigned short *secondary_addr) | |
115 | { | |
116 | const struct acpi_device_id *id; | |
117 | struct acpi_device *adev; | |
118 | u32 i2c_addr = 0; | |
119 | LIST_HEAD(resources); | |
120 | int ret; | |
121 | ||
122 | id = acpi_match_device(client->dev.driver->acpi_match_table, | |
123 | &client->dev); | |
124 | if (!id) | |
125 | return -ENODEV; | |
126 | ||
127 | adev = ACPI_COMPANION(&client->dev); | |
128 | if (!adev) | |
129 | return -ENODEV; | |
130 | ||
131 | ret = acpi_dev_get_resources(adev, &resources, | |
132 | acpi_i2c_check_resource, &i2c_addr); | |
133 | if (ret < 0) | |
134 | return ret; | |
135 | ||
136 | acpi_dev_free_resource_list(&resources); | |
137 | *primary_addr = i2c_addr & 0x0000ffff; | |
138 | *secondary_addr = (i2c_addr & 0xffff0000) >> 16; | |
139 | ||
140 | return 0; | |
141 | } | |
142 | ||
b3eea8da | 143 | int inv_mpu_acpi_create_mux_client(struct i2c_client *client) |
a35c5d1a | 144 | { |
b3eea8da | 145 | struct inv_mpu6050_state *st = iio_priv(dev_get_drvdata(&client->dev)); |
a35c5d1a SP |
146 | |
147 | st->mux_client = NULL; | |
b3eea8da | 148 | if (ACPI_HANDLE(&client->dev)) { |
a35c5d1a SP |
149 | struct i2c_board_info info; |
150 | struct acpi_device *adev; | |
151 | int ret = -1; | |
152 | ||
b3eea8da | 153 | adev = ACPI_COMPANION(&client->dev); |
a35c5d1a SP |
154 | memset(&info, 0, sizeof(info)); |
155 | ||
156 | dmi_check_system(inv_mpu_dev_list); | |
157 | switch (matched_product_name) { | |
158 | case INV_MPU_ASUS_T100TA: | |
b3eea8da | 159 | ret = asus_acpi_get_sensor_info(adev, client, |
a35c5d1a SP |
160 | &info); |
161 | break; | |
162 | /* Add more matched product processing here */ | |
163 | default: | |
164 | break; | |
165 | } | |
166 | ||
167 | if (ret < 0) { | |
168 | /* No matching DMI, so create device on INV6XX type */ | |
169 | unsigned short primary, secondary; | |
170 | ||
b3eea8da | 171 | ret = inv_mpu_process_acpi_config(client, &primary, |
a35c5d1a SP |
172 | &secondary); |
173 | if (!ret && secondary) { | |
174 | char *name; | |
175 | ||
176 | info.addr = secondary; | |
177 | strlcpy(info.type, dev_name(&adev->dev), | |
178 | sizeof(info.type)); | |
179 | name = strchr(info.type, ':'); | |
180 | if (name) | |
181 | *name = '\0'; | |
182 | strlcat(info.type, "-client", | |
183 | sizeof(info.type)); | |
184 | } else | |
185 | return 0; /* no secondary addr, which is OK */ | |
186 | } | |
51f97f6d | 187 | st->mux_client = i2c_new_device(st->muxc->adapter[0], &info); |
a35c5d1a SP |
188 | if (!st->mux_client) |
189 | return -ENODEV; | |
a35c5d1a SP |
190 | } |
191 | ||
192 | return 0; | |
193 | } | |
194 | ||
b3eea8da | 195 | void inv_mpu_acpi_delete_mux_client(struct i2c_client *client) |
a35c5d1a | 196 | { |
b3eea8da | 197 | struct inv_mpu6050_state *st = iio_priv(dev_get_drvdata(&client->dev)); |
d92241a0 | 198 | |
a35c5d1a SP |
199 | if (st->mux_client) |
200 | i2c_unregister_device(st->mux_client); | |
201 | } | |
202 | #else | |
203 | ||
204 | #include "inv_mpu_iio.h" | |
205 | ||
b3eea8da | 206 | int inv_mpu_acpi_create_mux_client(struct i2c_client *client) |
a35c5d1a SP |
207 | { |
208 | return 0; | |
209 | } | |
210 | ||
b3eea8da | 211 | void inv_mpu_acpi_delete_mux_client(struct i2c_client *client) |
a35c5d1a SP |
212 | { |
213 | } | |
214 | #endif |