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
23enum inv_mpu_product_name {
24	INV_MPU_NOT_MATCHED,
25	INV_MPU_ASUS_T100TA,
26};
27
28static enum inv_mpu_product_name matched_product_name;
29
30static 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
37static 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
51static 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
60	status = acpi_evaluate_object(adev->handle, "CNF0", NULL, &buffer);
61	if (ACPI_FAILURE(status))
62		return -ENODEV;
63
64	cpm = buffer.pointer;
65	for (i = 0; i < cpm->package.count; ++i) {
66		union acpi_object *elem;
67		int j;
68
69		elem = &(cpm->package.elements[i]);
70		for (j = 0; j < elem->package.count; ++j) {
71			union acpi_object *sub_elem;
72
73			sub_elem = &(elem->package.elements[j]);
74			if (sub_elem->type == ACPI_TYPE_STRING)
75				strlcpy(info->type, sub_elem->string.pointer,
76					sizeof(info->type));
77			else if (sub_elem->type == ACPI_TYPE_INTEGER) {
78				if (sub_elem->integer.value != client->addr) {
79					info->addr = sub_elem->integer.value;
80					break; /* Not a MPU6500 primary */
81				}
82			}
83		}
84	}
85
86	kfree(buffer.pointer);
87
88	return cpm->package.count;
89}
90
91static int acpi_i2c_check_resource(struct acpi_resource *ares, void *data)
92{
93	u32 *addr = data;
94
95	if (ares->type == ACPI_RESOURCE_TYPE_SERIAL_BUS) {
96		struct acpi_resource_i2c_serialbus *sb;
97
98		sb = &ares->data.i2c_serial_bus;
99		if (sb->type == ACPI_RESOURCE_SERIAL_TYPE_I2C) {
100			if (*addr)
101				*addr |= (sb->slave_address << 16);
102			else
103				*addr = sb->slave_address;
104		}
105	}
106
107	/* Tell the ACPI core that we already copied this address */
108	return 1;
109}
110
111static int inv_mpu_process_acpi_config(struct i2c_client *client,
112				       unsigned short *primary_addr,
113				       unsigned short *secondary_addr)
114{
115	const struct acpi_device_id *id;
116	struct acpi_device *adev;
117	u32 i2c_addr = 0;
118	LIST_HEAD(resources);
119	int ret;
120
121	id = acpi_match_device(client->dev.driver->acpi_match_table,
122			       &client->dev);
123	if (!id)
124		return -ENODEV;
125
126	adev = ACPI_COMPANION(&client->dev);
127	if (!adev)
128		return -ENODEV;
129
130	ret = acpi_dev_get_resources(adev, &resources,
131				     acpi_i2c_check_resource, &i2c_addr);
132	if (ret < 0)
133		return ret;
134
135	acpi_dev_free_resource_list(&resources);
136	*primary_addr = i2c_addr & 0x0000ffff;
137	*secondary_addr = (i2c_addr & 0xffff0000) >> 16;
138
139	return 0;
140}
141
142int inv_mpu_acpi_create_mux_client(struct inv_mpu6050_state *st)
143{
144
145	st->mux_client = NULL;
146	if (ACPI_HANDLE(&st->client->dev)) {
147		struct i2c_board_info info;
148		struct acpi_device *adev;
149		int ret = -1;
150
151		adev = ACPI_COMPANION(&st->client->dev);
152		memset(&info, 0, sizeof(info));
153
154		dmi_check_system(inv_mpu_dev_list);
155		switch (matched_product_name) {
156		case INV_MPU_ASUS_T100TA:
157			ret = asus_acpi_get_sensor_info(adev, st->client,
158							&info);
159			break;
160		/* Add more matched product processing here */
161		default:
162			break;
163		}
164
165		if (ret < 0) {
166			/* No matching DMI, so create device on INV6XX type */
167			unsigned short primary, secondary;
168
169			ret = inv_mpu_process_acpi_config(st->client, &primary,
170							  &secondary);
171			if (!ret && secondary) {
172				char *name;
173
174				info.addr = secondary;
175				strlcpy(info.type, dev_name(&adev->dev),
176					sizeof(info.type));
177				name = strchr(info.type, ':');
178				if (name)
179					*name = '\0';
180				strlcat(info.type, "-client",
181					sizeof(info.type));
182			} else
183				return 0; /* no secondary addr, which is OK */
184		}
185		st->mux_client = i2c_new_device(st->mux_adapter, &info);
186		if (!st->mux_client)
187			return -ENODEV;
188
189	}
190
191	return 0;
192}
193
194void inv_mpu_acpi_delete_mux_client(struct inv_mpu6050_state *st)
195{
196	if (st->mux_client)
197		i2c_unregister_device(st->mux_client);
198}
199#else
200
201#include "inv_mpu_iio.h"
202
203int inv_mpu_acpi_create_mux_client(struct inv_mpu6050_state *st)
204{
205	return 0;
206}
207
208void inv_mpu_acpi_delete_mux_client(struct inv_mpu6050_state *st)
209{
210}
211#endif
212