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;
59 	int ret;
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 
70 		elem = &cpm->package.elements[i];
71 		for (j = 0; j < elem->package.count; ++j) {
72 			union acpi_object *sub_elem;
73 
74 			sub_elem = &elem->package.elements[j];
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 	}
86 	ret = cpm->package.count;
87 	kfree(buffer.pointer);
88 
89 	return ret;
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 
143 int inv_mpu_acpi_create_mux_client(struct i2c_client *client)
144 {
145 	struct inv_mpu6050_state *st = iio_priv(dev_get_drvdata(&client->dev));
146 
147 	st->mux_client = NULL;
148 	if (ACPI_HANDLE(&client->dev)) {
149 		struct i2c_board_info info;
150 		struct acpi_device *adev;
151 		int ret = -1;
152 
153 		adev = ACPI_COMPANION(&client->dev);
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:
159 			ret = asus_acpi_get_sensor_info(adev, client,
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 
171 			ret = inv_mpu_process_acpi_config(client, &primary,
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 		}
187 		st->mux_client = i2c_new_device(st->muxc->adapter[0], &info);
188 		if (!st->mux_client)
189 			return -ENODEV;
190 	}
191 
192 	return 0;
193 }
194 
195 void inv_mpu_acpi_delete_mux_client(struct i2c_client *client)
196 {
197 	struct inv_mpu6050_state *st = iio_priv(dev_get_drvdata(&client->dev));
198 
199 	i2c_unregister_device(st->mux_client);
200 }
201 #else
202 
203 #include "inv_mpu_iio.h"
204 
205 int inv_mpu_acpi_create_mux_client(struct i2c_client *client)
206 {
207 	return 0;
208 }
209 
210 void inv_mpu_acpi_delete_mux_client(struct i2c_client *client)
211 {
212 }
213 #endif
214