10f382cadSJob Noorman // SPDX-License-Identifier: GPL-2.0-only 20f382cadSJob Noorman /* 30f382cadSJob Noorman * Driver for Himax hx83112b touchscreens 40f382cadSJob Noorman * 50f382cadSJob Noorman * Copyright (C) 2022 Job Noorman <job@noorman.info> 60f382cadSJob Noorman * 70f382cadSJob Noorman * This code is based on "Himax Android Driver Sample Code for QCT platform": 80f382cadSJob Noorman * 90f382cadSJob Noorman * Copyright (C) 2017 Himax Corporation. 100f382cadSJob Noorman */ 110f382cadSJob Noorman 120f382cadSJob Noorman #include <linux/delay.h> 130f382cadSJob Noorman #include <linux/err.h> 140f382cadSJob Noorman #include <linux/gpio/consumer.h> 150f382cadSJob Noorman #include <linux/i2c.h> 160f382cadSJob Noorman #include <linux/input.h> 170f382cadSJob Noorman #include <linux/input/mt.h> 180f382cadSJob Noorman #include <linux/input/touchscreen.h> 190f382cadSJob Noorman #include <linux/interrupt.h> 200f382cadSJob Noorman #include <linux/kernel.h> 210f382cadSJob Noorman #include <linux/regmap.h> 220f382cadSJob Noorman 230f382cadSJob Noorman #define HIMAX_ID_83112B 0x83112b 240f382cadSJob Noorman 250f382cadSJob Noorman #define HIMAX_MAX_POINTS 10 260f382cadSJob Noorman 27*05eab545SFelix Kaechele #define HIMAX_AHB_ADDR_BYTE_0 0x00 28*05eab545SFelix Kaechele #define HIMAX_AHB_ADDR_RDATA_BYTE_0 0x08 29*05eab545SFelix Kaechele #define HIMAX_AHB_ADDR_ACCESS_DIRECTION 0x0c 30*05eab545SFelix Kaechele #define HIMAX_AHB_ADDR_EVENT_STACK 0x30 310f382cadSJob Noorman 32*05eab545SFelix Kaechele #define HIMAX_AHB_CMD_ACCESS_DIRECTION_READ 0x00 33*05eab545SFelix Kaechele 34*05eab545SFelix Kaechele #define HIMAX_REG_ADDR_ICID 0x900000d0 350f382cadSJob Noorman 360f382cadSJob Noorman #define HIMAX_INVALID_COORD 0xffff 370f382cadSJob Noorman 380f382cadSJob Noorman struct himax_event_point { 390f382cadSJob Noorman __be16 x; 400f382cadSJob Noorman __be16 y; 410f382cadSJob Noorman } __packed; 420f382cadSJob Noorman 430f382cadSJob Noorman struct himax_event { 440f382cadSJob Noorman struct himax_event_point points[HIMAX_MAX_POINTS]; 450f382cadSJob Noorman u8 majors[HIMAX_MAX_POINTS]; 460f382cadSJob Noorman u8 pad0[2]; 470f382cadSJob Noorman u8 num_points; 480f382cadSJob Noorman u8 pad1[2]; 490f382cadSJob Noorman u8 checksum_fix; 500f382cadSJob Noorman } __packed; 510f382cadSJob Noorman 520f382cadSJob Noorman static_assert(sizeof(struct himax_event) == 56); 530f382cadSJob Noorman 540f382cadSJob Noorman struct himax_ts_data { 550f382cadSJob Noorman struct gpio_desc *gpiod_rst; 560f382cadSJob Noorman struct input_dev *input_dev; 570f382cadSJob Noorman struct i2c_client *client; 580f382cadSJob Noorman struct regmap *regmap; 590f382cadSJob Noorman struct touchscreen_properties props; 600f382cadSJob Noorman }; 610f382cadSJob Noorman 620f382cadSJob Noorman static const struct regmap_config himax_regmap_config = { 630f382cadSJob Noorman .reg_bits = 8, 640f382cadSJob Noorman .val_bits = 32, 650f382cadSJob Noorman .val_format_endian = REGMAP_ENDIAN_LITTLE, 660f382cadSJob Noorman }; 670f382cadSJob Noorman 680f382cadSJob Noorman static int himax_read_config(struct himax_ts_data *ts, u32 address, u32 *dst) 690f382cadSJob Noorman { 700f382cadSJob Noorman int error; 710f382cadSJob Noorman 72*05eab545SFelix Kaechele error = regmap_write(ts->regmap, HIMAX_AHB_ADDR_BYTE_0, address); 730f382cadSJob Noorman if (error) 740f382cadSJob Noorman return error; 750f382cadSJob Noorman 76*05eab545SFelix Kaechele error = regmap_write(ts->regmap, HIMAX_AHB_ADDR_ACCESS_DIRECTION, 77*05eab545SFelix Kaechele HIMAX_AHB_CMD_ACCESS_DIRECTION_READ); 780f382cadSJob Noorman if (error) 790f382cadSJob Noorman return error; 800f382cadSJob Noorman 81*05eab545SFelix Kaechele error = regmap_read(ts->regmap, HIMAX_AHB_ADDR_RDATA_BYTE_0, dst); 820f382cadSJob Noorman if (error) 830f382cadSJob Noorman return error; 840f382cadSJob Noorman 850f382cadSJob Noorman return 0; 860f382cadSJob Noorman } 870f382cadSJob Noorman 880f382cadSJob Noorman static void himax_reset(struct himax_ts_data *ts) 890f382cadSJob Noorman { 900f382cadSJob Noorman gpiod_set_value_cansleep(ts->gpiod_rst, 1); 910f382cadSJob Noorman 920f382cadSJob Noorman /* Delay copied from downstream driver */ 930f382cadSJob Noorman msleep(20); 940f382cadSJob Noorman gpiod_set_value_cansleep(ts->gpiod_rst, 0); 950f382cadSJob Noorman 960f382cadSJob Noorman /* 970f382cadSJob Noorman * The downstream driver doesn't contain this delay but is seems safer 980f382cadSJob Noorman * to include it. The range is just a guess that seems to work well. 990f382cadSJob Noorman */ 1000f382cadSJob Noorman usleep_range(1000, 1100); 1010f382cadSJob Noorman } 1020f382cadSJob Noorman 1030f382cadSJob Noorman static int himax_read_product_id(struct himax_ts_data *ts, u32 *product_id) 1040f382cadSJob Noorman { 1050f382cadSJob Noorman int error; 1060f382cadSJob Noorman 107*05eab545SFelix Kaechele error = himax_read_config(ts, HIMAX_REG_ADDR_ICID, product_id); 1080f382cadSJob Noorman if (error) 1090f382cadSJob Noorman return error; 1100f382cadSJob Noorman 1110f382cadSJob Noorman *product_id >>= 8; 1120f382cadSJob Noorman return 0; 1130f382cadSJob Noorman } 1140f382cadSJob Noorman 1150f382cadSJob Noorman static int himax_check_product_id(struct himax_ts_data *ts) 1160f382cadSJob Noorman { 1170f382cadSJob Noorman int error; 1180f382cadSJob Noorman u32 product_id; 1190f382cadSJob Noorman 1200f382cadSJob Noorman error = himax_read_product_id(ts, &product_id); 1210f382cadSJob Noorman if (error) 1220f382cadSJob Noorman return error; 1230f382cadSJob Noorman 1240f382cadSJob Noorman dev_dbg(&ts->client->dev, "Product id: %x\n", product_id); 1250f382cadSJob Noorman 1260f382cadSJob Noorman switch (product_id) { 1270f382cadSJob Noorman case HIMAX_ID_83112B: 1280f382cadSJob Noorman return 0; 1290f382cadSJob Noorman 1300f382cadSJob Noorman default: 1310f382cadSJob Noorman dev_err(&ts->client->dev, 1320f382cadSJob Noorman "Unknown product id: %x\n", product_id); 1330f382cadSJob Noorman return -EINVAL; 1340f382cadSJob Noorman } 1350f382cadSJob Noorman } 1360f382cadSJob Noorman 1370f382cadSJob Noorman static int himax_input_register(struct himax_ts_data *ts) 1380f382cadSJob Noorman { 1390f382cadSJob Noorman int error; 1400f382cadSJob Noorman 1410f382cadSJob Noorman ts->input_dev = devm_input_allocate_device(&ts->client->dev); 1420f382cadSJob Noorman if (!ts->input_dev) { 1430f382cadSJob Noorman dev_err(&ts->client->dev, "Failed to allocate input device\n"); 1440f382cadSJob Noorman return -ENOMEM; 1450f382cadSJob Noorman } 1460f382cadSJob Noorman 1470f382cadSJob Noorman ts->input_dev->name = "Himax Touchscreen"; 1480f382cadSJob Noorman 1490f382cadSJob Noorman input_set_capability(ts->input_dev, EV_ABS, ABS_MT_POSITION_X); 1500f382cadSJob Noorman input_set_capability(ts->input_dev, EV_ABS, ABS_MT_POSITION_Y); 1510f382cadSJob Noorman input_set_abs_params(ts->input_dev, ABS_MT_WIDTH_MAJOR, 0, 200, 0, 0); 1520f382cadSJob Noorman input_set_abs_params(ts->input_dev, ABS_MT_TOUCH_MAJOR, 0, 200, 0, 0); 1530f382cadSJob Noorman 1540f382cadSJob Noorman touchscreen_parse_properties(ts->input_dev, true, &ts->props); 1550f382cadSJob Noorman 1560f382cadSJob Noorman error = input_mt_init_slots(ts->input_dev, HIMAX_MAX_POINTS, 1570f382cadSJob Noorman INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED); 1580f382cadSJob Noorman if (error) { 1590f382cadSJob Noorman dev_err(&ts->client->dev, 1600f382cadSJob Noorman "Failed to initialize MT slots: %d\n", error); 1610f382cadSJob Noorman return error; 1620f382cadSJob Noorman } 1630f382cadSJob Noorman 1640f382cadSJob Noorman error = input_register_device(ts->input_dev); 1650f382cadSJob Noorman if (error) { 1660f382cadSJob Noorman dev_err(&ts->client->dev, 1670f382cadSJob Noorman "Failed to register input device: %d\n", error); 1680f382cadSJob Noorman return error; 1690f382cadSJob Noorman } 1700f382cadSJob Noorman 1710f382cadSJob Noorman return 0; 1720f382cadSJob Noorman } 1730f382cadSJob Noorman 1740f382cadSJob Noorman static u8 himax_event_get_num_points(const struct himax_event *event) 1750f382cadSJob Noorman { 1760f382cadSJob Noorman if (event->num_points == 0xff) 1770f382cadSJob Noorman return 0; 1780f382cadSJob Noorman else 1790f382cadSJob Noorman return event->num_points & 0x0f; 1800f382cadSJob Noorman } 1810f382cadSJob Noorman 1820f382cadSJob Noorman static bool himax_process_event_point(struct himax_ts_data *ts, 1830f382cadSJob Noorman const struct himax_event *event, 1840f382cadSJob Noorman int point_index) 1850f382cadSJob Noorman { 1860f382cadSJob Noorman const struct himax_event_point *point = &event->points[point_index]; 1870f382cadSJob Noorman u16 x = be16_to_cpu(point->x); 1880f382cadSJob Noorman u16 y = be16_to_cpu(point->y); 1890f382cadSJob Noorman u8 w = event->majors[point_index]; 1900f382cadSJob Noorman 1910f382cadSJob Noorman if (x == HIMAX_INVALID_COORD || y == HIMAX_INVALID_COORD) 1920f382cadSJob Noorman return false; 1930f382cadSJob Noorman 1940f382cadSJob Noorman input_mt_slot(ts->input_dev, point_index); 1950f382cadSJob Noorman input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, true); 1960f382cadSJob Noorman touchscreen_report_pos(ts->input_dev, &ts->props, x, y, true); 1970f382cadSJob Noorman input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, w); 1980f382cadSJob Noorman input_report_abs(ts->input_dev, ABS_MT_WIDTH_MAJOR, w); 1990f382cadSJob Noorman return true; 2000f382cadSJob Noorman } 2010f382cadSJob Noorman 2020f382cadSJob Noorman static void himax_process_event(struct himax_ts_data *ts, 2030f382cadSJob Noorman const struct himax_event *event) 2040f382cadSJob Noorman { 2050f382cadSJob Noorman int i; 2060f382cadSJob Noorman int num_points_left = himax_event_get_num_points(event); 2070f382cadSJob Noorman 2080f382cadSJob Noorman for (i = 0; i < HIMAX_MAX_POINTS && num_points_left > 0; i++) { 2090f382cadSJob Noorman if (himax_process_event_point(ts, event, i)) 2100f382cadSJob Noorman num_points_left--; 2110f382cadSJob Noorman } 2120f382cadSJob Noorman 2130f382cadSJob Noorman input_mt_sync_frame(ts->input_dev); 2140f382cadSJob Noorman input_sync(ts->input_dev); 2150f382cadSJob Noorman } 2160f382cadSJob Noorman 2170f382cadSJob Noorman static bool himax_verify_checksum(struct himax_ts_data *ts, 2180f382cadSJob Noorman const struct himax_event *event) 2190f382cadSJob Noorman { 2200f382cadSJob Noorman u8 *data = (u8 *)event; 2210f382cadSJob Noorman int i; 2220f382cadSJob Noorman u16 checksum = 0; 2230f382cadSJob Noorman 2240f382cadSJob Noorman for (i = 0; i < sizeof(*event); i++) 2250f382cadSJob Noorman checksum += data[i]; 2260f382cadSJob Noorman 2270f382cadSJob Noorman if ((checksum & 0x00ff) != 0) { 2280f382cadSJob Noorman dev_err(&ts->client->dev, "Wrong event checksum: %04x\n", 2290f382cadSJob Noorman checksum); 2300f382cadSJob Noorman return false; 2310f382cadSJob Noorman } 2320f382cadSJob Noorman 2330f382cadSJob Noorman return true; 2340f382cadSJob Noorman } 2350f382cadSJob Noorman 2360f382cadSJob Noorman static int himax_handle_input(struct himax_ts_data *ts) 2370f382cadSJob Noorman { 2380f382cadSJob Noorman int error; 2390f382cadSJob Noorman struct himax_event event; 2400f382cadSJob Noorman 241*05eab545SFelix Kaechele error = regmap_raw_read(ts->regmap, HIMAX_AHB_ADDR_EVENT_STACK, &event, 2420f382cadSJob Noorman sizeof(event)); 2430f382cadSJob Noorman if (error) { 2440f382cadSJob Noorman dev_err(&ts->client->dev, "Failed to read input event: %d\n", 2450f382cadSJob Noorman error); 2460f382cadSJob Noorman return error; 2470f382cadSJob Noorman } 2480f382cadSJob Noorman 2490f382cadSJob Noorman /* 2500f382cadSJob Noorman * Only process the current event when it has a valid checksum but 2510f382cadSJob Noorman * don't consider it a fatal error when it doesn't. 2520f382cadSJob Noorman */ 2530f382cadSJob Noorman if (himax_verify_checksum(ts, &event)) 2540f382cadSJob Noorman himax_process_event(ts, &event); 2550f382cadSJob Noorman 2560f382cadSJob Noorman return 0; 2570f382cadSJob Noorman } 2580f382cadSJob Noorman 2590f382cadSJob Noorman static irqreturn_t himax_irq_handler(int irq, void *dev_id) 2600f382cadSJob Noorman { 2610f382cadSJob Noorman int error; 2620f382cadSJob Noorman struct himax_ts_data *ts = dev_id; 2630f382cadSJob Noorman 2640f382cadSJob Noorman error = himax_handle_input(ts); 2650f382cadSJob Noorman if (error) 2660f382cadSJob Noorman return IRQ_NONE; 2670f382cadSJob Noorman 2680f382cadSJob Noorman return IRQ_HANDLED; 2690f382cadSJob Noorman } 2700f382cadSJob Noorman 2714d1c7cc6SUwe Kleine-König static int himax_probe(struct i2c_client *client) 2720f382cadSJob Noorman { 2730f382cadSJob Noorman int error; 2740f382cadSJob Noorman struct device *dev = &client->dev; 2750f382cadSJob Noorman struct himax_ts_data *ts; 2760f382cadSJob Noorman 2770f382cadSJob Noorman if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { 2780f382cadSJob Noorman dev_err(dev, "I2C check functionality failed\n"); 2790f382cadSJob Noorman return -ENXIO; 2800f382cadSJob Noorman } 2810f382cadSJob Noorman 2820f382cadSJob Noorman ts = devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL); 2830f382cadSJob Noorman if (!ts) 2840f382cadSJob Noorman return -ENOMEM; 2850f382cadSJob Noorman 2860f382cadSJob Noorman i2c_set_clientdata(client, ts); 2870f382cadSJob Noorman ts->client = client; 2880f382cadSJob Noorman 2890f382cadSJob Noorman ts->regmap = devm_regmap_init_i2c(client, &himax_regmap_config); 2900f382cadSJob Noorman error = PTR_ERR_OR_ZERO(ts->regmap); 2910f382cadSJob Noorman if (error) { 2920f382cadSJob Noorman dev_err(dev, "Failed to initialize regmap: %d\n", error); 2930f382cadSJob Noorman return error; 2940f382cadSJob Noorman } 2950f382cadSJob Noorman 2960f382cadSJob Noorman ts->gpiod_rst = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); 2970f382cadSJob Noorman error = PTR_ERR_OR_ZERO(ts->gpiod_rst); 2980f382cadSJob Noorman if (error) { 2990f382cadSJob Noorman dev_err(dev, "Failed to get reset GPIO: %d\n", error); 3000f382cadSJob Noorman return error; 3010f382cadSJob Noorman } 3020f382cadSJob Noorman 3030f382cadSJob Noorman himax_reset(ts); 3040f382cadSJob Noorman 3050f382cadSJob Noorman error = himax_check_product_id(ts); 3060f382cadSJob Noorman if (error) 3070f382cadSJob Noorman return error; 3080f382cadSJob Noorman 3090f382cadSJob Noorman error = himax_input_register(ts); 3100f382cadSJob Noorman if (error) 3110f382cadSJob Noorman return error; 3120f382cadSJob Noorman 3130f382cadSJob Noorman error = devm_request_threaded_irq(dev, client->irq, NULL, 3140f382cadSJob Noorman himax_irq_handler, IRQF_ONESHOT, 3150f382cadSJob Noorman client->name, ts); 3160f382cadSJob Noorman if (error) 3170f382cadSJob Noorman return error; 3180f382cadSJob Noorman 3190f382cadSJob Noorman return 0; 3200f382cadSJob Noorman } 3210f382cadSJob Noorman 3220f382cadSJob Noorman static int himax_suspend(struct device *dev) 3230f382cadSJob Noorman { 3240f382cadSJob Noorman struct himax_ts_data *ts = dev_get_drvdata(dev); 3250f382cadSJob Noorman 3260f382cadSJob Noorman disable_irq(ts->client->irq); 3270f382cadSJob Noorman return 0; 3280f382cadSJob Noorman } 3290f382cadSJob Noorman 3300f382cadSJob Noorman static int himax_resume(struct device *dev) 3310f382cadSJob Noorman { 3320f382cadSJob Noorman struct himax_ts_data *ts = dev_get_drvdata(dev); 3330f382cadSJob Noorman 3340f382cadSJob Noorman enable_irq(ts->client->irq); 3350f382cadSJob Noorman return 0; 3360f382cadSJob Noorman } 3370f382cadSJob Noorman 3380f382cadSJob Noorman static DEFINE_SIMPLE_DEV_PM_OPS(himax_pm_ops, himax_suspend, himax_resume); 3390f382cadSJob Noorman 3400f382cadSJob Noorman static const struct i2c_device_id himax_ts_id[] = { 3415852f2afSUwe Kleine-König { "hx83112b" }, 3420f382cadSJob Noorman { /* sentinel */ } 3430f382cadSJob Noorman }; 3440f382cadSJob Noorman MODULE_DEVICE_TABLE(i2c, himax_ts_id); 3450f382cadSJob Noorman 3460f382cadSJob Noorman #ifdef CONFIG_OF 3470f382cadSJob Noorman static const struct of_device_id himax_of_match[] = { 3480f382cadSJob Noorman { .compatible = "himax,hx83112b" }, 3490f382cadSJob Noorman { /* sentinel */ } 3500f382cadSJob Noorman }; 3510f382cadSJob Noorman MODULE_DEVICE_TABLE(of, himax_of_match); 3520f382cadSJob Noorman #endif 3530f382cadSJob Noorman 3540f382cadSJob Noorman static struct i2c_driver himax_ts_driver = { 355d8bde56dSUwe Kleine-König .probe = himax_probe, 3560f382cadSJob Noorman .id_table = himax_ts_id, 3570f382cadSJob Noorman .driver = { 3580f382cadSJob Noorman .name = "Himax-hx83112b-TS", 3590f382cadSJob Noorman .of_match_table = of_match_ptr(himax_of_match), 3600f382cadSJob Noorman .pm = pm_sleep_ptr(&himax_pm_ops), 3610f382cadSJob Noorman }, 3620f382cadSJob Noorman }; 3630f382cadSJob Noorman module_i2c_driver(himax_ts_driver); 3640f382cadSJob Noorman 3650f382cadSJob Noorman MODULE_AUTHOR("Job Noorman <job@noorman.info>"); 3660f382cadSJob Noorman MODULE_DESCRIPTION("Himax hx83112b touchscreen driver"); 3670f382cadSJob Noorman MODULE_LICENSE("GPL"); 368