android_kernel_modules_leno.../drivers/hwmon/psh_byt.c

513 lines
12 KiB
C

/*
* psh.c - Baytrail PSH IA side driver
*
* (C) Copyright 2012 Intel Corporation
* Author: Alek Du <alek.du@intel.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License v2 as published by the
* Free Software Foundation.
*
* This program is distributed in the hope that 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.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
/*
* PSH IA side driver for Baytrail Platform
*/
#include <linux/device.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/pci.h>
#include <linux/circ_buf.h>
#include <linux/completion.h>
#include <linux/module.h>
#include <linux/dma-mapping.h>
#include <linux/string.h>
#include <linux/kthread.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/firmware.h>
#include <linux/i2c.h>
#include <linux/gpio.h>
#include <linux/acpi_gpio.h>
#include <linux/pm_runtime.h>
#include <asm/intel_vlv2.h>
#include <linux/workqueue.h>
#include "psh_ia_common.h"
#define GPIO_PSH_INT ((int)133)
/* need a global lock to check the psh driver access */
struct psh_ext_if {
struct device *hwmon_dev;
struct i2c_client *pshc;
char psh_frame[LBUF_MAX_CELL_SIZE];
int gpio_psh_ctl, gpio_psh_rst;
int gpio_psh_int;
int irq_disabled;
struct work_struct work;
struct workqueue_struct *wq;
};
int read_psh_data(struct psh_ia_priv *ia_data)
{
struct psh_ext_if *psh_if_info =
(struct psh_ext_if *)ia_data->platform_priv;
int cur_read = 0, ret = 0;
struct frame_head fh;
struct i2c_msg msg[2] = {
{
.addr = psh_if_info->pshc->addr,
.flags = I2C_M_RD,
.len = sizeof(fh),
.buf = (void *)&fh
},
{
.addr = psh_if_info->pshc->addr,
.flags = I2C_M_RD,
.buf = (void *)&psh_if_info->psh_frame
}
};
int sequent_dummy = 0;
static int loop = 0;
/* We may need to zero all the buffer */
pm_runtime_get_sync(&psh_if_info->pshc->dev);
/* Loop read till error or no more data */
while (!gpio_get_value(psh_if_info->gpio_psh_int)) {
char *ptr;
int len;
if (ia_data->cmd_in_progress == CMD_RESET)
break;
else if (ia_data->cmd_in_progress != CMD_INVALID)
schedule();
if (sequent_dummy >= 2) {
/* something wrong, check FW */
dev_dbg(&psh_if_info->pshc->dev,
"2 sequent dummy frame header read!");
break;
}
ret = i2c_transfer(psh_if_info->pshc->adapter, msg, 1);
if (ret != 1) {
dev_err(&psh_if_info->pshc->dev, "Read frame header error!"
" ret=%d\n", ret);
loop++;
break;
}
if (fh.sign == LBUF_CELL_SIGN) {
if (fh.length > LBUF_MAX_CELL_SIZE) {
dev_err(&psh_if_info->pshc->dev, "frame size is too big!\n");
ret = -EPERM;
break;
}
sequent_dummy = 0;
} else {
if (fh.sign || fh.length) {
dev_err(&psh_if_info->pshc->dev, "wrong fh (0x%x, 0x%x)\n",
fh.sign, fh.length);
ret = -EPERM;
break;
}
sequent_dummy++;
continue;
}
len = frame_size(fh.length) - sizeof(fh);
msg[1].len = len;
ret = i2c_transfer(psh_if_info->pshc->adapter, msg + 1, 1);
if (ret != 1) {
dev_err(&psh_if_info->pshc->dev, "Read main frame error!"
" ret=%d\n", ret);
break;
}
ptr = psh_if_info->psh_frame;
while (len > 0) {
struct cmd_resp *resp = (struct cmd_resp *)ptr;
u32 size = sizeof(*resp) + resp->data_len;
ret = ia_handle_frame(ia_data, ptr, size);
if (ret > 0) {
cur_read += ret;
if (cur_read > 250) {
cur_read = 0;
sysfs_notify(&psh_if_info->pshc->dev.kobj,
NULL, "data_size");
}
}
ptr += frame_size(size);
len -= frame_size(size);
}
}
pm_runtime_mark_last_busy(&psh_if_info->pshc->dev);
pm_runtime_put_autosuspend(&psh_if_info->pshc->dev);
if (cur_read)
sysfs_notify(&psh_if_info->pshc->dev.kobj, NULL, "data_size");
if (loop > 8) {
queue_work(psh_if_info->wq, &psh_if_info->work);
loop = 0;
}
return ret;
}
int process_send_cmd(struct psh_ia_priv *ia_data,
int ch, struct ia_cmd *cmd, int len)
{
struct psh_ext_if *psh_if_info =
(struct psh_ext_if *)ia_data->platform_priv;
int ret = 0;
struct i2c_msg i2c_cmd = {
.addr = psh_if_info->pshc->addr,
.flags = 0,
.len = len,
.buf = (void *)cmd
};
pm_runtime_get_sync(&psh_if_info->pshc->dev);
if (ch == 0 && cmd->cmd_id == CMD_RESET) {
if (psh_if_info->irq_disabled == 0) {
disable_irq(psh_if_info->pshc->irq);
psh_if_info->irq_disabled = 1;
}
/* first send soft reset to disable sensors running,
or sensor I2C bus may hang */
ret = i2c_transfer(psh_if_info->pshc->adapter, &i2c_cmd, 1);
msleep(200);
gpio_set_value(psh_if_info->gpio_psh_rst, 0);
usleep_range(10000, 10000);
gpio_set_value(psh_if_info->gpio_psh_rst, 1);
/* wait for pshfw to run */
msleep(1000);
if (psh_if_info->irq_disabled == 1) {
enable_irq(psh_if_info->pshc->irq);
psh_if_info->irq_disabled = 0;
}
} else if (ch == 0 && cmd->cmd_id == CMD_FW_UPDATE) {
if (psh_if_info->irq_disabled == 0) {
disable_irq(psh_if_info->pshc->irq);
psh_if_info->irq_disabled = 1;
}
msleep(1000);
ret = 0;
goto exit;
} else if (ch == 0 && psh_if_info->irq_disabled == 1) {
/* prevent sending command during firmware updating,
* or update will fail.
*/
ret = -EPERM;
goto exit;
}
ret = i2c_transfer(psh_if_info->pshc->adapter, &i2c_cmd, 1);
if (ret != 1) {
dev_err(&psh_if_info->pshc->dev, "sendcmd through I2C fail!\n");
ret = -EIO;
} else {
ret = 0;
}
pm_runtime_mark_last_busy(&psh_if_info->pshc->dev);
exit:
pm_runtime_put_autosuspend(&psh_if_info->pshc->dev);
return ret;
}
int do_setup_ddr(struct device *dev)
{
return 0;
}
static irqreturn_t psh_byt_irq_thread(int irq, void *dev)
{
struct i2c_client *client = (struct i2c_client *)dev;
struct psh_ia_priv *ia_data =
(struct psh_ia_priv *)dev_get_drvdata(&client->dev);
read_psh_data(ia_data);
return IRQ_HANDLED;
}
static void psh_byt_toggle_ctl_pin(struct device *dev,
int value)
{
struct psh_ia_priv *ia_data =
(struct psh_ia_priv *)dev_get_drvdata(dev);
struct psh_ext_if *psh_if_info =
(struct psh_ext_if *)ia_data->platform_priv;
if (psh_if_info->gpio_psh_ctl > 0) {
gpio_set_value(psh_if_info->gpio_psh_ctl, value);
if (value)
usleep_range(2000, 2000);
}
}
static int psh_byt_suspend(struct device *dev)
{
int ret;
struct i2c_client *client =
container_of(dev, struct i2c_client, dev);
ret = psh_ia_comm_suspend(dev);
if (ret)
return ret;
disable_irq(client->irq);
psh_byt_toggle_ctl_pin(dev, 0);
enable_irq_wake(client->irq);
return 0;
}
static int psh_byt_resume(struct device *dev)
{
struct psh_ia_priv *ia_data =
(struct psh_ia_priv *)dev_get_drvdata(dev);
struct i2c_client *client =
container_of(dev, struct i2c_client, dev);
psh_byt_toggle_ctl_pin(dev, 1);
read_psh_data(ia_data);
enable_irq(client->irq);
disable_irq_wake(client->irq);
return psh_ia_comm_resume(dev);
}
static int psh_byt_runtime_suspend(struct device *dev)
{
dev_dbg(dev, "PSH_BYT: %s\n", __func__);
psh_byt_toggle_ctl_pin(dev, 0);
return 0;
}
static int psh_byt_runtime_resume(struct device *dev)
{
dev_dbg(dev, "PSH_BYT: %s\n", __func__);
psh_byt_toggle_ctl_pin(dev, 1);
return 0;
}
static const struct dev_pm_ops psh_byt_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(psh_byt_suspend,
psh_byt_resume)
SET_RUNTIME_PM_OPS(psh_byt_runtime_suspend,
psh_byt_runtime_resume, NULL)
};
static void psh_work_func(struct work_struct *work)
{
struct psh_ext_if *psh_if_info =
container_of(work, struct psh_ext_if, work);
if (psh_if_info->irq_disabled == 0) {
disable_irq(psh_if_info->pshc->irq);
psh_if_info->irq_disabled = 1;
dev_err(&psh_if_info->pshc->dev, "disable psh irq\n");
}
}
/* FIXME: it will be a platform device */
static int psh_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
int ret = -EPERM;
struct psh_ia_priv *ia_data;
struct psh_ext_if *psh_if_info;
int rc;
psh_if_info = kzalloc(sizeof(*psh_if_info), GFP_KERNEL);
if (!psh_if_info) {
dev_err(&client->dev, "can not allocate psh_if_info\n");
goto psh_if_err;
}
ret = psh_ia_common_init(&client->dev, &ia_data);
if (ret) {
dev_err(&client->dev, "fail to init psh_ia_common\n");
goto psh_ia_err;
}
psh_if_info->hwmon_dev = hwmon_device_register(&client->dev);
if (!psh_if_info->hwmon_dev) {
dev_err(&client->dev, "fail to register hwmon device\n");
goto hwmon_err;
}
psh_if_info->pshc = client;
ia_data->platform_priv = psh_if_info;
psh_if_info->gpio_psh_ctl =
acpi_get_gpio_by_index(&client->dev, 1, NULL);
if (psh_if_info->gpio_psh_ctl < 0) {
dev_warn(&client->dev, "fail to get psh_ctl pin by ACPI\n");
} else {
rc = gpio_request(psh_if_info->gpio_psh_ctl, "psh_ctl");
if (rc) {
dev_warn(&client->dev, "fail to request psh_ctl pin\n");
psh_if_info->gpio_psh_ctl = -1;
} else {
gpio_export(psh_if_info->gpio_psh_ctl, 1);
gpio_direction_output(psh_if_info->gpio_psh_ctl, 1);
gpio_set_value(psh_if_info->gpio_psh_ctl, 1);
}
}
psh_if_info->gpio_psh_rst =
acpi_get_gpio_by_index(&client->dev, 0, NULL);
if (psh_if_info->gpio_psh_rst < 0) {
dev_warn(&client->dev, "failed to get psh_rst pin by ACPI\n");
} else {
rc = gpio_request(psh_if_info->gpio_psh_rst, "psh_rst");
if (rc) {
dev_warn(&client->dev, "fail to request psh_rst pin\n");
psh_if_info->gpio_psh_rst = -1;
} else {
gpio_export(psh_if_info->gpio_psh_rst, 1);
gpio_direction_output(psh_if_info->gpio_psh_rst, 1);
gpio_set_value(psh_if_info->gpio_psh_rst, 1);
}
}
psh_if_info->gpio_psh_int = (int)id->driver_data;
rc = gpio_request(psh_if_info->gpio_psh_int, "psh_int");
if (rc) {
dev_warn(&client->dev, "fail to request psh_int pin\n");
psh_if_info->gpio_psh_int = -1;
} else {
gpio_export(psh_if_info->gpio_psh_int, 1);
}
/* set the flag to to enable irq when need */
irq_set_status_flags(client->irq, IRQ_NOAUTOEN);
ret = request_threaded_irq(client->irq, NULL, psh_byt_irq_thread,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT, "psh_byt", client);
if (ret) {
dev_err(&client->dev, "fail to request irq\n");
goto irq_err;
}
psh_if_info->irq_disabled = 1;
psh_if_info->wq = create_singlethread_workqueue("psh_work");
if (!psh_if_info->wq) {
dev_err(&client->dev, "fail to create workqueue\n");
goto wq_err;
}
INIT_WORK(&psh_if_info->work, psh_work_func);
pm_runtime_set_active(&client->dev);
pm_runtime_use_autosuspend(&client->dev);
pm_runtime_set_autosuspend_delay(&client->dev, 0);
pm_runtime_enable(&client->dev);
return 0;
wq_err:
free_irq(client->irq, psh_if_info->pshc);
irq_err:
hwmon_device_unregister(psh_if_info->hwmon_dev);
hwmon_err:
psh_ia_common_deinit(&client->dev);
psh_ia_err:
kfree(psh_if_info);
psh_if_err:
return ret;
}
static int psh_remove(struct i2c_client *client)
{
struct psh_ia_priv *ia_data =
(struct psh_ia_priv *)dev_get_drvdata(&client->dev);
struct psh_ext_if *psh_if_info =
(struct psh_ext_if *)ia_data->platform_priv;
pm_runtime_get_sync(&client->dev);
pm_runtime_disable(&client->dev);
if (psh_if_info->wq)
destroy_workqueue(psh_if_info->wq);
free_irq(client->irq, psh_if_info->pshc);
gpio_unexport(psh_if_info->gpio_psh_rst);
gpio_unexport(psh_if_info->gpio_psh_ctl);
gpio_free(psh_if_info->gpio_psh_rst);
gpio_free(psh_if_info->gpio_psh_ctl);
hwmon_device_unregister(psh_if_info->hwmon_dev);
psh_ia_common_deinit(&client->dev);
return 0;
}
static void psh_shutdown(struct i2c_client *client)
{
struct psh_ia_priv *ia_data =
(struct psh_ia_priv *)dev_get_drvdata(&client->dev);
struct psh_ext_if *psh_if_info =
(struct psh_ext_if *)ia_data->platform_priv;
free_irq(client->irq, psh_if_info->pshc);
if (psh_if_info->gpio_psh_rst)
gpio_set_value(psh_if_info->gpio_psh_rst, 0);
dev_dbg(&psh_if_info->pshc->dev, "PSH_BYT: %s\n", __func__);
}
static const struct i2c_device_id psh_byt_id[] = {
{ "SMO91D0:00", GPIO_PSH_INT },
{ "SMO91D0", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, psh_byt_id);
static struct i2c_driver psh_byt_driver = {
.driver = {
.name = "psh_byt_i2c",
.owner = THIS_MODULE,
.pm = &psh_byt_pm_ops,
},
.probe = psh_probe,
.remove = psh_remove,
.id_table = psh_byt_id,
.shutdown = psh_shutdown,
};
module_i2c_driver(psh_byt_driver);
MODULE_LICENSE("GPL v2");