Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Linux/drivers/input/touchscreen/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 12 kB image not shown  

Quelle  chipone_icn8505.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0+
/*
 * Driver for ChipOne icn8505 i2c touchscreen controller
 *
 * Copyright (c) 2015-2018 Red Hat Inc.
 *
 * Red Hat authors:
 * Hans de Goede <hdegoede@redhat.com>
 */


#include <linux/unaligned.h>
#include <linux/acpi.h>
#include <linux/crc32.h>
#include <linux/delay.h>
#include <linux/firmware.h>
#include <linux/interrupt.h>
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/input/mt.h>
#include <linux/input/touchscreen.h>
#include <linux/module.h>

/* Normal operation mode defines */
#define ICN8505_REG_ADDR_WIDTH  16

#define ICN8505_REG_POWER  0x0004
#define ICN8505_REG_TOUCHDATA  0x1000
#define ICN8505_REG_CONFIGDATA  0x8000

/* ICN8505_REG_POWER commands */
#define ICN8505_POWER_ACTIVE  0x00
#define ICN8505_POWER_MONITOR  0x01
#define ICN8505_POWER_HIBERNATE  0x02
/*
 * The Android driver uses these to turn on/off the charger filter, but the
 * filter is way too aggressive making e.g. onscreen keyboards unusable.
 */

#define ICN8505_POWER_ENA_CHARGER_MODE 0x55
#define ICN8505_POWER_DIS_CHARGER_MODE 0x66

#define ICN8505_MAX_TOUCHES  10

/* Programming mode defines */
#define ICN8505_PROG_I2C_ADDR  0x30
#define ICN8505_PROG_REG_ADDR_WIDTH 24

#define MAX_FW_UPLOAD_TRIES  3

struct icn8505_touch {
 u8 slot;
 u8 x[2];
 u8 y[2];
 u8 pressure; /* Seems more like finger width then pressure really */
 u8 event;
/* The difference between 2 and 3 is unclear */
#define ICN8505_EVENT_NO_DATA 1 /* No finger seen yet since wakeup */
#define ICN8505_EVENT_UPDATE1 2 /* New or updated coordinates */
#define ICN8505_EVENT_UPDATE2 3 /* New or updated coordinates */
#define ICN8505_EVENT_END 4 /* Finger lifted */
} __packed;

struct icn8505_touch_data {
 u8 softbutton;
 u8 touch_count;
 struct icn8505_touch touches[ICN8505_MAX_TOUCHES];
} __packed;

struct icn8505_data {
 struct i2c_client *client;
 struct input_dev *input;
 struct touchscreen_properties prop;
 char firmware_name[32];
};

static int icn8505_read_xfer(struct i2c_client *client, u16 i2c_addr,
        int reg_addr, int reg_addr_width,
        void *data, int len, bool silent)
{
 u8 buf[3];
 int i, ret;
 struct i2c_msg msg[2] = {
  {
   .addr = i2c_addr,
   .buf = buf,
   .len = reg_addr_width / 8,
  },
  {
   .addr = i2c_addr,
   .flags = I2C_M_RD,
   .buf = data,
   .len = len,
  }
 };

 for (i = 0; i < (reg_addr_width / 8); i++)
  buf[i] = (reg_addr >> (reg_addr_width - (i + 1) * 8)) & 0xff;

 ret = i2c_transfer(client->adapter, msg, 2);
 if (ret != ARRAY_SIZE(msg)) {
  if (ret >= 0)
   ret = -EIO;
  if (!silent)
   dev_err(&client->dev,
    "Error reading addr %#x reg %#x: %d\n",
    i2c_addr, reg_addr, ret);
  return ret;
 }

 return 0;
}

