root/drivers/input/touchscreen/resistive-adc-touch.c
// SPDX-License-Identifier: GPL-2.0
/*
 * ADC generic resistive touchscreen (GRTS)
 * This is a generic input driver that connects to an ADC
 * given the channels in device tree, and reports events to the input
 * subsystem.
 *
 * Copyright (C) 2017,2018 Microchip Technology,
 * Author: Eugen Hristev <eugen.hristev@microchip.com>
 *
 */
#include <linux/input.h>
#include <linux/input/touchscreen.h>
#include <linux/iio/consumer.h>
#include <linux/iio/iio.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/property.h>

#define DRIVER_NAME                                     "resistive-adc-touch"
#define GRTS_DEFAULT_PRESSURE_MIN                       50000
#define GRTS_DEFAULT_PRESSURE_MAX                       65535
#define GRTS_MAX_POS_MASK                               GENMASK(11, 0)
#define GRTS_MAX_CHANNELS                               4

enum grts_ch_type {
        GRTS_CH_X,
        GRTS_CH_Y,
        GRTS_CH_PRESSURE,
        GRTS_CH_Z1,
        GRTS_CH_Z2,
        GRTS_CH_MAX = GRTS_CH_Z2 + 1
};

/**
 * struct grts_state - generic resistive touch screen information struct
 * @x_plate_ohms:       resistance of the X plate
 * @pressure_min:       number representing the minimum for the pressure
 * @pressure:           are we getting pressure info or not
 * @iio_chans:          list of channels acquired
 * @iio_cb:             iio_callback buffer for the data
 * @input:              the input device structure that we register
 * @prop:               touchscreen properties struct
 * @ch_map:             map of channels that are defined for the touchscreen
 */
struct grts_state {
        u32                             x_plate_ohms;
        u32                             pressure_min;
        bool                            pressure;
        struct iio_channel              *iio_chans;
        struct iio_cb_buffer            *iio_cb;
        struct input_dev                *input;
        struct touchscreen_properties   prop;
        u8                              ch_map[GRTS_CH_MAX];
};

static int grts_cb(const void *data, void *private)
{
        const u16 *touch_info = data;
        struct grts_state *st = private;
        unsigned int x, y, press = 0;

        x = touch_info[st->ch_map[GRTS_CH_X]];
        y = touch_info[st->ch_map[GRTS_CH_Y]];

        if (st->ch_map[GRTS_CH_PRESSURE] < GRTS_MAX_CHANNELS) {
                press = touch_info[st->ch_map[GRTS_CH_PRESSURE]];
        } else if (st->ch_map[GRTS_CH_Z1] < GRTS_MAX_CHANNELS) {
                unsigned int z1 = touch_info[st->ch_map[GRTS_CH_Z1]];
                unsigned int z2 = touch_info[st->ch_map[GRTS_CH_Z2]];
                unsigned int Rt;

                if (likely(x && z1)) {
                        Rt = z2;
                        Rt -= z1;
                        Rt *= st->x_plate_ohms;
                        Rt = DIV_ROUND_CLOSEST(Rt, 16);
                        Rt *= x;
                        Rt /= z1;
                        Rt = DIV_ROUND_CLOSEST(Rt, 256);
                        /*
                         * On increased pressure the resistance (Rt) is
                         * decreasing so, convert values to make it looks as
                         * real pressure.
                         */
                        if (Rt < GRTS_DEFAULT_PRESSURE_MAX)
                                press = GRTS_DEFAULT_PRESSURE_MAX - Rt;
                }
        }

        if ((!x && !y) || (st->pressure && (press < st->pressure_min))) {
                /* report end of touch */
                input_report_key(st->input, BTN_TOUCH, 0);
                input_sync(st->input);
                return 0;
        }

        /* report proper touch to subsystem*/
        touchscreen_report_pos(st->input, &st->prop, x, y, false);
        if (st->pressure)
                input_report_abs(st->input, ABS_PRESSURE, press);
        input_report_key(st->input, BTN_TOUCH, 1);
        input_sync(st->input);

        return 0;
}

static int grts_open(struct input_dev *dev)
{
        int error;
        struct grts_state *st = input_get_drvdata(dev);

        error = iio_channel_start_all_cb(st->iio_cb);
        if (error) {
                dev_err(dev->dev.parent, "failed to start callback buffer.\n");
                return error;
        }
        return 0;
}

static void grts_close(struct input_dev *dev)
{
        struct grts_state *st = input_get_drvdata(dev);

        iio_channel_stop_all_cb(st->iio_cb);
}

static void grts_disable(void *data)
{
        iio_channel_release_all_cb(data);
}

static int grts_map_channel(struct grts_state *st, struct device *dev,
                            enum grts_ch_type type, const char *name,
                            bool optional)
{
        int idx;

        idx = device_property_match_string(dev, "io-channel-names", name);
        if (idx < 0) {
                if (!optional)
                        return idx;
                idx = GRTS_MAX_CHANNELS;
        } else if (idx >= GRTS_MAX_CHANNELS) {
                return -EOVERFLOW;
        }

        st->ch_map[type] = idx;
        return 0;
}

