/* * psh.c - Baytrail PSH IA side driver * * (C) Copyright 2012 Intel Corporation * Author: Alek Du * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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");