static int icn8505_write_xfer(struct i2c_client *client, u16 i2c_addr,
         int reg_addr, int reg_addr_width,
         const void *data, int len, bool silent)
{
 u8 buf[3 + 32]; /* 3 bytes for 24 bit reg-addr + 32 bytes max len */
 int i, ret;
 struct i2c_msg msg = {
  .addr = i2c_addr,
  .buf = buf,
  .len = reg_addr_width / 8 + len,
 };

 if (WARN_ON(len > 32))
  return -EINVAL;

 for (i = 0; i < (reg_addr_width / 8); i++)
  buf[i] = (reg_addr >> (reg_addr_width - (i + 1) * 8)) & 0xff;

 memcpy(buf + reg_addr_width / 8, data, len);

 ret = i2c_transfer(client->adapter, &msg, 1);
 if (ret != 1) {
  if (ret >= 0)
   ret = -EIO;
  if (!silent)
   dev_err(&client->dev,
    "Error writing addr %#x reg %#x: %d\n",
    i2c_addr, reg_addr, ret);
  return ret;
 }

 return 0;
}

static int icn8505_read_data(struct icn8505_data *icn8505, int reg,
        void *buf, int len)
{
 return icn8505_read_xfer(icn8505->client, icn8505->client->addr, reg,
     ICN8505_REG_ADDR_WIDTH, buf, len, false);
}

static int icn8505_read_reg_silent(struct icn8505_data *icn8505, int reg)
{
 u8 buf;
 int error;

 error = icn8505_read_xfer(icn8505->client, icn8505->client->addr, reg,
      ICN8505_REG_ADDR_WIDTH, &buf, 1, true);
 if (error)
  return error;

 return buf;
}

static int icn8505_write_reg(struct icn8505_data *icn8505, int reg, u8 val)
{
 return icn8505_write_xfer(icn8505->client, icn8505->client->addr, reg,
      ICN8505_REG_ADDR_WIDTH, &val, 1, false);
}

static int icn8505_read_prog_data(struct icn8505_data *icn8505, int reg,
      void *buf, int len)
{
 return icn8505_read_xfer(icn8505->client, ICN8505_PROG_I2C_ADDR, reg,
     ICN8505_PROG_REG_ADDR_WIDTH, buf, len, false);
}

static int icn8505_write_prog_data(struct icn8505_data *icn8505, int reg,
       const void *buf, int len)
{
 return icn8505_write_xfer(icn8505->client, ICN8505_PROG_I2C_ADDR, reg,
      ICN8505_PROG_REG_ADDR_WIDTH, buf, len, false);
}

static int icn8505_write_prog_reg(struct icn8505_data *icn8505, int reg, u8 val)
{
 return icn8505_write_xfer(icn8505->client, ICN8505_PROG_I2C_ADDR, reg,
      ICN8505_PROG_REG_ADDR_WIDTH, &val, 1, false);
}

/*
 * Note this function uses a number of magic register addresses and values,
 * there are deliberately no defines for these because the algorithm is taken
 * from the icn85xx Android driver and I do not want to make up possibly wrong
 * names for the addresses and/or values.
 */

static int icn8505_try_fw_upload(struct icn8505_data *icn8505,
     const struct firmware *fw)
{
 struct device *dev = &icn8505->client->dev;
 size_t offset, count;
 int error;
 u8 buf[4];
 u32 crc;

 /* Put the controller in programming mode */
 error = icn8505_write_prog_reg(icn8505, 0xcc3355, 0x5a);
 if (error)
  return error;

 usleep_range(2000, 5000);

 error = icn8505_write_prog_reg(icn8505, 0x040400, 0x01);
 if (error)
  return error;

 usleep_range(2000, 5000);

 error = icn8505_read_prog_data(icn8505, 0x040002, buf, 1);
 if (error)
  return error;

 if (buf[0] != 0x85) {
  dev_err(dev, "Failed to enter programming mode\n");
  return -ENODEV;
 }

 usleep_range(1000, 5000);

 /* Enable CRC mode */
 error = icn8505_write_prog_reg(icn8505, 0x40028, 1);
 if (error)
  return error;

 /* Send the firmware to SRAM */
 for (offset = 0; offset < fw->size; offset += count) {
  count = min_t(size_t, fw->size - offset, 32);
  error = icn8505_write_prog_data(icn8505, offset,
           fw->data + offset, count);
  if (error)
   return error;
 }

 /* Disable CRC mode */
 error = icn8505_write_prog_reg(icn8505, 0x40028, 0);
 if (error)
  return error;

 /* Get and check length and CRC */
 error = icn8505_read_prog_data(icn8505, 0x40034, buf, 2);
 if (error)
  return error;

 if (get_unaligned_le16(buf) != fw->size) {
  dev_warn(dev, "Length mismatch after uploading fw\n");
  return -EIO;
 }

 error = icn8505_read_prog_data(icn8505, 0x4002c, buf, 4);
 if (error)
  return error;

 crc = crc32_be(0, fw->data, fw->size);
 if (get_unaligned_le32(buf) != crc) {
  dev_warn(dev, "CRC mismatch after uploading fw\n");
  return -EIO;
 }

 /* Boot controller from SRAM */
 error = icn8505_write_prog_reg(icn8505, 0x40400, 0x03);
 if (error)
  return error;

 usleep_range(2000, 5000);
 return 0;
}

