root/drivers/input/misc/mma8450.c
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 *  Driver for Freescale's 3-Axis Accelerometer MMA8450
 *
 *  Copyright (C) 2011 Freescale Semiconductor, Inc. All Rights Reserved.
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/mod_devicetable.h>

#define MMA8450_DRV_NAME        "mma8450"

#define MODE_CHANGE_DELAY_MS    100
#define POLL_INTERVAL           100
#define POLL_INTERVAL_MAX       500

/* register definitions */
#define MMA8450_STATUS          0x00
#define MMA8450_STATUS_ZXYDR    0x08

#define MMA8450_OUT_X8          0x01
#define MMA8450_OUT_Y8          0x02
#define MMA8450_OUT_Z8          0x03

#define MMA8450_OUT_X_LSB       0x05
#define MMA8450_OUT_X_MSB       0x06
#define MMA8450_OUT_Y_LSB       0x07
#define MMA8450_OUT_Y_MSB       0x08
#define MMA8450_OUT_Z_LSB       0x09
#define MMA8450_OUT_Z_MSB       0x0a

#define MMA8450_XYZ_DATA_CFG    0x16

#define MMA8450_CTRL_REG1       0x38
#define MMA8450_CTRL_REG2       0x39
#define MMA8450_ID              0xc6
#define MMA8450_WHO_AM_I        0x0f

static int mma8450_read(struct i2c_client *c, unsigned int off)
{
        int ret;

        ret = i2c_smbus_read_byte_data(c, off);
        if (ret < 0)
                dev_err(&c->dev,
                        "failed to read register 0x%02x, error %d\n",
                        off, ret);

        return ret;
}

static int mma8450_write(struct i2c_client *c, unsigned int off, u8 v)
{
        int error;

        error = i2c_smbus_write_byte_data(c, off, v);
        if (error < 0) {
                dev_err(&c->dev,
                        "failed to write to register 0x%02x, error %d\n",
                        off, error);
                return error;
        }

        return 0;
}

static int mma8450_read_block(struct i2c_client *c, unsigned int off,
                              u8 *buf, size_t size)
{
        int err;

        err = i2c_smbus_read_i2c_block_data(c, off, size, buf);
        if (err < 0) {
                dev_err(&c->dev,
                        "failed to read block data at 0x%02x, error %d\n",
                        MMA8450_OUT_X_LSB, err);
                return err;
        }

        return 0;
}

static void mma8450_poll(struct input_dev *input)
{
        struct i2c_client *c = input_get_drvdata(input);
        int x, y, z;
        int ret;
        u8 buf[6];

        ret = mma8450_read(c, MMA8450_STATUS);
        if (ret < 0)
                return;

        if (!(ret & MMA8450_STATUS_ZXYDR))
                return;

        ret = mma8450_read_block(c, MMA8450_OUT_X_LSB, buf, sizeof(buf));
        if (ret < 0)
                return;

        x = ((int)(s8)buf[1] << 4) | (buf[0] & 0xf);
        y = ((int)(s8)buf[3] << 4) | (buf[2] & 0xf);
        z = ((int)(s8)buf[5] << 4) | (buf[4] & 0xf);

        input_report_abs(input, ABS_X, x);
        input_report_abs(input, ABS_Y, y);
        input_report_abs(input, ABS_Z, z);
        input_sync(input);
}

/* Initialize the MMA8450 chip */
static int mma8450_open(struct input_dev *input)
{
        struct i2c_client *c = input_get_drvdata(input);
        int err;

        /* enable all events from X/Y/Z, no FIFO */
        err = mma8450_write(c, MMA8450_XYZ_DATA_CFG, 0x07);
        if (err)
                return err;

        /*
         * Sleep mode poll rate - 50Hz
         * System output data rate - 400Hz
         * Full scale selection - Active, +/- 2G
         */
        err = mma8450_write(c, MMA8450_CTRL_REG1, 0x01);
        if (err)
                return err;

        msleep(MODE_CHANGE_DELAY_MS);
        return 0;
}

static void mma8450_close(struct input_dev *input)
{
        struct i2c_client *c = input_get_drvdata(input);

        mma8450_write(c, MMA8450_CTRL_REG1, 0x00);
        mma8450_write(c, MMA8450_CTRL_REG2, 0x01);
}

/*
 * I2C init/probing/exit functions
 */
static int mma8450_probe(struct i2c_client *c)
{
        struct i2c_adapter *adapter = c->adapter;
        struct input_dev *input;
        int err, client_id;

        if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE |
                                                I2C_FUNC_SMBUS_BYTE_DATA))
                return dev_err_probe(&c->dev, -EINVAL,
                                     "I2C adapter doesn't support SMBUS BYTE");

        client_id = i2c_smbus_read_byte_data(c, MMA8450_WHO_AM_I);
        if (client_id != MMA8450_ID)
                return dev_err_probe(&c->dev, -EINVAL,
                                     "unexpected chip ID 0x%x (vs 0x%x)\n",
                                     client_id, MMA8450_ID);

        input = devm_input_allocate_device(&c->dev);
        if (!input)
                return -ENOMEM;

        input_set_drvdata(input, c);

        input->name = MMA8450_DRV_NAME;
        input->id.bustype = BUS_I2C;

        input->open = mma8450_open;
        input->close = mma8450_close;

        input_set_abs_params(input, ABS_X, -2048, 2047, 32, 32);
        input_set_abs_params(input, ABS_Y, -2048, 2047, 32, 32);
        input_set_abs_params(input, ABS_Z, -2048, 2047, 32, 32);

        err = input_setup_polling(input, mma8450_poll);
        if (err) {
                dev_err(&c->dev, "failed to set up polling\n");
                return err;
        }

        input_set_poll_interval(input, POLL_INTERVAL);
        input_set_max_poll_interval(input, POLL_INTERVAL_MAX);

        err = input_register_device(input);
        if (err) {
                dev_err(&c->dev, "failed to register input device\n");
                return err;
        }

        return 0;
}

static const struct i2c_device_id mma8450_id[] = {
        { MMA8450_DRV_NAME },
        { }
};
MODULE_DEVICE_TABLE(i2c, mma8450_id);

static const struct of_device_id mma8450_dt_ids[] = {
        { .compatible = "fsl,mma8450", },
        { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, mma8450_dt_ids);

static struct i2c_driver mma8450_driver = {
        .driver = {
                .name   = MMA8450_DRV_NAME,
                .of_match_table = mma8450_dt_ids,
        },
        .probe          = mma8450_probe,
        .id_table       = mma8450_id,
};

module_i2c_driver(mma8450_driver);

MODULE_AUTHOR("Freescale Semiconductor, Inc.");
MODULE_DESCRIPTION("MMA8450 3-Axis Accelerometer Driver");
MODULE_LICENSE("GPL");