aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSrinivas Pandruvada <srinivas.pandruvada@linux.intel.com>2015-01-30 17:25:45 -0500
committerJonathan Cameron <jic23@kernel.org>2015-02-25 07:02:59 -0500
commita35c5d1aa96aa6cc70e91786cbe9be4db23f8f4a (patch)
tree61615139eb7c11836fedbde7d0a06ee8172b1548
parent9820d88332095bc4b5ef67799e149472fcb9aa40 (diff)
iio: imu: inv_mpu6050: Create mux clients for ACPI
This is a follow up patches after adding i2c mux adapter for bypass mode. Potentially many different types of sensor can be attached to INVMPU6XXX device, which can be connected to main cpu i2c bus in bypass mode. Why do we need this? The system ACPI table entry will consist of only one device for INV6XXX, assuming that this driver will handle all connected sensors. That is not true for the Linux driver. There are bunch of IIO drivers for each sensors, hence we created a mux on this device. So to load these additional drivers, we need to create i2c devices for them in this driver using this mux adapter. There are multiple options: 1. Use the auto detect feature, this needs a new i2c class for the adapter as the existing HWMON class is not acceptable. Also the autodetect has overhead of executing detect method for each matching class of adapters. This is a simple implementation. This option was previously submitted with not a happy feedback. 2. Option is use ACPI magic and parse the configuration data. What we need to create a i2c device at a minimum is address and a name. Address can be obtained for secondary device in more or less in a standard way from using _CRS element. But there is no name. To get name we need to process proprietary vendor data. Not having name is not fun, as you have to create device using the device name of INVN6XXXX, respecting driver duplicate name space restriction. Also each client driver needs to have this name in the id table. Since multiple driver can be loaded, the driver should be able to detect its presence and gracefully exit for the other client driver to take it over. So we use two step process: - Use DMI to id platform and parse propritery data. This is not uncommon for many x86 platform specific driver. We will get both name and address. The change created necessary infrastructure to add more properitery vendor data parsing. - If DMI match fails, then create device on INV6XXX-client (we can't create with same name as INV6XXX as it will cause duplicate name and driver model will reject.) With this each client sensor driver which needs to get attached via INV6XXXX, need this name in the id table and detect the physical presence of sensor in probe and exit if not found. Signed-off-by: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com> Signed-off-by: Jonathan Cameron <jic23@kernel.org>
-rw-r--r--drivers/iio/imu/inv_mpu6050/Makefile2
-rw-r--r--drivers/iio/imu/inv_mpu6050/inv_mpu_acpi.c211
-rw-r--r--drivers/iio/imu/inv_mpu6050/inv_mpu_core.c7
-rw-r--r--drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h3
4 files changed, 222 insertions, 1 deletions
diff --git a/drivers/iio/imu/inv_mpu6050/Makefile b/drivers/iio/imu/inv_mpu6050/Makefile
index 3a677c778afb..f566f6a7b3a9 100644
--- a/drivers/iio/imu/inv_mpu6050/Makefile
+++ b/drivers/iio/imu/inv_mpu6050/Makefile
@@ -3,4 +3,4 @@
3# 3#
4 4
5obj-$(CONFIG_INV_MPU6050_IIO) += inv-mpu6050.o 5obj-$(CONFIG_INV_MPU6050_IIO) += inv-mpu6050.o
6inv-mpu6050-objs := inv_mpu_core.o inv_mpu_ring.o inv_mpu_trigger.o 6inv-mpu6050-objs := inv_mpu_core.o inv_mpu_ring.o inv_mpu_trigger.o inv_mpu_acpi.o
diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_acpi.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_acpi.c
new file mode 100644
index 000000000000..1c982a56acd5
--- /dev/null
+++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_acpi.c
@@ -0,0 +1,211 @@
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
diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c
index f73e60b7a796..c42e08ee92da 100644
--- a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c
+++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c
@@ -821,8 +821,14 @@ static int inv_mpu_probe(struct i2c_client *client,
821 goto out_unreg_device; 821 goto out_unreg_device;
822 } 822 }
823 823
824 result = inv_mpu_acpi_create_mux_client(st);
825 if (result)
826 goto out_del_mux;
827
824 return 0; 828 return 0;
825 829
830out_del_mux:
831 i2c_del_mux_adapter(st->mux_adapter);
826out_unreg_device: 832out_unreg_device:
827 iio_device_unregister(indio_dev); 833 iio_device_unregister(indio_dev);
828out_remove_trigger: 834out_remove_trigger:
@@ -837,6 +843,7 @@ static int inv_mpu_remove(struct i2c_client *client)
837 struct iio_dev *indio_dev = i2c_get_clientdata(client); 843 struct iio_dev *indio_dev = i2c_get_clientdata(client);
838 struct inv_mpu6050_state *st = iio_priv(indio_dev); 844 struct inv_mpu6050_state *st = iio_priv(indio_dev);
839 845
846 inv_mpu_acpi_delete_mux_client(st);
840 i2c_del_mux_adapter(st->mux_adapter); 847 i2c_del_mux_adapter(st->mux_adapter);
841 iio_device_unregister(indio_dev); 848 iio_device_unregister(indio_dev);
842 inv_mpu6050_remove_trigger(st); 849 inv_mpu6050_remove_trigger(st);
diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h b/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h
index aa837de57079..db0a4a2758ab 100644
--- a/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h
+++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h
@@ -121,6 +121,7 @@ struct inv_mpu6050_state {
121 spinlock_t time_stamp_lock; 121 spinlock_t time_stamp_lock;
122 struct i2c_client *client; 122 struct i2c_client *client;
123 struct i2c_adapter *mux_adapter; 123 struct i2c_adapter *mux_adapter;
124 struct i2c_client *mux_client;
124 unsigned int powerup_count; 125 unsigned int powerup_count;
125 struct inv_mpu6050_platform_data plat_data; 126 struct inv_mpu6050_platform_data plat_data;
126 DECLARE_KFIFO(timestamps, long long, TIMESTAMP_FIFO_SIZE); 127 DECLARE_KFIFO(timestamps, long long, TIMESTAMP_FIFO_SIZE);
@@ -251,3 +252,5 @@ int inv_reset_fifo(struct iio_dev *indio_dev);
251int inv_mpu6050_switch_engine(struct inv_mpu6050_state *st, bool en, u32 mask); 252int inv_mpu6050_switch_engine(struct inv_mpu6050_state *st, bool en, u32 mask);
252int inv_mpu6050_write_reg(struct inv_mpu6050_state *st, int reg, u8 val); 253int inv_mpu6050_write_reg(struct inv_mpu6050_state *st, int reg, u8 val);
253int inv_mpu6050_set_power_itg(struct inv_mpu6050_state *st, bool power_on); 254int inv_mpu6050_set_power_itg(struct inv_mpu6050_state *st, bool power_on);
255int inv_mpu_acpi_create_mux_client(struct inv_mpu6050_state *st);
256void inv_mpu_acpi_delete_mux_client(struct inv_mpu6050_state *st);