static int icn8505_upload_fw(struct icn8505_data *icn8505)
{
 struct device *dev = &icn8505->client->dev;
 const struct firmware *fw;
 int i, error;

 /*
 * Always load the firmware, even if we don't need it at boot, we
 * we may need it at resume. Having loaded it once will make the
 * firmware class code cache it at suspend/resume.
 */

 error = firmware_request_platform(&fw, icn8505->firmware_name, dev);
 if (error) {
  dev_err(dev, "Firmware request error %d\n", error);
  return error;
 }

 /* Check if the controller is not already up and running */
 if (icn8505_read_reg_silent(icn8505, 0x000a) == 0x85)
  goto success;

 for (i = 1; i <= MAX_FW_UPLOAD_TRIES; i++) {
  error = icn8505_try_fw_upload(icn8505, fw);
  if (!error)
   goto success;

  dev_err(dev, "Failed to upload firmware: %d (attempt %d/%d)\n",
   error, i, MAX_FW_UPLOAD_TRIES);
  usleep_range(2000, 5000);
 }

success:
 release_firmware(fw);
 return error;
}

static bool icn8505_touch_active(u8 event)
{
 return event == ICN8505_EVENT_UPDATE1 ||
        event == ICN8505_EVENT_UPDATE2;
}

static irqreturn_t icn8505_irq(int irq, void *dev_id)
{
 struct icn8505_data *icn8505 = dev_id;
 struct device *dev = &icn8505->client->dev;
 struct icn8505_touch_data touch_data;
 int i, error;

 error = icn8505_read_data(icn8505, ICN8505_REG_TOUCHDATA,
      &touch_data, sizeof(touch_data));
 if (error) {
  dev_err(dev, "Error reading touch data: %d\n", error);
  return IRQ_HANDLED;
 }

 if (touch_data.touch_count > ICN8505_MAX_TOUCHES) {
  dev_warn(dev, "Too many touches %d > %d\n",
    touch_data.touch_count, ICN8505_MAX_TOUCHES);
  touch_data.touch_count = ICN8505_MAX_TOUCHES;
 }

 for (i = 0; i < touch_data.touch_count; i++) {
  struct icn8505_touch *touch = &touch_data.touches[i];
  bool act = icn8505_touch_active(touch->event);

  input_mt_slot(icn8505->input, touch->slot);
  input_mt_report_slot_state(icn8505->input, MT_TOOL_FINGER, act);
  if (!act)
   continue;

  touchscreen_report_pos(icn8505->input, &icn8505->prop,
           get_unaligned_le16(touch->x),
           get_unaligned_le16(touch->y),
           true);
 }

 input_mt_sync_frame(icn8505->input);
 input_report_key(icn8505->input, KEY_LEFTMETA,
    touch_data.softbutton == 1);
 input_sync(icn8505->input);

 return IRQ_HANDLED;
}

static int icn8505_probe_acpi(struct icn8505_data *icn8505, struct device *dev)
{
 const char *subsys;
 int error;

 subsys = acpi_get_subsystem_id(ACPI_HANDLE(dev));
 error = PTR_ERR_OR_ZERO(subsys);
 if (error == -ENODATA)
  subsys = "unknown";
 else if (error)
  return error;

 snprintf(icn8505->firmware_name, sizeof(icn8505->firmware_name),
   "chipone/icn8505-%s.fw", subsys);

 kfree_const(subsys);
 return 0;
}

