blob: cf76327292242fdd7938190b9bc5e783b48a6257 [file] [log] [blame]
/*
* Copyright (C) 2013 Motorola Mobility LLC
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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.
*/
#include <linux/err.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/m4sensorhub.h>
#include <linux/m4sensorhub/MemMapUserSettings.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
#include <linux/slab.h>
#include <linux/wakelock.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/clk.h>
enum {
C55_OFF,
C55_ON,
C55_MODE_MAX
};
struct c55_ctrl_data {
int c55_ap_int_gpio;
int ap_c55_int_gpio;
int c55_ap_int_enabled;
struct wake_lock wake_lock;
struct regulator *reg_vddc;
struct regulator *reg_vddldo;
struct pinctrl *pctrl;
struct pinctrl_state *states[C55_MODE_MAX];
int c55_mode;
struct mutex ctrl_mutex; /* mutex to handle critical area */
int fw_verified;
};
const char *c55_pin_state_labels[C55_MODE_MAX] = {
"off",
"on"
};
#define NUM_GPIOS 2
const char *gpio_labels[NUM_GPIOS] = {
"gpio_ap_int",
"gpio_c55_int"
};
static irqreturn_t c55_ctrl_isr(int irq, void *data)
{
struct c55_ctrl_data *cdata = data;
pr_debug("%s: value=%d\n", __func__,
gpio_get_value(cdata->c55_ap_int_gpio));
/* Interrupt is active low */
if (gpio_get_value(cdata->c55_ap_int_gpio) == 0)
wake_lock(&cdata->wake_lock);
else
wake_unlock(&cdata->wake_lock);
return IRQ_HANDLED;
}
static void c55_ctrl_int_setup(struct c55_ctrl_data *cdata, int gpio)
{
int ret;
int irq = __gpio_to_irq(gpio);
unsigned int flags = 0;
if (cdata->c55_ap_int_gpio >= 0) {
/* Interrupt already registered */
return;
}
/* Interrupt is shared with user space */
flags |= IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;
flags |= IRQF_SHARED;
cdata->c55_ap_int_enabled = 1;
ret = request_threaded_irq(irq, c55_ctrl_isr, NULL,
flags, "c55_ctrl", cdata);
if (ret) {
pr_err("%s: IRQ request failed: %d\n", __func__, ret);
return;
}
cdata->c55_ap_int_gpio = gpio;
}
static int c55_ctrl_gpio_setup(struct c55_ctrl_data *cdata, struct device *dev)
{
int i;
if (of_gpio_count(dev->of_node) != NUM_GPIOS) {
dev_err(dev, "%s: gpio count is not %d.\n", __func__, NUM_GPIOS);
return -EINVAL;
}
for (i = 0; i < NUM_GPIOS; i++) {
enum of_gpio_flags flags;
int gpio;
gpio = of_get_gpio_flags(dev->of_node, i, &flags);
if (gpio < 0) {
pr_err("%s: of_get_gpio failed: %d\n", __func__, gpio);
return gpio;
}
gpio_request(gpio, gpio_labels[i]);
gpio_export(gpio, false);
gpio_export_link(dev, gpio_labels[i], gpio);
if ((flags & GPIOF_IN) == GPIOF_IN) {
gpio_direction_input(gpio);
c55_ctrl_int_setup(cdata, gpio);
} else {
cdata->ap_c55_int_gpio = gpio;
if ((flags & GPIOF_OUT_INIT_HIGH) == GPIOF_OUT_INIT_HIGH)
gpio_direction_output(gpio, 1);
else
gpio_direction_output(gpio, 0);
}
}
return 0;
}
static ssize_t c55_ctrl_enable(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct c55_ctrl_data *cdata = dev_get_drvdata(dev);
int mode;
if (kstrtoint(buf, 10, &mode) < 0)
return -EINVAL;
if (mode >= C55_MODE_MAX) {
dev_err(dev, "%s: Invalid mode %d\n", __func__, mode);
return -EINVAL;
}
if (m4sensorhub_get_current_mode() != NORMALMODE) {
dev_err(dev, "M4 not ready, Unable to set screen status\n");
return -EINVAL;
}
if (mode == cdata->c55_mode)
return count;
mutex_lock(&cdata->ctrl_mutex);
if (mode == C55_ON) {
pinctrl_select_state(cdata->pctrl, cdata->states[C55_ON]);
gpio_set_value(cdata->ap_c55_int_gpio, 1);
if (m4sensorhub_extern_set_audio_status(AUDIO_STATUS_ON) < 0) {
dev_err(dev, "Unable to set screen status to 0x01\n");
mutex_unlock(&cdata->ctrl_mutex);
return -EINVAL;
}
if (!cdata->c55_ap_int_enabled) {
cdata->c55_ap_int_enabled = 1;
enable_irq(__gpio_to_irq(cdata->c55_ap_int_gpio));
}
} else {
/* Disable C55->AP IRQ when turning off C55 */
if (cdata->c55_ap_int_enabled) {
disable_irq_nosync(
__gpio_to_irq(cdata->c55_ap_int_gpio));
cdata->c55_ap_int_enabled = 0;
}
if (m4sensorhub_extern_set_audio_status(AUDIO_STATUS_OFF) < 0) {
dev_err(dev, "Unable to set screen status to 0x00\n");
mutex_unlock(&cdata->ctrl_mutex);
return -EINVAL;
}
/* AP->C55 interrupt needs to be set low when C55 is off
* for current drain reasons */
gpio_set_value(cdata->ap_c55_int_gpio, 0);
pinctrl_select_state(cdata->pctrl, cdata->states[C55_OFF]);
/* Unlock wake lock in case it is active */
wake_unlock(&cdata->wake_lock);
}
cdata->c55_mode = mode;
mutex_unlock(&cdata->ctrl_mutex);
dev_info(dev, "%s: enable = %d\n", __func__, mode);
return count;
}
static DEVICE_ATTR(enable, S_IWUSR | S_IWGRP, NULL, c55_ctrl_enable);
static ssize_t c55_fw_verified_write(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct c55_ctrl_data *cdata = dev_get_drvdata(dev);
int verified = 0;
if (kstrtoint(buf, 10, &verified) < 0)
return -EINVAL;
mutex_lock(&cdata->ctrl_mutex);
cdata->fw_verified = verified;
mutex_unlock(&cdata->ctrl_mutex);
return count;
}
static ssize_t c55_fw_verified_read(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct c55_ctrl_data *cdata = dev_get_drvdata(dev);
int ret;
mutex_lock(&cdata->ctrl_mutex);
ret = sprintf(buf, "%d\n", cdata->fw_verified);
mutex_unlock(&cdata->ctrl_mutex);
return ret;
}
static DEVICE_ATTR(fw_verified, S_IRUSR | S_IRGRP | S_IWUSR | S_IWGRP,
c55_fw_verified_read, c55_fw_verified_write);
static int c55_ctrl_pin_setup(struct device *dev, struct c55_ctrl_data *cdata)
{
int i, ret = 0;
cdata->pctrl = devm_pinctrl_get(dev);
if (IS_ERR(cdata->pctrl)) {
ret = PTR_ERR(cdata->pctrl);
dev_dbg(dev, "no pinctrl handle\n");
}
for (i = 0; !ret && (i < C55_MODE_MAX); i++) {
cdata->states[i] = pinctrl_lookup_state(cdata->pctrl,
c55_pin_state_labels[i]);
if (IS_ERR(cdata->states[i])) {
ret = PTR_ERR(cdata->states[i]);
dev_dbg(dev, "no %s pinctrl state\n",
c55_pin_state_labels[i]);
}
}
if (!ret) {
ret = pinctrl_select_state(cdata->pctrl,
cdata->states[C55_OFF]);
if (ret)
dev_dbg(dev, "failed to activate %s pinctrl state\n",
c55_pin_state_labels[C55_OFF]);
}
return ret;
}
static int c55_ctrl_probe(struct platform_device *pdev)
{
struct c55_ctrl_data *cdata;
int ret;
if (!pdev->dev.of_node) {
/* Platform data not currently supported */
dev_err(&pdev->dev, "%s: of devtree data not found\n", __func__);
return -EINVAL;
}
cdata = devm_kzalloc(&pdev->dev, sizeof(*cdata), GFP_KERNEL);
if (cdata == NULL) {
dev_err(&pdev->dev, "%s: devm_kzalloc failed.\n", __func__);
return -ENOMEM;
}
mutex_init(&cdata->ctrl_mutex);
ret = c55_ctrl_pin_setup(&pdev->dev, cdata);
if (ret) {
dev_err(&pdev->dev, "%s: c55_ctrl_pin_setup failed.\n",
__func__);
return ret;
}
cdata->c55_ap_int_gpio = -1;
cdata->ap_c55_int_gpio = -1;
ret = c55_ctrl_gpio_setup(cdata, &pdev->dev);
if (ret) {
dev_err(&pdev->dev, "%s: c55_ctrl_gpio_setup failed.\n", __func__);
return ret;
}
cdata->reg_vddc = devm_regulator_get(&pdev->dev, "vddc");
if (IS_ERR(cdata->reg_vddc))
cdata->reg_vddc = NULL;
else if (regulator_enable(cdata->reg_vddc))
dev_err(&pdev->dev, "regulator_enable failed for vddc\n");
cdata->reg_vddldo = devm_regulator_get(&pdev->dev, "vddldo");
if (IS_ERR(cdata->reg_vddldo))
cdata->reg_vddldo = NULL;
else if (regulator_enable(cdata->reg_vddldo))
dev_err(&pdev->dev, "regulator_enable failed for vddldo\n");
/* M4 by default has C55 ON at power up */
cdata->c55_mode = C55_ON;
ret = device_create_file(&pdev->dev, &dev_attr_enable);
if (ret) {
dev_err(&pdev->dev, "%s: c55_ctrl creating set_mode failed.\n",
__func__);
return ret;
}
cdata->fw_verified = 0;
ret = device_create_file(&pdev->dev, &dev_attr_fw_verified);
if (ret) {
dev_err(&pdev->dev, "%s: c55_ctrl creating fw_verified failed.\n",
__func__);
return ret;
}
wake_lock_init(&cdata->wake_lock, WAKE_LOCK_SUSPEND, "c55_ctrl");
platform_set_drvdata(pdev, cdata);
return 0;
}
static int c55_ctrl_remove(struct platform_device *pdev)
{
device_remove_file(&pdev->dev, &dev_attr_enable);
return 0;
}
#ifdef CONFIG_PM
static int c55_ctrl_suspend(struct platform_device *dev, pm_message_t state)
{
struct c55_ctrl_data *cdata = dev_get_drvdata(&dev->dev);
if (cdata->c55_mode != C55_OFF) {
dev_warn(&dev->dev, "C55 still ON when going into suspend\n");
/* Disable C55->AP IRQ when turning off C55 */
if (cdata->c55_ap_int_enabled) {
disable_irq_nosync(
__gpio_to_irq(cdata->c55_ap_int_gpio));
cdata->c55_ap_int_enabled = 0;
}
if (m4sensorhub_extern_set_audio_status(AUDIO_STATUS_OFF) < 0) {
dev_err(&dev->dev,
"Unable to set screen status to 0x00\n");
}
/* AP->C55 interrupt needs to be set low when C55 is off
* for current drain reasons */
gpio_set_value(cdata->ap_c55_int_gpio, 0);
cdata->c55_mode = C55_OFF;
}
pinctrl_select_state(cdata->pctrl, cdata->states[C55_OFF]);
return 0;
}
static int c55_ctrl_resume(struct platform_device *dev)
{
struct c55_ctrl_data *cdata = dev_get_drvdata(&dev->dev);
pinctrl_select_state(cdata->pctrl, cdata->states[C55_ON]);
return 0;
}
#else
#define c55_ctrl_suspend NULL
#define c55_ctrl_resume NULL
#endif
static struct of_device_id c55_ctrl_match[] = {
{.compatible = "ti,c55-ctrl",},
{},
};
MODULE_DEVICE_TABLE(of, c55_ctrl_match);
static const struct platform_device_id c55_ctrl_id_table[] = {
{"c55_ctrl", 0},
{},
};
MODULE_DEVICE_TABLE(of, c55_ctrl_id_table);
static struct platform_driver c55_ctrl_driver = {
.driver = {
.name = "c55_ctrl",
.owner = THIS_MODULE,
.of_match_table = c55_ctrl_match,
},
.probe = c55_ctrl_probe,
.remove = c55_ctrl_remove,
.suspend = c55_ctrl_suspend,
.resume = c55_ctrl_resume,
.id_table = c55_ctrl_id_table,
};
static int __init c55_ctrl_init(void)
{
return platform_driver_register(&c55_ctrl_driver);
}
static void __exit c55_ctrl_exit(void)
{
platform_driver_unregister(&c55_ctrl_driver);
}
module_init(c55_ctrl_init);
module_exit(c55_ctrl_exit);
MODULE_ALIAS("platform:c55_ctrl");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Motorola");
MODULE_DESCRIPTION("TI C55 control driver");