android_kernel_samsung_on5x.../drivers/muic/universal/muic_task.c
2018-06-19 23:16:04 +02:00

664 lines
17 KiB
C

/*
* muic_task.c
*
* Copyright (C) 2014 Samsung Electronics
* Thomas Ryu <smilesr.ryu@samsung.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <linux/gpio.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/host_notify.h>
#include <linux/muic/muic.h>
#include <linux/muic/muic_afc.h>
#if defined(CONFIG_MUIC_NOTIFIER)
#include <linux/muic/muic_notifier.h>
#endif /* CONFIG_MUIC_NOTIFIER */
#if defined(CONFIG_OF)
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#endif /* CONFIG_OF */
#include "muic-internal.h"
#include "muic_apis.h"
#include "muic_state.h"
#include "muic_vps.h"
#include "muic_i2c.h"
#include "muic_sysfs.h"
#include "muic_debug.h"
#include "muic_dt.h"
#include "muic_regmap.h"
#define MUIC_INT_DETACH_MASK (0x1 << 1)
#define MUIC_INT_ATTACH_MASK (0x1 << 0)
#if !defined(CONFIG_MUIC_UNIVERSAL_SM5504)
#define MUIC_INT_OVP_EN_MASK (0x1 << 5)
#define MUIC_INT_VBUS_OFF_MASK (0x1 << 0)
#else
#define MUIC_INT_OVP_EN_MASK (0x1 << 7)
#endif
#if defined(CONFIG_MUIC_UNIVERSAL_SM5504)
#define MUIC_REG_CTRL_RESET_VALUE (0xE5)
#else
#define MUIC_REG_CTRL_RESET_VALUE (0x1F)
#endif
#define MUIC_REG_CTRL 0x02
#define MUIC_REG_INT1 0x03
#define MUIC_REG_INT2 0x04
#define MUIC_REG_INT3 0x05
extern struct muic_platform_data muic_pdata;
#if defined(CONFIG_MUIC_UNIVERSAL_SM5705_AFC)
/* SM5705 Interrupt 3 AFC register */
#define INT3_AFC_ERROR_SHIFT 5
#define INT3_AFC_STA_CHG_SHIFT 4
#define INT3_AFC_MULTI_BYTE_SHIFT 3
#define INT3_AFC_VBUS_UPDATE_SHIFT 2
#define INT3_AFC_ACCEPTED_SHIFT 1
#define INT3_AFC_TA_ATTACHED_SHIFT 0
#define INT3_AFC_ERROR_MASK (1 << INT3_AFC_ERROR_SHIFT)
#define INT3_AFC_STA_CHG_MASK (1 << INT3_AFC_STA_CHG_SHIFT)
#define INT3_AFC_MULTI_BYTE_MASK (1 << INT3_AFC_MULTI_BYTE_SHIFT)
#define INT3_AFC_VBUS_UPDATE_MASK (1 << INT3_AFC_VBUS_UPDATE_SHIFT)
#define INT3_AFC_ACCEPTED_MASK (1 << INT3_AFC_ACCEPTED_SHIFT)
#define INT3_AFC_TA_ATTACHED_MASK (1 << INT3_AFC_TA_ATTACHED_SHIFT)
static int muic_irq_handler_afc(muic_data_t *pmuic, int irq)
{
struct i2c_client *i2c = pmuic->i2c;
struct afc_ops *afcops = pmuic->regmapdesc->afcops;
int intr1, intr2, intr3, ret;
pr_info("%s:%s irq(%d)\n", pmuic->chip_name, __func__, irq);
/* read and clear interrupt status bits */
intr1 = muic_i2c_read_byte(i2c, MUIC_REG_INT1);
intr2 = muic_i2c_read_byte(i2c, MUIC_REG_INT2);
intr3 = muic_i2c_read_byte(i2c, MUIC_REG_INT3);
if ((intr1 < 0) || (intr2 < 0)) {
pr_err("%s: err read interrupt status [1:0x%x, 2:0x%x]\n",
__func__, intr1, intr2);
return INT_REQ_DISCARD;
}
if (intr1 & MUIC_INT_ATTACH_MASK)
{
int intr_tmp;
intr_tmp = muic_i2c_read_byte(i2c, MUIC_REG_INT1);
if (intr_tmp & 0x2)
{
pr_info("%s:%s attach/detach interrupt occurred\n",
pmuic->chip_name, __func__);
intr1 &= 0xFE;
}
intr1 |= intr_tmp;
}
if (intr1 & MUIC_INT_DETACH_MASK) {
cancel_delayed_work(&pmuic->afc_retry_work);
cancel_delayed_work(&pmuic->afc_restart_work);
}
pr_info("%s:%s intr[1:0x%x, 2:0x%x, 3:0x%x]\n", pmuic->chip_name, __func__,
intr1, intr2, intr3);
/* check for muic reset and recover for every interrupt occurred */
if ((intr1 & MUIC_INT_OVP_EN_MASK) ||
((intr1 == 0) && (intr2 == 0) && (intr3 == 0) && (irq != -1)))
{
int ctrl;
ctrl = muic_i2c_read_byte(i2c, MUIC_REG_CTRL);
if (ctrl == 0x1F)
{
/* CONTROL register is reset to 1F */
muic_print_reg_log();
muic_print_reg_dump(pmuic);
pr_err("%s: err muic could have been reseted. Initilize!!\n",
__func__);
muic_reg_init(pmuic);
muic_print_reg_dump(pmuic);
/* MUIC Interrupt On */
set_int_mask(pmuic, false);
}
if ((intr1 & MUIC_INT_ATTACH_MASK) == 0)
return INT_REQ_DISCARD;
}
pmuic->intr.intr1 = intr1;
pmuic->intr.intr2 = intr2;
pmuic->intr.intr3 = intr3;
if ((irq == -1) && (intr3 & INT3_AFC_TA_ATTACHED_MASK))
{
ret = muic_i2c_write_byte(i2c, 0x0F, 0x01);
if(ret < 0){
pr_err("%s: err write register value \n", __func__);
return INT_REQ_DISCARD;
}
return INT_REQ_DONE;
}
if ((intr1 & MUIC_INT_DETACH_MASK) && (intr2 & MUIC_INT_VBUS_OFF_MASK))
return INT_REQ_DONE;
if (intr3 & INT3_AFC_TA_ATTACHED_MASK) { /*AFC_TA_ATTACHED*/
return afcops->afc_ta_attach(pmuic->regmapdesc);
} else if (intr3 & INT3_AFC_ACCEPTED_MASK) { /*AFC_ACCEPTED*/
return afcops->afc_ta_accept(pmuic->regmapdesc);
} else if (intr3 & INT3_AFC_VBUS_UPDATE_MASK) { /*AFC_VBUS_UPDATE*/
return afcops->afc_vbus_update(pmuic->regmapdesc);
} else if (intr3 & INT3_AFC_MULTI_BYTE_MASK) { /*AFC_MULTI_BYTE*/
return afcops->afc_multi_byte(pmuic->regmapdesc);
} else if (intr3 & INT3_AFC_ERROR_MASK) { /*AFC_ERROR*/
cancel_delayed_work(&pmuic->afc_retry_work);
return afcops->afc_error(pmuic->regmapdesc);
} else if (intr3 & INT3_AFC_STA_CHG_MASK) { /*AFC_STA_CHG*/
return 0;
}
return INT_REQ_DONE;
}
#else
static int muic_irq_handler(muic_data_t *pmuic, int irq)
{
struct i2c_client *i2c = pmuic->i2c;
int intr1, intr2, ctrl = 0;
pr_info("%s:%s irq(%d)\n", pmuic->chip_name, __func__, irq);
#if defined(CONFIG_MUIC_UNIVERSAL_SM5504)
/* SM5504 needs 100ms delay */
msleep(100);
#endif
/* read and clear interrupt status bits */
intr1 = muic_i2c_read_byte(i2c, MUIC_REG_INT1);
intr2 = muic_i2c_read_byte(i2c, MUIC_REG_INT2);
if ((intr1 < 0) || (intr2 < 0)) {
pr_err("%s: err read interrupt status [1:0x%x, 2:0x%x]\n",
__func__, intr1, intr2);
return INT_REQ_DISCARD;
}
if (intr1 & MUIC_INT_ATTACH_MASK) {
int intr_tmp;
intr_tmp = muic_i2c_read_byte(i2c, MUIC_REG_INT1);
if (intr_tmp & 0x2) {
pr_info("%s:%s attach/detach interrupt occurred\n",
pmuic->chip_name, __func__);
intr1 &= 0xFE;
}
intr1 |= intr_tmp;
}
pr_info("%s:%s intr[1:0x%x, 2:0x%x]\n", pmuic->chip_name, __func__,
intr1, intr2);
ctrl = muic_i2c_read_byte(i2c, MUIC_REG_CTRL);
/* check for muic reset and recover for every interrupt occurred */
if ((intr1 & MUIC_INT_OVP_EN_MASK) ||
((ctrl == MUIC_REG_CTRL_RESET_VALUE) && (irq != -1))) {
if (ctrl == MUIC_REG_CTRL_RESET_VALUE) {
/* CONTROL register is reset to 1F */
muic_print_reg_log();
muic_print_reg_dump(pmuic);
pr_err("%s: err muic could have been reseted. Initilize!!\n",
__func__);
muic_reg_init(pmuic);
muic_print_reg_dump(pmuic);
/* MUIC Interrupt On */
set_int_mask(pmuic, false);
}
if ((intr1 & MUIC_INT_ATTACH_MASK) == 0)
return INT_REQ_DISCARD;
}
pmuic->intr.intr1 = intr1;
pmuic->intr.intr2 = intr2;
return INT_REQ_DONE;
}
#endif
enum max_intr_bits {
INTR1_ADC1K_MASK = (1<<3),
INTR1_ADCERR_MASK = (1<<2),
INTR1_ADC_MASK = (1<<0),
INTR2_VBVOLT_MASK = (1<<4),
INTR2_OVP_MASK = (1<<3),
INTR2_DCTTMR_MASK = (1<<2),
INTR2_CHGDETRUN_MASK = (1<<1),
INTR2_CHGTYP_MASK = (1<<0),
};
#define MAX77849_REG_INT1 (0x01)
#define MAX77849_REG_INT2 (0x02)
static irqreturn_t max77849_muic_irq_handler(muic_data_t *pmuic, int irq)
{
u8 intr1, intr2;
pr_info("%s:%s irq:%d\n", MUIC_DEV_NAME, __func__, irq);
intr1 = muic_i2c_read_byte(pmuic->i2c, MAX77849_REG_INT1);
intr2 = muic_i2c_read_byte(pmuic->i2c, MAX77849_REG_INT2);
if (intr1 & INTR1_ADC1K_MASK)
pr_info("%s ADC1K interrupt occured\n", __func__);
if (intr1 & INTR1_ADCERR_MASK)
pr_info("%s ADC1K interrupt occured\n", __func__);
if (intr1 & INTR1_ADCERR_MASK)
pr_info("%s ADCERR interrupt occured\n", __func__);
if (intr2 & INTR2_VBVOLT_MASK)
pr_info("%s VBVOLT interrupt occured\n", __func__);
if (intr2 & INTR2_OVP_MASK)
pr_info("%s OVP interrupt occured\n", __func__);
if (intr2 & INTR2_DCTTMR_MASK)
pr_info("%s DCTTMR interrupt occured\n", __func__);
if (intr2 & INTR2_CHGTYP_MASK)
pr_info("%s DCTTMR interrupt occured\n", __func__);
return INT_REQ_DONE;
}
static irqreturn_t muic_irq_thread(int irq, void *data)
{
muic_data_t *pmuic = data;
mutex_lock(&pmuic->muic_mutex);
pmuic->irq_n = irq;
if (pmuic->vps_table == VPS_TYPE_TABLE) {
if (max77849_muic_irq_handler(pmuic, irq) & INT_REQ_DONE)
muic_detect_dev(pmuic);
} else{
#if defined(CONFIG_MUIC_UNIVERSAL_SM5705_AFC)
if (muic_irq_handler_afc(pmuic, irq) & INT_REQ_DONE)
muic_detect_dev(pmuic);
#else
if (muic_irq_handler(pmuic, irq) & INT_REQ_DONE)
muic_detect_dev(pmuic);
#endif
}
mutex_unlock(&pmuic->muic_mutex);
return IRQ_HANDLED;
}
static void muic_init_detect(struct work_struct *work)
{
muic_data_t *pmuic =
container_of(work, muic_data_t, init_work.work);
pr_info("%s:%s\n", pmuic->chip_name, __func__);
/* MUIC Interrupt On */
set_int_mask(pmuic, false);
muic_irq_thread(-1, pmuic);
}
static int muic_irq_init(muic_data_t *pmuic)
{
struct i2c_client *i2c = pmuic->i2c;
struct muic_platform_data *pdata = pmuic->pdata;
int ret = 0;
pr_info("%s:%s\n", pmuic->chip_name, __func__);
if (!pdata->irq_gpio) {
pr_warn("%s:%s No interrupt specified\n", pmuic->chip_name,
__func__);
return -ENXIO;
}
i2c->irq = gpio_to_irq(pdata->irq_gpio);
if (i2c->irq) {
ret = request_threaded_irq(i2c->irq, NULL,
muic_irq_thread,
(IRQF_TRIGGER_FALLING | IRQF_ONESHOT),
"muic-irq", pmuic);
if (ret < 0) {
pr_err("%s:%s failed to reqeust IRQ(%d)\n",
pmuic->chip_name, __func__, i2c->irq);
return ret;
}
ret = enable_irq_wake(i2c->irq);
if (ret < 0)
pr_err("%s:%s failed to enable wakeup src\n",
pmuic->chip_name, __func__);
}
return ret;
}
static int muic_probe(struct i2c_client *i2c,
const struct i2c_device_id *id)
{
struct i2c_adapter *adapter = to_i2c_adapter(i2c->dev.parent);
struct muic_platform_data *pdata = &muic_pdata;
muic_data_t *pmuic;
struct regmap_desc *pdesc = NULL;
struct regmap_ops *pops = NULL;
int ret = 0;
pr_info("%s:%s\n", MUIC_DEV_NAME, __func__);
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
pr_err("%s: i2c functionality check error\n", __func__);
ret = -EIO;
goto err_return;
}
pmuic = kzalloc(sizeof(muic_data_t), GFP_KERNEL);
if (!pmuic) {
pr_err("%s: failed to allocate driver data\n", __func__);
ret = -ENOMEM;
goto err_return;
}
i2c->dev.platform_data = pdata;
i2c_set_clientdata(i2c, pmuic);
#if defined(CONFIG_OF)
ret = of_muic_dt(i2c, pdata);
if (ret < 0) {
pr_err("%s:%s Failed to get device of_node \n",
MUIC_DEV_NAME, __func__);
goto err_io;
}
#if defined(CONFIG_MUIC_PINCTRL)
ret = of_muic_pinctrl(i2c);
if (ret < 0) {
pr_err("%s:%s Failed to set pinctrl\n",
MUIC_DEV_NAME, __func__);
goto err_io;
}
#endif
of_update_supported_list(i2c, pdata);
vps_show_table();
#endif /* CONFIG_OF */
if (!pdata) {
pr_err("%s: failed to get i2c platform data\n", __func__);
ret = -ENODEV;
goto err_io;
}
mutex_init(&pmuic->muic_mutex);
pmuic->pdata = pdata;
pmuic->i2c = i2c;
pmuic->is_factory_start = false;
pmuic->is_otg_test = false;
pmuic->attached_dev = ATTACHED_DEV_UNKNOWN_MUIC;
pmuic->is_usb_ready = false;
#ifdef CONFIG_UART_SEL
pmuic->pdata->muic_set_path = java_muic_set_path;
#endif
if (!strcmp(pmuic->chip_name, "max,max77849"))
pmuic->vps_table = VPS_TYPE_TABLE;
else
pmuic->vps_table = VPS_TYPE_SCATTERED;
pr_info("%s: VPS_TYPE=%d\n", __func__, pmuic->vps_table);
if (!pdata->set_gpio_uart_sel) {
if (pmuic->gpio_uart_sel) {
pr_info("%s: gpio_uart_sel registered.\n", __func__);
pdata->set_gpio_uart_sel = muic_set_gpio_uart_sel;
} else
pr_info("%s: gpio_uart_sel is not supported.\n", __func__);
}
if (pmuic->pdata->init_gpio_cb) {
ret = pmuic->pdata->init_gpio_cb();
if (ret) {
pr_err("%s: failed to init gpio(%d)\n", __func__, ret);
goto fail_init_gpio;
}
}
if (pmuic->pdata->init_switch_dev_cb)
pmuic->pdata->init_switch_dev_cb();
if (!(get_switch_sel() & SWITCH_SEL_RUSTPROOF_MASK)) {
pr_info(" Enable rustproof mode\n");
pmuic->is_rustproof = true;
} else {
pr_info(" Disable rustproof mode\n");
pmuic->is_rustproof = false;
}
/* Register chipset register map. */
muic_register_regmap(&pdesc, pmuic);
pdesc->muic = pmuic;
pops = pdesc->regmapops;
pmuic->regmapdesc = pdesc;
/* set switch device's driver_data */
dev_set_drvdata(switch_device, pmuic);
/* create sysfs group */
ret = sysfs_create_group(&switch_device->kobj, &muic_sysfs_group);
if (ret) {
pr_err("%s: failed to create sm5703 muic attribute group\n",
__func__);
goto fail;
}
ret = pops->revision(pdesc);
if (ret) {
pr_err("%s: failed to init muic rev register(%d)\n", __func__,
ret);
goto fail;
}
ret = muic_reg_init(pmuic);
if (ret) {
pr_err("%s: failed to init muic register(%d)\n", __func__, ret);
goto fail;
}
pops->update(pdesc);
pops->show(pdesc);
ret = muic_irq_init(pmuic);
if (ret) {
pr_err("%s: failed to init muic irq(%d)\n", __func__, ret);
goto fail_init_irq;
}
#if defined(CONFIG_MUIC_UNIVERSAL_SM5705_AFC)
muic_init_afc_state(pmuic);
#endif
/* initial cable detection */
INIT_DELAYED_WORK(&pmuic->init_work, muic_init_detect);
schedule_delayed_work(&pmuic->init_work, msecs_to_jiffies(300));
#ifdef DEBUG_MUIC
INIT_DELAYED_WORK(&pmuic->usb_work, muic_show_debug_info);
schedule_delayed_work(&pmuic->usb_work, msecs_to_jiffies(10000));
#endif
return 0;
fail_init_irq:
if (i2c->irq)
free_irq(i2c->irq, pmuic);
fail:
if (pmuic->pdata->cleanup_switch_dev_cb)
pmuic->pdata->cleanup_switch_dev_cb();
sysfs_remove_group(&switch_device->kobj, &muic_sysfs_group);
mutex_unlock(&pmuic->muic_mutex);
mutex_destroy(&pmuic->muic_mutex);
fail_init_gpio:
i2c_set_clientdata(i2c, NULL);
err_io:
kfree(pmuic);
err_return:
return ret;
}
static int muic_remove(struct i2c_client *i2c)
{
muic_data_t *pmuic = i2c_get_clientdata(i2c);
sysfs_remove_group(&switch_device->kobj, &muic_sysfs_group);
if (pmuic) {
pr_info("%s:%s\n", pmuic->chip_name, __func__);
cancel_delayed_work(&pmuic->init_work);
cancel_delayed_work(&pmuic->usb_work);
disable_irq_wake(pmuic->i2c->irq);
free_irq(pmuic->i2c->irq, pmuic);
if (pmuic->pdata->cleanup_switch_dev_cb)
pmuic->pdata->cleanup_switch_dev_cb();
mutex_destroy(&pmuic->muic_mutex);
i2c_set_clientdata(pmuic->i2c, NULL);
kfree(pmuic);
}
return 0;
}
static const struct i2c_device_id muic_i2c_id[] = {
{ MUIC_DEV_NAME, 0 },
{}
};
MODULE_DEVICE_TABLE(i2c, muic_i2c_id);
#if defined(CONFIG_OF)
MODULE_DEVICE_TABLE(of, muic_i2c_dt_ids);
#endif /* CONFIG_OF */
static void muic_shutdown(struct i2c_client *i2c)
{
muic_data_t *pmuic = i2c_get_clientdata(i2c);
int ret;
pr_info("%s:%s\n", pmuic->chip_name, __func__);
if (!pmuic->i2c) {
pr_err("%s:%s no muic i2c client\n", pmuic->chip_name, __func__);
return;
}
pr_info("%s:%s open D+,D-\n", pmuic->chip_name, __func__);
ret = com_to_open_with_vbus(pmuic);
if (ret < 0)
pr_err("%s:%s fail to open mansw1 reg\n", pmuic->chip_name,
__func__);
/* set auto sw mode before shutdown to make sure device goes into */
/* LPM charging when TA or USB is connected during off state */
pr_info("%s:%s muic auto detection enable\n", pmuic->chip_name, __func__);
set_switch_mode(pmuic, SWMODE_AUTO);
if (pmuic->pdata && pmuic->pdata->cleanup_switch_dev_cb)
pmuic->pdata->cleanup_switch_dev_cb();
pr_info("%s:%s -\n", MUIC_DEV_NAME, __func__);
}
#if defined(CONFIG_PM)
static int muic_suspend(struct device *dev)
{
muic_data_t *pmuic = dev_get_drvdata(dev);
struct i2c_client *i2c = pmuic->i2c;
disable_irq_nosync(i2c->irq);
return 0;
}
static int muic_resume(struct device *dev)
{
muic_data_t *pmuic = dev_get_drvdata(dev);
struct i2c_client *i2c = pmuic->i2c;
enable_irq(i2c->irq);
return 0;
}
const struct dev_pm_ops muic_pm = {
.suspend = muic_suspend,
.resume = muic_resume,
};
#endif /* CONFIG_PM */
static struct i2c_driver muic_driver = {
.driver = {
.name = MUIC_DEV_NAME,
#if defined(CONFIG_OF)
.of_match_table = muic_i2c_dt_ids,
#endif /* CONFIG_OF */
#if defined(CONFIG_PM)
.pm = &muic_pm,
#endif /* CONFIG_PM */
},
.probe = muic_probe,
.remove = muic_remove,
.shutdown = muic_shutdown,
.id_table = muic_i2c_id,
};
static int __init muic_init(void)
{
pr_info("%s:%s\n", MUIC_DEV_NAME, __func__);
return i2c_add_driver(&muic_driver);
}
module_init(muic_init);
static void muic_exit(void)
{
pr_info("%s:%s\n", MUIC_DEV_NAME, __func__);
i2c_del_driver(&muic_driver);
}
module_exit(muic_exit);
MODULE_DESCRIPTION("MUIC driver");
MODULE_AUTHOR("<smilesr.ryu@samsung.com>");
MODULE_LICENSE("GPL");