static int icn8505_probe(struct i2c_client *client)
{
 struct device *dev = &client->dev;
 struct icn8505_data *icn8505;
 struct input_dev *input;
 __le16 resolution[2];
 int error;

 if (!client->irq) {
  dev_err(dev, "No irq specified\n");
  return -EINVAL;
 }

 icn8505 = devm_kzalloc(dev, sizeof(*icn8505), GFP_KERNEL);
 if (!icn8505)
  return -ENOMEM;

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

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

 input_set_capability(input, EV_ABS, ABS_MT_POSITION_X);
 input_set_capability(input, EV_ABS, ABS_MT_POSITION_Y);
 input_set_capability(input, EV_KEY, KEY_LEFTMETA);

 icn8505->client = client;
 icn8505->input = input;
 input_set_drvdata(input, icn8505);

 error = icn8505_probe_acpi(icn8505, dev);
 if (error)
  return error;

 error = icn8505_upload_fw(icn8505);
 if (error)
  return error;

 error = icn8505_read_data(icn8505, ICN8505_REG_CONFIGDATA,
    resolution, sizeof(resolution));
 if (error) {
  dev_err(dev, "Error reading resolution: %d\n", error);
  return error;
 }

 input_set_abs_params(input, ABS_MT_POSITION_X, 0,
        le16_to_cpu(resolution[0]) - 1, 0, 0);
 input_set_abs_params(input, ABS_MT_POSITION_Y, 0,
        le16_to_cpu(resolution[1]) - 1, 0, 0);

 touchscreen_parse_properties(input, true, &icn8505->prop);
 if (!input_abs_get_max(input, ABS_MT_POSITION_X) ||
     !input_abs_get_max(input, ABS_MT_POSITION_Y)) {
  dev_err(dev, "Error touchscreen-size-x and/or -y missing\n");
  return -EINVAL;
 }

 error = input_mt_init_slots(input, ICN8505_MAX_TOUCHES,
      INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
 if (error)
  return error;

 error = devm_request_threaded_irq(dev, client->irq, NULL, icn8505_irq,
     IRQF_ONESHOT, client->name, icn8505);
 if (error) {
  dev_err(dev, "Error requesting irq: %d\n", error);
  return error;
 }

 error = input_register_device(input);
 if (error)
  return error;

 i2c_set_clientdata(client, icn8505);
 return 0;
}

static int icn8505_suspend(struct device *dev)
{
 struct icn8505_data *icn8505 = i2c_get_clientdata(to_i2c_client(dev));

 disable_irq(icn8505->client->irq);

 icn8505_write_reg(icn8505, ICN8505_REG_POWER, ICN8505_POWER_HIBERNATE);

 return 0;
}

static int icn8505_resume(struct device *dev)
{
 struct icn8505_data *icn8505 = i2c_get_clientdata(to_i2c_client(dev));
 int error;

 error = icn8505_upload_fw(icn8505);
 if (error)
  return error;

 enable_irq(icn8505->client->irq);
 return 0;
}

static DEFINE_SIMPLE_DEV_PM_OPS(icn8505_pm_ops, icn8505_suspend, icn8505_resume);

static const struct acpi_device_id icn8505_acpi_match[] = {
 { "CHPN0001" },
 { }
};
MODULE_DEVICE_TABLE(acpi, icn8505_acpi_match);

static struct i2c_driver icn8505_driver = {
 .driver = {
  .name = "chipone_icn8505",
  .pm = pm_sleep_ptr(&icn8505_pm_ops),
  .acpi_match_table = icn8505_acpi_match,
 },
 .probe = icn8505_probe,
};

module_i2c_driver(icn8505_driver);

MODULE_DESCRIPTION("ChipOne icn8505 I2C Touchscreen Driver");
MODULE_AUTHOR("Hans de Goede ");
MODULE_LICENSE("GPL");

Messung V0.5
C=94 H=95 G=94

¤ Dauer der Verarbeitung: 0.14 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.