1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * hmc6352.c - Honeywell Compass Driver 4 * 5 * Copyright (C) 2009 Intel Corp 6 * 7 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 8 * 9 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 10 */ 11 12 #include <linux/module.h> 13 #include <linux/slab.h> 14 #include <linux/i2c.h> 15 #include <linux/err.h> 16 #include <linux/delay.h> 17 #include <linux/sysfs.h> 18 #include <linux/nospec.h> 19 20 static DEFINE_MUTEX(compass_mutex); 21 22 static int compass_command(struct i2c_client *c, u8 cmd) 23 { 24 int ret = i2c_master_send(c, &cmd, 1); 25 if (ret < 0) 26 dev_warn(&c->dev, "command '%c' failed.\n", cmd); 27 return ret; 28 } 29 30 static int compass_store(struct device *dev, const char *buf, size_t count, 31 const char *map) 32 { 33 struct i2c_client *c = to_i2c_client(dev); 34 int ret; 35 unsigned long val; 36 37 ret = kstrtoul(buf, 10, &val); 38 if (ret) 39 return ret; 40 if (val >= strlen(map)) 41 return -EINVAL; 42 val = array_index_nospec(val, strlen(map)); 43 mutex_lock(&compass_mutex); 44 ret = compass_command(c, map[val]); 45 mutex_unlock(&compass_mutex); 46 if (ret < 0) 47 return ret; 48 return count; 49 } 50 51 static ssize_t compass_calibration_store(struct device *dev, 52 struct device_attribute *attr, const char *buf, size_t count) 53 { 54 return compass_store(dev, buf, count, "EC"); 55 } 56 57 static ssize_t compass_power_mode_store(struct device *dev, 58 struct device_attribute *attr, const char *buf, size_t count) 59 { 60 return compass_store(dev, buf, count, "SW"); 61 } 62 63 static ssize_t compass_heading_data_show(struct device *dev, 64 struct device_attribute *attr, char *buf) 65 { 66 struct i2c_client *client = to_i2c_client(dev); 67 unsigned char i2c_data[2]; 68 int ret; 69 70 mutex_lock(&compass_mutex); 71 ret = compass_command(client, 'A'); 72 if (ret != 1) { 73 mutex_unlock(&compass_mutex); 74 return ret; 75 } 76 msleep(10); /* sending 'A' cmd we need to wait for 7-10 millisecs */ 77 ret = i2c_master_recv(client, i2c_data, 2); 78 mutex_unlock(&compass_mutex); 79 if (ret < 0) { 80 dev_warn(dev, "i2c read data cmd failed\n"); 81 return ret; 82 } 83 ret = (i2c_data[0] << 8) | i2c_data[1]; 84 return sprintf(buf, "%d.%d\n", ret/10, ret%10); 85 } 86 87 88 static DEVICE_ATTR(heading0_input, S_IRUGO, compass_heading_data_show, NULL); 89 static DEVICE_ATTR(calibration, S_IWUSR, NULL, compass_calibration_store); 90 static DEVICE_ATTR(power_state, S_IWUSR, NULL, compass_power_mode_store); 91 92 static struct attribute *mid_att_compass[] = { 93 &dev_attr_heading0_input.attr, 94 &dev_attr_calibration.attr, 95 &dev_attr_power_state.attr, 96 NULL 97 }; 98 99 static const struct attribute_group m_compass_gr = { 100 .name = "hmc6352", 101 .attrs = mid_att_compass 102 }; 103 104 static int hmc6352_probe(struct i2c_client *client, 105 const struct i2c_device_id *id) 106 { 107 int res; 108 109 res = sysfs_create_group(&client->dev.kobj, &m_compass_gr); 110 if (res) { 111 dev_err(&client->dev, "device_create_file failed\n"); 112 return res; 113 } 114 dev_info(&client->dev, "%s HMC6352 compass chip found\n", 115 client->name); 116 return 0; 117 } 118 119 static void hmc6352_remove(struct i2c_client *client) 120 { 121 sysfs_remove_group(&client->dev.kobj, &m_compass_gr); 122 } 123 124 static const struct i2c_device_id hmc6352_id[] = { 125 { "hmc6352", 0 }, 126 { } 127 }; 128 129 MODULE_DEVICE_TABLE(i2c, hmc6352_id); 130 131 static struct i2c_driver hmc6352_driver = { 132 .driver = { 133 .name = "hmc6352", 134 }, 135 .probe = hmc6352_probe, 136 .remove = hmc6352_remove, 137 .id_table = hmc6352_id, 138 }; 139 140 module_i2c_driver(hmc6352_driver); 141 142 MODULE_AUTHOR("Kalhan Trisal <kalhan.trisal@intel.com"); 143 MODULE_DESCRIPTION("hmc6352 Compass Driver"); 144 MODULE_LICENSE("GPL v2"); 145