/* SPDX-License-Identifier: GPL-2.0-only * * Verifone Gsensor driver for screen orientation */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 ST_ACCEL_STATUS_REG_ADDR 0x27 #define SC7A20_WHO_AM_I_VALUE 0x11 /* Configuration */ #define GSENSOR_DEBOUNCE_MS 300 #define GSENSOR_POLL_INTERVAL_MS 200 #define ST_ACCEL_NUMBER_DATA_CHANNELS 3 /* Driver Data Structure */ struct gsensor_data { struct i2c_client *client; struct mutex lock; struct delayed_work poll_work; struct kobject *gsensor_kobj; struct iio_dev *indio_dev; 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; /* IIO buffer data */ s16 buffer[ST_ACCEL_NUMBER_DATA_CHANNELS + 1]; u8 who_am_i; int irq; }; 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 raw acceleration data (12-bit, right-aligned) */ static int gsensor_read_raw_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; } static int gsensor_set_dataready_irq(struct gsensor_data *data, bool enable) { /* For SC7A20, data ready is automatically enabled when sensor is active */ /* This function is for compatibility with st_* trigger framework */ return 0; } static int gsensor_init_sensor(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 */ 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_raw_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)); } /* IIO Framework */ static int gsensor_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *ch, int *val, int *val2, long mask) { struct gsensor_data *data = iio_priv(indio_dev); int x, y, z; int err; switch (mask) { case IIO_CHAN_INFO_RAW: err = gsensor_read_raw_data(data, &x, &y, &z); if (err < 0) return err; switch (ch->channel2) { case IIO_MOD_X: *val = x; return IIO_VAL_INT; case IIO_MOD_Y: *val = y; return IIO_VAL_INT; case IIO_MOD_Z: *val = z; return IIO_VAL_INT; default: return -EINVAL; } case IIO_CHAN_INFO_SCALE: *val = 0; *val2 = 61035; /* 0.000061035 g */ return IIO_VAL_INT_PLUS_NANO; case IIO_CHAN_INFO_SAMP_FREQ: *val = 100; return IIO_VAL_INT; default: return -EINVAL; } } static int gsensor_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *ch, int val, int val2, long mask) { switch (mask) { case IIO_CHAN_INFO_SAMP_FREQ: if (val == 100) return 0; return -EINVAL; default: return -EINVAL; } } /* IIO channels */ static const struct iio_chan_spec gsensor_channels[] = { { .type = IIO_ACCEL, .modified = 1, .channel2 = IIO_MOD_X, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_SAMP_FREQ), .scan_index = 0, .scan_type = { .sign = 's', .realbits = 16, .storagebits = 16, .endianness = IIO_LE, }, }, { .type = IIO_ACCEL, .modified = 1, .channel2 = IIO_MOD_Y, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_SAMP_FREQ), .scan_index = 1, .scan_type = { .sign = 's', .realbits = 16, .storagebits = 16, .endianness = IIO_LE, }, }, { .type = IIO_ACCEL, .modified = 1, .channel2 = IIO_MOD_Z, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_SAMP_FREQ), .scan_index = 2, .scan_type = { .sign = 's', .realbits = 16, .storagebits = 16, .endianness = IIO_LE, }, }, IIO_CHAN_SOFT_TIMESTAMP(3), }; /* Trigger functions */ static int gsensor_trig_set_state(struct iio_trigger *trig, bool state) { struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); struct gsensor_data *data = iio_priv(indio_dev); return gsensor_set_dataready_irq(data, state); } static const struct iio_trigger_ops gsensor_trigger_ops = { .set_trigger_state = gsensor_trig_set_state, .validate_device = iio_trigger_validate_own_device, }; /* Buffer functions */ static int gsensor_buffer_postenable(struct iio_dev *indio_dev) { struct gsensor_data *data = iio_priv(indio_dev); int err; err = gsensor_set_enable(data, true); if (err < 0) return err; return gsensor_set_dataready_irq(data, true); } static int gsensor_buffer_predisable(struct iio_dev *indio_dev) { struct gsensor_data *data = iio_priv(indio_dev); int err; err = gsensor_set_dataready_irq(data, false); if (err < 0) return err; return gsensor_set_enable(data, false); } static const struct iio_buffer_setup_ops gsensor_buffer_setup_ops = { .postenable = &gsensor_buffer_postenable, .predisable = &gsensor_buffer_predisable, }; /* Trigger handler */ static irqreturn_t gsensor_trigger_handler(int irq, void *p) { struct iio_poll_func *pf = p; struct iio_dev *indio_dev = pf->indio_dev; struct gsensor_data *data = iio_priv(indio_dev); int x, y, z; gsensor_read_raw_data(data, &x, &y, &z); data->buffer[0] = x; data->buffer[1] = y; data->buffer[2] = z; iio_push_to_buffers_with_timestamp(indio_dev, data->buffer, iio_get_time_ns(indio_dev)); iio_trigger_notify_done(indio_dev->trig); return IRQ_HANDLED; } /* IIO attributes */ static ssize_t gsensor_show_available_freq(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "100\n"); } static IIO_DEVICE_ATTR(sampling_frequency_available, 0444, gsensor_show_available_freq, NULL, 0); static struct attribute *gsensor_iio_attrs[] = { &iio_dev_attr_sampling_frequency_available.dev_attr.attr, NULL, }; static const struct attribute_group gsensor_iio_attr_group = { .attrs = gsensor_iio_attrs, }; static const struct iio_info gsensor_iio_info = { .read_raw = gsensor_read_raw, .write_raw = gsensor_write_raw, .attrs = &gsensor_iio_attr_group, }; /* Allocate ring buffer and trigger */ static int gsensor_allocate_ring(struct iio_dev *indio_dev) { return devm_iio_triggered_buffer_setup(indio_dev->dev.parent, indio_dev, NULL, &gsensor_trigger_handler, &gsensor_buffer_setup_ops); } /* ==================== Verifone 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_sensor(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_raw_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 iio_dev *indio_dev; struct gsensor_data *data; int ret; indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); if (!indio_dev) return -ENOMEM; data = iio_priv(indio_dev); data->client = client; data->indio_dev = indio_dev; data->irq = client->irq; 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, indio_dev); ret = gsensor_init_sensor(data); if (ret < 0) { dev_err(&client->dev, "Init failed: %d\n", ret); return ret; } indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->info = &gsensor_iio_info; indio_dev->channels = gsensor_channels; indio_dev->num_channels = ARRAY_SIZE(gsensor_channels); ret = gsensor_allocate_ring(indio_dev); if (ret < 0) { dev_err(&client->dev, "Failed to allocate ring buffer: %d\n", ret); return ret; } if (data->irq > 0) { struct iio_trigger *trig; trig = devm_iio_trigger_alloc(&client->dev, "%s-dev%d", indio_dev->name, iio_device_id(indio_dev)); if (!trig) { ret = -ENOMEM; goto error; } trig->ops = &gsensor_trigger_ops; iio_trigger_set_drvdata(trig, indio_dev); ret = devm_iio_trigger_register(&client->dev, trig); if (ret < 0) goto error; indio_dev->trig = iio_trigger_get(trig); } ret = iio_device_register(indio_dev); if (ret < 0) { dev_err(&client->dev, "Failed to register IIO device: %d\n", ret); goto error; } 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 error_unregister; } } else { dev_err(&client->dev, "Failed to create gsensor kobject\n"); goto error_unregister; } } else { dev_err(&client->dev, "vfiec_kobj not available\n"); goto error_unregister; } /* ========== auto enable the sensor========== */ ret = gsensor_set_enable(data, true); if (ret == 0) { data->enabled = true; schedule_delayed_work(&data->poll_work, 0); gsensor_update_orientation(data); dev_info(&client->dev, "Gsensor auto-enabled on load\n"); } else { dev_warn(&client->dev, "Failed to auto-enable sensor: %d\n", ret); } /* ================================= */ dev_info(&client->dev, "Gsensor driver loaded (WHO_AM_I=0x%02x)\n", data->who_am_i); return 0; error_unregister: iio_device_unregister(indio_dev); error: return ret; } static int gsensor_remove(struct i2c_client *client) { struct iio_dev *indio_dev = i2c_get_clientdata(client); struct gsensor_data *data = iio_priv(indio_dev); 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); } iio_device_unregister(indio_dev); mutex_destroy(&data->lock); 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");