1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Sensor HUB driver that discovers sensors behind a ChromeOS Embedded 4 * Controller. 5 * 6 * Copyright 2019 Google LLC 7 */ 8 9 #include <linux/init.h> 10 #include <linux/device.h> 11 #include <linux/module.h> 12 #include <linux/platform_data/cros_ec_commands.h> 13 #include <linux/platform_data/cros_ec_proto.h> 14 #include <linux/platform_data/cros_ec_sensorhub.h> 15 #include <linux/platform_device.h> 16 #include <linux/slab.h> 17 #include <linux/types.h> 18 19 #define DRV_NAME "cros-ec-sensorhub" 20 21 static void cros_ec_sensorhub_free_sensor(void *arg) 22 { 23 struct platform_device *pdev = arg; 24 25 platform_device_unregister(pdev); 26 } 27 28 static int cros_ec_sensorhub_allocate_sensor(struct device *parent, 29 char *sensor_name, 30 int sensor_num) 31 { 32 struct cros_ec_sensor_platform sensor_platforms = { 33 .sensor_num = sensor_num, 34 }; 35 struct platform_device *pdev; 36 37 pdev = platform_device_register_data(parent, sensor_name, 38 PLATFORM_DEVID_AUTO, 39 &sensor_platforms, 40 sizeof(sensor_platforms)); 41 if (IS_ERR(pdev)) 42 return PTR_ERR(pdev); 43 44 return devm_add_action_or_reset(parent, 45 cros_ec_sensorhub_free_sensor, 46 pdev); 47 } 48 49 static int cros_ec_sensorhub_register(struct device *dev, 50 struct cros_ec_sensorhub *sensorhub) 51 { 52 int sensor_type[MOTIONSENSE_TYPE_MAX] = { 0 }; 53 struct cros_ec_dev *ec = sensorhub->ec; 54 struct ec_params_motion_sense *params; 55 struct ec_response_motion_sense *resp; 56 struct cros_ec_command *msg; 57 int ret, i, sensor_num; 58 char *name; 59 60 sensor_num = cros_ec_get_sensor_count(ec); 61 if (sensor_num < 0) { 62 dev_err(dev, 63 "Unable to retrieve sensor information (err:%d)\n", 64 sensor_num); 65 return sensor_num; 66 } 67 68 if (sensor_num == 0) { 69 dev_err(dev, "Zero sensors reported.\n"); 70 return -EINVAL; 71 } 72 73 /* Prepare a message to send INFO command to each sensor. */ 74 msg = kzalloc(sizeof(*msg) + max(sizeof(*params), sizeof(*resp)), 75 GFP_KERNEL); 76 if (!msg) 77 return -ENOMEM; 78 79 msg->version = 1; 80 msg->command = EC_CMD_MOTION_SENSE_CMD + ec->cmd_offset; 81 msg->outsize = sizeof(*params); 82 msg->insize = sizeof(*resp); 83 params = (struct ec_params_motion_sense *)msg->data; 84 resp = (struct ec_response_motion_sense *)msg->data; 85 86 for (i = 0; i < sensor_num; i++) { 87 params->cmd = MOTIONSENSE_CMD_INFO; 88 params->info.sensor_num = i; 89 90 ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg); 91 if (ret < 0) { 92 dev_warn(dev, "no info for EC sensor %d : %d/%d\n", 93 i, ret, msg->result); 94 continue; 95 } 96 97 switch (resp->info.type) { 98 case MOTIONSENSE_TYPE_ACCEL: 99 name = "cros-ec-accel"; 100 break; 101 case MOTIONSENSE_TYPE_BARO: 102 name = "cros-ec-baro"; 103 break; 104 case MOTIONSENSE_TYPE_GYRO: 105 name = "cros-ec-gyro"; 106 break; 107 case MOTIONSENSE_TYPE_MAG: 108 name = "cros-ec-mag"; 109 break; 110 case MOTIONSENSE_TYPE_PROX: 111 name = "cros-ec-prox"; 112 break; 113 case MOTIONSENSE_TYPE_LIGHT: 114 name = "cros-ec-light"; 115 break; 116 case MOTIONSENSE_TYPE_ACTIVITY: 117 name = "cros-ec-activity"; 118 break; 119 default: 120 dev_warn(dev, "unknown type %d\n", resp->info.type); 121 continue; 122 } 123 124 ret = cros_ec_sensorhub_allocate_sensor(dev, name, i); 125 if (ret) 126 goto error; 127 128 sensor_type[resp->info.type]++; 129 } 130 131 if (sensor_type[MOTIONSENSE_TYPE_ACCEL] >= 2) 132 ec->has_kb_wake_angle = true; 133 134 if (cros_ec_check_features(ec, 135 EC_FEATURE_REFINED_TABLET_MODE_HYSTERESIS)) { 136 ret = cros_ec_sensorhub_allocate_sensor(dev, 137 "cros-ec-lid-angle", 138 0); 139 if (ret) 140 goto error; 141 } 142 143 kfree(msg); 144 return 0; 145 146 error: 147 kfree(msg); 148 return ret; 149 } 150 151 static int cros_ec_sensorhub_probe(struct platform_device *pdev) 152 { 153 struct device *dev = &pdev->dev; 154 struct cros_ec_sensorhub *data; 155 int ret; 156 int i; 157 158 data = devm_kzalloc(dev, sizeof(struct cros_ec_sensorhub), GFP_KERNEL); 159 if (!data) 160 return -ENOMEM; 161 162 data->ec = dev_get_drvdata(dev->parent); 163 dev_set_drvdata(dev, data); 164 165 /* Check whether this EC is a sensor hub. */ 166 if (cros_ec_check_features(data->ec, EC_FEATURE_MOTION_SENSE)) { 167 ret = cros_ec_sensorhub_register(dev, data); 168 if (ret) 169 return ret; 170 } else { 171 /* 172 * If the device has sensors but does not claim to 173 * be a sensor hub, we are in legacy mode. 174 */ 175 for (i = 0; i < 2; i++) { 176 ret = cros_ec_sensorhub_allocate_sensor(dev, 177 "cros-ec-accel-legacy", i); 178 if (ret) 179 return ret; 180 } 181 } 182 183 return 0; 184 } 185 186 static struct platform_driver cros_ec_sensorhub_driver = { 187 .driver = { 188 .name = DRV_NAME, 189 }, 190 .probe = cros_ec_sensorhub_probe, 191 }; 192 193 module_platform_driver(cros_ec_sensorhub_driver); 194 195 MODULE_ALIAS("platform:" DRV_NAME); 196 MODULE_AUTHOR("Gwendal Grignou <gwendal@chromium.org>"); 197 MODULE_DESCRIPTION("ChromeOS EC MEMS Sensor Hub Driver"); 198 MODULE_LICENSE("GPL"); 199