static int grts_get_properties(struct grts_state *st, struct device *dev)
{
        int error;

        error = grts_map_channel(st, dev, GRTS_CH_X, "x", false);
        if (error)
                return error;

        error = grts_map_channel(st, dev, GRTS_CH_Y, "y", false);
        if (error)
                return error;

        /* pressure is optional */
        error = grts_map_channel(st, dev, GRTS_CH_PRESSURE, "pressure", true);
        if (error)
                return error;

        if (st->ch_map[GRTS_CH_PRESSURE] < GRTS_MAX_CHANNELS) {
                st->pressure = true;
                return 0;
        }

        /* if no pressure is defined, try optional z1 + z2 */
        error = grts_map_channel(st, dev, GRTS_CH_Z1, "z1", true);
        if (error)
                return error;

        if (st->ch_map[GRTS_CH_Z1] >= GRTS_MAX_CHANNELS)
                return 0;

        /* if z1 is provided z2 is not optional */
        error = grts_map_channel(st, dev, GRTS_CH_Z2, "z2", true);
        if (error)
                return error;

        error = device_property_read_u32(dev,
                                         "touchscreen-x-plate-ohms",
                                         &st->x_plate_ohms);
        if (error) {
                dev_err(dev, "can't get touchscreen-x-plate-ohms property\n");
                return error;
        }

        st->pressure = true;
        return 0;
}

static int grts_probe(struct platform_device *pdev)
{
        struct grts_state *st;
        struct input_dev *input;
        struct device *dev = &pdev->dev;
        int error;

        st = devm_kzalloc(dev, sizeof(struct grts_state), GFP_KERNEL);
        if (!st)
                return -ENOMEM;

        /* get the channels from IIO device */
        st->iio_chans = devm_iio_channel_get_all(dev);
        if (IS_ERR(st->iio_chans))
                return dev_err_probe(dev, PTR_ERR(st->iio_chans), "can't get iio channels\n");

        if (!device_property_present(dev, "io-channel-names"))
                return -ENODEV;

        error = grts_get_properties(st, dev);
        if (error) {
                dev_err(dev, "Failed to parse properties\n");
                return error;
        }

        if (st->pressure) {
                error = device_property_read_u32(dev,
                                                 "touchscreen-min-pressure",
                                                 &st->pressure_min);
                if (error) {
                        dev_dbg(dev, "can't get touchscreen-min-pressure property.\n");
                        st->pressure_min = GRTS_DEFAULT_PRESSURE_MIN;
                }
        }

        input = devm_input_allocate_device(dev);
        if (!input) {
                dev_err(dev, "failed to allocate input device.\n");
                return -ENOMEM;
        }

        input->name = DRIVER_NAME;
        input->id.bustype = BUS_HOST;
        input->open = grts_open;
        input->close = grts_close;

        input_set_abs_params(input, ABS_X, 0, GRTS_MAX_POS_MASK - 1, 0, 0);
        input_set_abs_params(input, ABS_Y, 0, GRTS_MAX_POS_MASK - 1, 0, 0);
        if (st->pressure)
                input_set_abs_params(input, ABS_PRESSURE, st->pressure_min,
                                     GRTS_DEFAULT_PRESSURE_MAX, 0, 0);

        input_set_capability(input, EV_KEY, BTN_TOUCH);

        /* parse optional device tree properties */
        touchscreen_parse_properties(input, false, &st->prop);

        st->input = input;
        input_set_drvdata(input, st);

        error = input_register_device(input);
        if (error) {
                dev_err(dev, "failed to register input device.");
                return error;
        }

        st->iio_cb = iio_channel_get_all_cb(dev, grts_cb, st);
        if (IS_ERR(st->iio_cb)) {
                dev_err(dev, "failed to allocate callback buffer.\n");
                return PTR_ERR(st->iio_cb);
        }

        error = devm_add_action_or_reset(dev, grts_disable, st->iio_cb);
        if (error) {
                dev_err(dev, "failed to add disable action.\n");
                return error;
        }

        return 0;
}

static const struct of_device_id grts_of_match[] = {
        {
                .compatible = "resistive-adc-touch",
        }, {
                /* sentinel */
        },
};

MODULE_DEVICE_TABLE(of, grts_of_match);

static struct platform_driver grts_driver = {
        .probe = grts_probe,
        .driver = {
                .name = DRIVER_NAME,
                .of_match_table = grts_of_match,
        },
};

module_platform_driver(grts_driver);

MODULE_AUTHOR("Eugen Hristev <eugen.hristev@microchip.com>");
MODULE_DESCRIPTION("Generic ADC Resistive Touch Driver");
MODULE_LICENSE("GPL v2");