| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503 |
- /* SPDX-License-Identifier: GPL-2.0-only
- *
- * Verifone Gsensor driver for screen orientation
- */
- #include <linux/module.h>
- #include <linux/kernel.h>
- #include <linux/init.h>
- #include <linux/i2c.h>
- #include <linux/delay.h>
- #include <linux/mutex.h>
- #include <linux/sysfs.h>
- #include <linux/slab.h>
- #include <linux/workqueue.h>
- extern struct kobject *vfiec_kobj;
- /* Verifone API Definitions */
- enum gsensor_orientation_hex {
- GSENSOR_ORIENT_UNKNOWN_HEX = 0x00,
- GSENSOR_ORIENT_PORTRAIT_HEX = 0x14,
- GSENSOR_ORIENT_LANDSCAPE_HEX = 0x15,
- GSENSOR_ORIENT_PORTRAIT_FLIP_HEX = 0x16,
- GSENSOR_ORIENT_LANDSCAPE_FLIP_HEX = 0x17,
- };
- #define GSENSOR_ORIENT_PORTRAIT_STR "portrait"
- #define GSENSOR_ORIENT_LANDSCAPE_STR "landscape"
- #define GSENSOR_ORIENT_PORTRAIT_FLIP_STR "portrait_flip"
- #define GSENSOR_ORIENT_LANDSCAPE_FLIP_STR "landscape_flip"
- #define GSENSOR_MODE_SOFT_RESET BIT(2)
- #define GSENSOR_MODE_INITIALIZED BIT(1)
- /* Register Definitions */
- #define ST_ACCEL_WHO_AM_I_ADDR 0x0F
- #define ST_ACCEL_CTRL_REG1_ADDR 0x20
- #define ST_ACCEL_CTRL_REG4_ADDR 0x23
- #define ST_ACCEL_OUT_X_L_ADDR 0x28
- #define SC7A20_WHO_AM_I_VALUE 0x11
- /* Configuration */
- #define GSENSOR_DEBOUNCE_MS 300
- #define GSENSOR_POLL_INTERVAL_MS 200
- struct gsensor_data {
- struct i2c_client *client;
- struct mutex lock;
- struct delayed_work poll_work;
- struct kobject *gsensor_kobj;
-
- bool enabled;
- bool initialized;
-
- enum gsensor_orientation_hex orientation_hex;
- enum gsensor_orientation_hex pending_orientation;
- char orientation_str[32];
- unsigned long last_change_jiffies;
-
- u8 who_am_i;
- };
- static struct gsensor_data *g_data = NULL;
- /* I2C Helper Functions */
- static int gsensor_read_reg(struct i2c_client *client, u8 reg, u8 *data)
- {
- int ret = i2c_smbus_read_byte_data(client, reg);
- if (ret < 0)
- return ret;
- *data = ret;
- return 0;
- }
- static int gsensor_write_reg(struct i2c_client *client, u8 reg, u8 value)
- {
- return i2c_smbus_write_byte_data(client, reg, value);
- }
- /* Read the raw data */
- static int gsensor_read_data(struct gsensor_data *data, int *x, int *y, int *z)
- {
- u8 xl, xh, yl, yh, zl, zh;
- s16 raw_x, raw_y, raw_z;
- int ret;
- /* Read the X-axis byte by byte */
- ret = gsensor_read_reg(data->client, 0x28, &xl);
- if (ret < 0) return ret;
- ret = gsensor_read_reg(data->client, 0x29, &xh);
- if (ret < 0) return ret;
- /* Read the Y-axis byte by byte */
- ret = gsensor_read_reg(data->client, 0x2a, &yl);
- if (ret < 0) return ret;
- ret = gsensor_read_reg(data->client, 0x2b, &yh);
- if (ret < 0) return ret;
- /* Z-axis */
- ret = gsensor_read_reg(data->client, 0x2c, &zl);
- if (ret < 0) return ret;
- ret = gsensor_read_reg(data->client, 0x2d, &zh);
- if (ret < 0) return ret;
-
- raw_x = (s16)((xh << 8) | xl);
- raw_y = (s16)((yh << 8) | yl);
- raw_z = (s16)((zh << 8) | zl);
- /* ±2g Mode: 1 LSB = 0.06103515625 mg */
- *x = (raw_x * 61) / 1000;
- *y = (raw_y * 61) / 1000;
- *z = (raw_z * 61) / 1000;
-
- return 0;
- }
- /* Enable/Disable sensor */
- static int gsensor_set_enable(struct gsensor_data *data, bool enable)
- {
- int ret;
-
- if (enable) {
- /* CTRL_REG1: 0x87 = 100Hz (0x80) + Enable all axes (0x07) */
- ret = gsensor_write_reg(data->client, ST_ACCEL_CTRL_REG1_ADDR, 0x87);
- if (ret < 0)
- return ret;
- msleep(20);
- } else {
- /* CTRL_REG1: 0x00 = Standby mode */
- ret = gsensor_write_reg(data->client, ST_ACCEL_CTRL_REG1_ADDR, 0x00);
- if (ret < 0)
- return ret;
- }
-
- return 0;
- }
- /* Initialize sensor */
- static int gsensor_init(struct gsensor_data *data)
- {
- u8 who_am_i;
- int ret;
- /* 1. Verify device */
- ret = gsensor_read_reg(data->client, ST_ACCEL_WHO_AM_I_ADDR, &who_am_i);
- if (ret < 0) {
- dev_err(&data->client->dev, "Failed to read WHO_AM_I: %d\n", ret);
- return ret;
- }
-
- data->who_am_i = who_am_i;
- dev_info(&data->client->dev, "WHO_AM_I = 0x%02x\n", who_am_i);
-
- if (who_am_i != SC7A20_WHO_AM_I_VALUE) {
- dev_err(&data->client->dev, "Invalid WHO_AM_I: 0x%02x\n", who_am_i);
- return -ENODEV;
- }
- /* 2. Setting CTRL_REG4: BDU enable , ±2g */
- ret = gsensor_write_reg(data->client, ST_ACCEL_CTRL_REG4_ADDR, 0x80);
- if (ret < 0)
- return ret;
- /* 3. Disable the sensor at startup */
- ret = gsensor_write_reg(data->client, ST_ACCEL_CTRL_REG1_ADDR, 0x00);
- if (ret < 0)
- return ret;
-
- data->initialized = true;
- dev_info(&data->client->dev, "Gsensor initialized successfully\n");
-
- return 0;
- }
- /* Orientation Calculation */
- static enum gsensor_orientation_hex gsensor_calc_orientation(int x, int y, int z)
- {
- int abs_x = abs(x);
- int abs_y = abs(y);
- int abs_z = abs(z);
- /* Device flat - Z axis maximum */
- if (abs_z > 800 && abs_z > abs_x && abs_z > abs_y) {
- return GSENSOR_ORIENT_UNKNOWN_HEX;
- }
- /* Landscape mode: X axis maximum */
- if (abs_x > 800 && abs_x > abs_y) {
- if (x > 0)
- return GSENSOR_ORIENT_LANDSCAPE_FLIP_HEX;
- else
- return GSENSOR_ORIENT_LANDSCAPE_HEX;
- }
- /* Portrait mode: Y axis maximum. */
- if (abs_y > 800 && abs_y > abs_x) {
- if (y > 0)
- return GSENSOR_ORIENT_PORTRAIT_FLIP_HEX;
- else
- return GSENSOR_ORIENT_PORTRAIT_HEX;
- }
-
- return GSENSOR_ORIENT_UNKNOWN_HEX;
- }
- static void gsensor_update_orientation(struct gsensor_data *data)
- {
- int x, y, z;
- enum gsensor_orientation_hex new_orient;
- unsigned long now = jiffies;
-
- if (!data->enabled)
- return;
-
- if (gsensor_read_data(data, &x, &y, &z) < 0)
- return;
-
- new_orient = gsensor_calc_orientation(x, y, z);
-
- if (new_orient != GSENSOR_ORIENT_UNKNOWN_HEX && new_orient != data->orientation_hex) {
- if (new_orient == data->pending_orientation) {
- if (time_after(now, data->last_change_jiffies +
- msecs_to_jiffies(GSENSOR_DEBOUNCE_MS))) {
- data->orientation_hex = new_orient;
- data->pending_orientation = GSENSOR_ORIENT_UNKNOWN_HEX;
-
- switch (new_orient) {
- case GSENSOR_ORIENT_PORTRAIT_HEX:
- strcpy(data->orientation_str, GSENSOR_ORIENT_PORTRAIT_STR);
- break;
- case GSENSOR_ORIENT_LANDSCAPE_HEX:
- strcpy(data->orientation_str, GSENSOR_ORIENT_LANDSCAPE_STR);
- break;
- case GSENSOR_ORIENT_PORTRAIT_FLIP_HEX:
- strcpy(data->orientation_str, GSENSOR_ORIENT_PORTRAIT_FLIP_STR);
- break;
- case GSENSOR_ORIENT_LANDSCAPE_FLIP_HEX:
- strcpy(data->orientation_str, GSENSOR_ORIENT_LANDSCAPE_FLIP_STR);
- break;
- default:
- break;
- }
-
- dev_info(&data->client->dev, "Orientation: %s [X=%d, Y=%d, Z=%d]\n",
- data->orientation_str, x, y, z);
- }
- } else {
- data->pending_orientation = new_orient;
- data->last_change_jiffies = now;
- }
- } else if (new_orient == data->orientation_hex) {
- data->pending_orientation = GSENSOR_ORIENT_UNKNOWN_HEX;
- }
- }
- static void gsensor_poll_work(struct work_struct *work)
- {
- struct gsensor_data *data = container_of(work, struct gsensor_data,
- poll_work.work);
-
- mutex_lock(&data->lock);
- gsensor_update_orientation(data);
- mutex_unlock(&data->lock);
-
- if (data->enabled)
- schedule_delayed_work(&data->poll_work,
- msecs_to_jiffies(GSENSOR_POLL_INTERVAL_MS));
- }
- /* Sysfs Interface */
- static ssize_t enable_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
- {
- return sprintf(buf, "%d\n", g_data ? g_data->enabled : 0);
- }
- static ssize_t enable_store(struct kobject *kobj, struct kobj_attribute *attr,
- const char *buf, size_t count)
- {
- struct gsensor_data *data = g_data;
- unsigned long val;
- int ret;
-
- if (!data)
- return -ENODEV;
-
- ret = kstrtoul(buf, 0, &val);
- if (ret)
- return ret;
-
- mutex_lock(&data->lock);
-
- if (val && !data->enabled) {
- ret = gsensor_set_enable(data, true);
- if (ret == 0) {
- data->enabled = true;
- schedule_delayed_work(&data->poll_work, 0);
- gsensor_update_orientation(data);
- }
- } else if (!val && data->enabled) {
- cancel_delayed_work_sync(&data->poll_work);
- ret = gsensor_set_enable(data, false);
- if (ret == 0)
- data->enabled = false;
- }
-
- mutex_unlock(&data->lock);
- return ret < 0 ? ret : count;
- }
- static struct kobj_attribute enable_attr = __ATTR_RW(enable);
- static ssize_t screen_orientation_show(struct kobject *kobj,
- struct kobj_attribute *attr, char *buf)
- {
- return sprintf(buf, "%s\n", g_data ? g_data->orientation_str : "unknown");
- }
- static struct kobj_attribute screen_orientation_attr = __ATTR_RO(screen_orientation);
- static ssize_t instantaneous_orientation_show(struct kobject *kobj,
- struct kobj_attribute *attr, char *buf)
- {
- return sprintf(buf, "0x%02x\n", g_data ? g_data->orientation_hex : 0);
- }
- static struct kobj_attribute instantaneous_orientation_attr = __ATTR_RO(instantaneous_orientation);
- static ssize_t mode_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
- {
- u8 mode = 0;
- if (g_data && g_data->initialized)
- mode |= GSENSOR_MODE_INITIALIZED;
- return sprintf(buf, "0x%02x\n", mode);
- }
- static ssize_t mode_store(struct kobject *kobj, struct kobj_attribute *attr,
- const char *buf, size_t count)
- {
- struct gsensor_data *data = g_data;
- unsigned long val;
-
- if (!data)
- return -ENODEV;
-
- if (kstrtoul(buf, 0, &val))
- return -EINVAL;
-
- if (val & GSENSOR_MODE_SOFT_RESET) {
- mutex_lock(&data->lock);
- gsensor_init(data);
- data->orientation_hex = GSENSOR_ORIENT_LANDSCAPE_HEX;
- strcpy(data->orientation_str, GSENSOR_ORIENT_LANDSCAPE_STR);
- mutex_unlock(&data->lock);
- }
-
- return count;
- }
- static struct kobj_attribute mode_attr = __ATTR_RW(mode);
- static ssize_t state_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
- {
- u8 state = 0;
- if (g_data && g_data->enabled)
- state |= 0x01;
- return sprintf(buf, "0x%02x\n", state);
- }
- static struct kobj_attribute state_attr = __ATTR_RO(state);
- static ssize_t raw_data_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
- {
- int x, y, z;
-
- if (!g_data)
- return -ENODEV;
-
- if (gsensor_read_data(g_data, &x, &y, &z) < 0)
- return -EIO;
-
- return sprintf(buf, "%d %d %d\n", x, y, z);
- }
- static struct kobj_attribute raw_data_attr = __ATTR_RO(raw_data);
- static struct attribute *gsensor_attrs[] = {
- &enable_attr.attr,
- &screen_orientation_attr.attr,
- &instantaneous_orientation_attr.attr,
- &mode_attr.attr,
- &state_attr.attr,
- &raw_data_attr.attr,
- NULL,
- };
- static const struct attribute_group gsensor_attr_group = {
- .attrs = gsensor_attrs,
- };
- /* I2C Probe/Remove */
- static int gsensor_probe(struct i2c_client *client)
- {
- struct gsensor_data *data;
- int ret;
-
- dev_info(&client->dev, "Gsensor probe\n");
-
- data = kzalloc(sizeof(*data), GFP_KERNEL);
- if (!data)
- return -ENOMEM;
-
- data->client = client;
- mutex_init(&data->lock);
- INIT_DELAYED_WORK(&data->poll_work, gsensor_poll_work);
- data->orientation_hex = GSENSOR_ORIENT_LANDSCAPE_HEX;
- strcpy(data->orientation_str, GSENSOR_ORIENT_LANDSCAPE_STR);
- data->pending_orientation = GSENSOR_ORIENT_UNKNOWN_HEX;
- data->last_change_jiffies = jiffies;
-
- i2c_set_clientdata(client, data);
-
- ret = gsensor_init(data);
- if (ret < 0) {
- dev_err(&client->dev, "Init failed: %d\n", ret);
- goto err_free;
- }
-
- g_data = data;
-
- if (vfiec_kobj) {
- data->gsensor_kobj = kobject_create_and_add("gsensor", vfiec_kobj);
- if (data->gsensor_kobj) {
- ret = sysfs_create_group(data->gsensor_kobj, &gsensor_attr_group);
- if (ret < 0) {
- dev_err(&client->dev, "Failed to create sysfs group\n");
- kobject_put(data->gsensor_kobj);
- goto err_free;
- }
- } else {
- dev_err(&client->dev, "Failed to create gsensor kobject\n");
- goto err_free;
- }
- } else {
- dev_err(&client->dev, "vfiec_kobj not available\n");
- goto err_free;
- }
-
- dev_info(&client->dev, "Gsensor driver loaded\n");
- return 0;
- err_free:
- kfree(data);
- g_data = NULL;
- return ret;
- }
- static int gsensor_remove(struct i2c_client *client)
- {
- struct gsensor_data *data = i2c_get_clientdata(client);
-
- if (data) {
- cancel_delayed_work_sync(&data->poll_work);
- if (data->enabled)
- gsensor_set_enable(data, false);
- if (data->gsensor_kobj) {
- sysfs_remove_group(data->gsensor_kobj, &gsensor_attr_group);
- kobject_put(data->gsensor_kobj);
- }
- mutex_destroy(&data->lock);
- kfree(data);
- g_data = NULL;
- }
- return 0;
- }
- /* I2C Driver */
- static const struct i2c_device_id gsensor_id_table[] = {
- { "sc7a20", 0 },
- { "gsensor", 0 },
- {}
- };
- MODULE_DEVICE_TABLE(i2c, gsensor_id_table);
- static struct i2c_driver gsensor_driver = {
- .driver = { .name = "verifone-gsensor" },
- .probe_new = gsensor_probe,
- .remove = gsensor_remove,
- .id_table = gsensor_id_table,
- };
- /* Init/Exit */
- int gsensor_init_main(void)
- {
- printk(KERN_INFO "gsensor: Registering driver\n");
- return i2c_add_driver(&gsensor_driver);
- }
- EXPORT_SYMBOL(gsensor_init_main);
- void gsensor_exit_main(void)
- {
- printk(KERN_INFO "gsensor: Unregistering driver\n");
- i2c_del_driver(&gsensor_driver);
- }
- EXPORT_SYMBOL(gsensor_exit_main);
- MODULE_LICENSE("GPL v2");
- MODULE_AUTHOR("Verifone, Inc.");
- MODULE_DESCRIPTION("Verifone Gsensor driver for screen orientation");
|