283 lines
6.7 KiB
C
283 lines
6.7 KiB
C
/*
|
|
* MEI client driver for Intel Precise Touch and Stylus
|
|
*
|
|
* Copyright (c) 2016, Intel Corporation.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms and conditions of the GNU General Public License,
|
|
* version 2, as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*/
|
|
|
|
#include <linux/mei_cl_bus.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mod_devicetable.h>
|
|
#include <linux/hid.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/intel_ipts_if.h>
|
|
|
|
#include "ipts.h"
|
|
#include "ipts-hid.h"
|
|
#include "ipts-msg-handler.h"
|
|
#include "ipts-mei-msgs.h"
|
|
#include "ipts-binary-spec.h"
|
|
#include "ipts-state.h"
|
|
|
|
#define IPTS_DRIVER_NAME "ipts"
|
|
#define IPTS_MEI_UUID UUID_LE(0x3e8d0870, 0x271a, 0x4208, \
|
|
0x8e, 0xb5, 0x9a, 0xcb, 0x94, 0x02, 0xae, 0x04)
|
|
|
|
static struct mei_cl_device_id ipts_mei_cl_tbl[] = {
|
|
{ "", IPTS_MEI_UUID, MEI_CL_VERSION_ANY},
|
|
{}
|
|
};
|
|
|
|
static ssize_t sensor_mode_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
ipts_info_t *ipts;
|
|
ipts = dev_get_drvdata(dev);
|
|
|
|
return sprintf(buf, "%d\n", ipts->sensor_mode);
|
|
}
|
|
|
|
//TODO: Verify the function implementation
|
|
static ssize_t sensor_mode_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf,
|
|
size_t count)
|
|
{
|
|
int ret;
|
|
long val;
|
|
ipts_info_t *ipts;
|
|
|
|
ipts = dev_get_drvdata(dev);
|
|
ret = kstrtol(buf, 10, &val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ipts_dbg(ipts, "try sensor mode = %ld\n", val);
|
|
|
|
switch (val) {
|
|
case TOUCH_SENSOR_MODE_HID:
|
|
break;
|
|
case TOUCH_SENSOR_MODE_RAW_DATA:
|
|
break;
|
|
default:
|
|
ipts_err(ipts, "sensor mode %ld is not supported\n", val);
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t device_info_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
ipts_info_t *ipts;
|
|
|
|
ipts = dev_get_drvdata(dev);
|
|
return sprintf(buf, "vendor id = 0x%04hX\n"
|
|
"device id = 0x%04hX\n"
|
|
"HW rev = 0x%08X\n"
|
|
"firmware rev = 0x%08X\n",
|
|
ipts->device_info.vendor_id, ipts->device_info.device_id,
|
|
ipts->device_info.hw_rev, ipts->device_info.fw_rev);
|
|
}
|
|
|
|
static DEVICE_ATTR_RW(sensor_mode);
|
|
static DEVICE_ATTR_RO(device_info);
|
|
|
|
static struct attribute *ipts_attrs[] = {
|
|
&dev_attr_sensor_mode.attr,
|
|
&dev_attr_device_info.attr,
|
|
NULL
|
|
};
|
|
|
|
static const struct attribute_group ipts_grp = {
|
|
.attrs = ipts_attrs,
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(mei, ipts_mei_cl_tbl);
|
|
|
|
static void raw_data_work_func(struct work_struct *work)
|
|
{
|
|
ipts_info_t *ipts = container_of(work, ipts_info_t, raw_data_work);
|
|
|
|
ipts_handle_processed_data(ipts);
|
|
}
|
|
|
|
static void gfx_status_work_func(struct work_struct *work)
|
|
{
|
|
ipts_info_t *ipts = container_of(work, ipts_info_t, gfx_status_work);
|
|
ipts_state_t state;
|
|
int status = ipts->gfx_status;
|
|
|
|
ipts_dbg(ipts, "notify gfx status : %d\n", status);
|
|
|
|
state = ipts_get_state(ipts);
|
|
|
|
if (state == IPTS_STA_RAW_DATA_STARTED || state == IPTS_STA_HID_STARTED) {
|
|
if (status == IPTS_NOTIFY_STA_BACKLIGHT_ON &&
|
|
ipts->display_status == false) {
|
|
ipts_send_sensor_clear_mem_window_cmd(ipts);
|
|
ipts->display_status = true;
|
|
} else if (status == IPTS_NOTIFY_STA_BACKLIGHT_OFF &&
|
|
ipts->display_status == true) {
|
|
ipts_send_sensor_quiesce_io_cmd(ipts);
|
|
ipts->display_status = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* event loop */
|
|
static int ipts_mei_cl_event_thread(void *data)
|
|
{
|
|
ipts_info_t *ipts = (ipts_info_t *)data;
|
|
struct mei_cl_device *cldev = ipts->cldev;
|
|
ssize_t msg_len;
|
|
touch_sensor_msg_m2h_t m2h_msg;
|
|
|
|
while (!kthread_should_stop()) {
|
|
msg_len = mei_cldev_recv(cldev, (u8*)&m2h_msg, sizeof(m2h_msg));
|
|
if (msg_len <= 0) {
|
|
ipts_err(ipts, "error in reading m2h msg\n");
|
|
continue;
|
|
}
|
|
|
|
if (ipts_handle_resp(ipts, &m2h_msg, msg_len) != 0) {
|
|
ipts_err(ipts, "error in handling resp msg\n");
|
|
}
|
|
}
|
|
|
|
ipts_dbg(ipts, "!! end event loop !!\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void init_work_func(struct work_struct *work)
|
|
{
|
|
ipts_info_t *ipts = container_of(work, ipts_info_t, init_work);
|
|
|
|
ipts->sensor_mode = TOUCH_SENSOR_MODE_RAW_DATA;
|
|
ipts->display_status = true;
|
|
|
|
ipts_start(ipts);
|
|
}
|
|
|
|
static int ipts_mei_cl_probe(struct mei_cl_device *cldev,
|
|
const struct mei_cl_device_id *id)
|
|
{
|
|
int ret = 0;
|
|
ipts_info_t *ipts = NULL;
|
|
|
|
pr_info("probing Intel Precise Touch & Stylus\n");
|
|
|
|
// setup the DMA BIT mask, the system will choose the best possible
|
|
if (dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(64)) == 0) {
|
|
pr_info("IPTS using DMA_BIT_MASK(64)\n");
|
|
} else if (dma_coerce_mask_and_coherent(&cldev->dev,
|
|
DMA_BIT_MASK(32)) == 0) {
|
|
pr_info("IPTS using DMA_BIT_MASK(32)\n");
|
|
} else {
|
|
pr_err("IPTS: No suitable DMA available\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
ret = mei_cldev_enable(cldev);
|
|
if (ret < 0) {
|
|
pr_err("cannot enable IPTS\n");
|
|
return ret;
|
|
}
|
|
|
|
ipts = devm_kzalloc(&cldev->dev, sizeof(ipts_info_t), GFP_KERNEL);
|
|
if (ipts == NULL) {
|
|
ret = -ENOMEM;
|
|
goto disable_mei;
|
|
}
|
|
ipts->cldev = cldev;
|
|
mei_cldev_set_drvdata(cldev, ipts);
|
|
|
|
ipts->event_loop = kthread_run(ipts_mei_cl_event_thread, (void*)ipts,
|
|
"ipts_event_thread");
|
|
|
|
if(ipts_dbgfs_register(ipts, "ipts"))
|
|
pr_debug("cannot register debugfs for IPTS\n");
|
|
|
|
INIT_WORK(&ipts->init_work, init_work_func);
|
|
INIT_WORK(&ipts->raw_data_work, raw_data_work_func);
|
|
INIT_WORK(&ipts->gfx_status_work, gfx_status_work_func);
|
|
|
|
ret = sysfs_create_group(&cldev->dev.kobj, &ipts_grp);
|
|
if (ret != 0) {
|
|
pr_debug("cannot create sysfs for IPTS\n");
|
|
}
|
|
|
|
schedule_work(&ipts->init_work);
|
|
|
|
return 0;
|
|
|
|
disable_mei :
|
|
mei_cldev_disable(cldev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int ipts_mei_cl_remove(struct mei_cl_device *cldev)
|
|
{
|
|
ipts_info_t *ipts = mei_cldev_get_drvdata(cldev);
|
|
|
|
ipts_stop(ipts);
|
|
|
|
sysfs_remove_group(&cldev->dev.kobj, &ipts_grp);
|
|
ipts_hid_release(ipts);
|
|
ipts_dbgfs_deregister(ipts);
|
|
mei_cldev_disable(cldev);
|
|
|
|
kthread_stop(ipts->event_loop);
|
|
|
|
pr_info("IPTS removed\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct mei_cl_driver ipts_mei_cl_driver = {
|
|
.id_table = ipts_mei_cl_tbl,
|
|
.name = IPTS_DRIVER_NAME,
|
|
.probe = ipts_mei_cl_probe,
|
|
.remove = ipts_mei_cl_remove,
|
|
};
|
|
|
|
static int ipts_mei_cl_init(void)
|
|
{
|
|
int ret;
|
|
|
|
pr_info("IPTS %s() is called\n", __func__);
|
|
|
|
ret = mei_cldev_driver_register(&ipts_mei_cl_driver);
|
|
if (ret) {
|
|
pr_err("unable to register IPTS mei client driver\n");
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void __exit ipts_mei_cl_exit(void)
|
|
{
|
|
pr_info("IPTS %s() is called\n", __func__);
|
|
|
|
mei_cldev_driver_unregister(&ipts_mei_cl_driver);
|
|
}
|
|
|
|
module_init(ipts_mei_cl_init);
|
|
module_exit(ipts_mei_cl_exit);
|
|
|
|
MODULE_DESCRIPTION
|
|
("Intel(R) Management Engine Interface Client Driver for "\
|
|
"Intel Precision Touch and Sylus");
|
|
MODULE_LICENSE("GPL");
|