Fixed MTP to work with TWRP

This commit is contained in:
awab228 2018-06-19 23:16:04 +02:00
commit f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions

96
drivers/extcon/Kconfig Normal file
View file

@ -0,0 +1,96 @@
menuconfig EXTCON
tristate "External Connector Class (extcon) support"
help
Say Y here to enable external connector class (extcon) support.
This allows monitoring external connectors by userspace
via sysfs and uevent and supports external connectors with
multiple states; i.e., an extcon that may have multiple
cables attached. For example, an external connector of a device
may be used to connect an HDMI cable and a AC adaptor, and to
host USB ports. Many of 30-pin connectors including PDMI are
also good examples.
if EXTCON
comment "Extcon Device Drivers"
config EXTCON_ADC_JACK
tristate "ADC Jack extcon support"
depends on IIO
help
Say Y here to enable extcon device driver based on ADC values.
config EXTCON_ARIZONA
tristate "Wolfson Arizona EXTCON support"
depends on MFD_ARIZONA && INPUT && SND_SOC
help
Say Y here to enable support for external accessory detection
with Wolfson Arizona devices. These are audio CODECs with
advanced audio accessory detection support.
config EXTCON_GPIO
tristate "GPIO extcon support"
depends on GPIOLIB
help
Say Y here to enable GPIO based extcon support. Note that GPIO
extcon supports single state per extcon instance.
config EXTCON_MAX14577
tristate "MAX14577/77836 EXTCON Support"
depends on MFD_MAX14577
select IRQ_DOMAIN
select REGMAP_I2C
help
If you say yes here you get support for the MUIC device of
Maxim MAX14577/77836. The MAX14577/77836 MUIC is a USB port accessory
detector and switch.
config EXTCON_MAX77693
tristate "MAX77693 EXTCON Support"
depends on MFD_MAX77693 && INPUT
select IRQ_DOMAIN
select REGMAP_I2C
help
If you say yes here you get support for the MUIC device of
Maxim MAX77693 PMIC. The MAX77693 MUIC is a USB port accessory
detector and switch.
config EXTCON_MAX8997
tristate "MAX8997 EXTCON Support"
depends on MFD_MAX8997 && IRQ_DOMAIN
help
If you say yes here you get support for the MUIC device of
Maxim MAX8997 PMIC. The MAX8997 MUIC is a USB port accessory
detector and switch.
config EXTCON_PALMAS
tristate "Palmas USB EXTCON support"
depends on MFD_PALMAS
help
Say Y here to enable support for USB peripheral and USB host
detection by palmas usb.
config EXTCON_RT8973A
tristate "RT8973A EXTCON support"
depends on I2C
select IRQ_DOMAIN
select REGMAP_I2C
select REGMAP_IRQ
help
If you say yes here you get support for the MUIC device of
Richtek RT8973A. The RT8973A is a USB port accessory detector
and switch that is optimized to protect low voltage system
from abnormal high input voltage (up to 28V).
config EXTCON_SM5502
tristate "SM5502 EXTCON support"
depends on I2C
select IRQ_DOMAIN
select REGMAP_I2C
select REGMAP_IRQ
help
If you say yes here you get support for the MUIC device of
Silicon Mitus SM5502. The SM5502 is a USB port accessory
detector and switch.
endif # MULTISTATE_SWITCH

14
drivers/extcon/Makefile Normal file
View file

@ -0,0 +1,14 @@
# Makefile for external connector class (extcon) devices
#
obj-$(CONFIG_EXTCON) += extcon-class.o
obj-$(CONFIG_EXTCON_ADC_JACK) += extcon-adc-jack.o
obj-$(CONFIG_EXTCON_ARIZONA) += extcon-arizona.o
obj-$(CONFIG_EXTCON_GPIO) += extcon-gpio.o
obj-$(CONFIG_EXTCON_MAX14577) += extcon-max14577.o
obj-$(CONFIG_EXTCON_MAX77693) += extcon-max77693.o
obj-$(CONFIG_EXTCON_MAX8997) += extcon-max8997.o
obj-$(CONFIG_EXTCON_PALMAS) += extcon-palmas.o
obj-$(CONFIG_EXTCON_RT8973A) += extcon-rt8973a.o
obj-$(CONFIG_EXTCON_SM5502) += extcon-sm5502.o

View file

@ -0,0 +1,193 @@
/*
* drivers/extcon/extcon-adc-jack.c
*
* Analog Jack extcon driver with ADC-based detection capability.
*
* Copyright (C) 2012 Samsung Electronics
* MyungJoo Ham <myungjoo.ham@samsung.com>
*
* Modified for calling to IIO to get adc by <anish.singh@samsung.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <linux/iio/consumer.h>
#include <linux/extcon/extcon-adc-jack.h>
#include <linux/extcon.h>
/**
* struct adc_jack_data - internal data for adc_jack device driver
* @edev: extcon device.
* @cable_names: list of supported cables.
* @num_cables: size of cable_names.
* @adc_conditions: list of adc value conditions.
* @num_conditions: size of adc_conditions.
* @irq: irq number of attach/detach event (0 if not exist).
* @handling_delay: interrupt handler will schedule extcon event
* handling at handling_delay jiffies.
* @handler: extcon event handler called by interrupt handler.
* @chan: iio channel being queried.
*/
struct adc_jack_data {
struct extcon_dev *edev;
const char **cable_names;
int num_cables;
struct adc_jack_cond *adc_conditions;
int num_conditions;
int irq;
unsigned long handling_delay; /* in jiffies */
struct delayed_work handler;
struct iio_channel *chan;
};
static void adc_jack_handler(struct work_struct *work)
{
struct adc_jack_data *data = container_of(to_delayed_work(work),
struct adc_jack_data,
handler);
u32 state = 0;
int ret, adc_val;
int i;
ret = iio_read_channel_raw(data->chan, &adc_val);
if (ret < 0) {
dev_err(&data->edev->dev, "read channel() error: %d\n", ret);
return;
}
/* Get state from adc value with adc_conditions */
for (i = 0; i < data->num_conditions; i++) {
struct adc_jack_cond *def = &data->adc_conditions[i];
if (!def->state)
break;
if (def->min_adc <= adc_val && def->max_adc >= adc_val) {
state = def->state;
break;
}
}
/* if no def has met, it means state = 0 (no cables attached) */
extcon_set_state(data->edev, state);
}
static irqreturn_t adc_jack_irq_thread(int irq, void *_data)
{
struct adc_jack_data *data = _data;
queue_delayed_work(system_power_efficient_wq,
&data->handler, data->handling_delay);
return IRQ_HANDLED;
}
static int adc_jack_probe(struct platform_device *pdev)
{
struct adc_jack_data *data;
struct adc_jack_pdata *pdata = dev_get_platdata(&pdev->dev);
int i, err = 0;
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
if (!pdata->cable_names) {
dev_err(&pdev->dev, "error: cable_names not defined.\n");
return -EINVAL;
}
data->edev = devm_extcon_dev_allocate(&pdev->dev, pdata->cable_names);
if (IS_ERR(data->edev)) {
dev_err(&pdev->dev, "failed to allocate extcon device\n");
return -ENOMEM;
}
data->edev->name = pdata->name;
/* Check the length of array and set num_cables */
for (i = 0; data->edev->supported_cable[i]; i++)
;
if (i == 0 || i > SUPPORTED_CABLE_MAX) {
dev_err(&pdev->dev, "error: pdata->cable_names size = %d\n",
i - 1);
return -EINVAL;
}
data->num_cables = i;
if (!pdata->adc_conditions ||
!pdata->adc_conditions[0].state) {
dev_err(&pdev->dev, "error: adc_conditions not defined.\n");
return -EINVAL;
}
data->adc_conditions = pdata->adc_conditions;
/* Check the length of array and set num_conditions */
for (i = 0; data->adc_conditions[i].state; i++)
;
data->num_conditions = i;
data->chan = iio_channel_get(&pdev->dev, pdata->consumer_channel);
if (IS_ERR(data->chan))
return PTR_ERR(data->chan);
data->handling_delay = msecs_to_jiffies(pdata->handling_delay_ms);
INIT_DEFERRABLE_WORK(&data->handler, adc_jack_handler);
platform_set_drvdata(pdev, data);
err = devm_extcon_dev_register(&pdev->dev, data->edev);
if (err)
return err;
data->irq = platform_get_irq(pdev, 0);
if (!data->irq) {
dev_err(&pdev->dev, "platform_get_irq failed\n");
return -ENODEV;
}
err = request_any_context_irq(data->irq, adc_jack_irq_thread,
pdata->irq_flags, pdata->name, data);
if (err < 0) {
dev_err(&pdev->dev, "error: irq %d\n", data->irq);
return err;
}
return 0;
}
static int adc_jack_remove(struct platform_device *pdev)
{
struct adc_jack_data *data = platform_get_drvdata(pdev);
free_irq(data->irq, data);
cancel_work_sync(&data->handler.work);
return 0;
}
static struct platform_driver adc_jack_driver = {
.probe = adc_jack_probe,
.remove = adc_jack_remove,
.driver = {
.name = "adc-jack",
.owner = THIS_MODULE,
},
};
module_platform_driver(adc_jack_driver);
MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");
MODULE_DESCRIPTION("ADC Jack extcon driver");
MODULE_LICENSE("GPL v2");

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,193 @@
/*
* drivers/extcon/extcon_gpio.c
*
* Single-state GPIO extcon driver based on extcon class
*
* Copyright (C) 2008 Google, Inc.
* Author: Mike Lockwood <lockwood@android.com>
*
* Modified by MyungJoo Ham <myungjoo.ham@samsung.com> to support extcon
* (originally switch class is supported)
*
* 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/extcon.h>
#include <linux/extcon/extcon-gpio.h>
#include <linux/gpio.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
struct gpio_extcon_data {
struct extcon_dev *edev;
unsigned gpio;
bool gpio_active_low;
const char *state_on;
const char *state_off;
int irq;
struct delayed_work work;
unsigned long debounce_jiffies;
bool check_on_resume;
};
static void gpio_extcon_work(struct work_struct *work)
{
int state;
struct gpio_extcon_data *data =
container_of(to_delayed_work(work), struct gpio_extcon_data,
work);
state = gpio_get_value(data->gpio);
if (data->gpio_active_low)
state = !state;
extcon_set_state(data->edev, state);
}
static irqreturn_t gpio_irq_handler(int irq, void *dev_id)
{
struct gpio_extcon_data *extcon_data = dev_id;
queue_delayed_work(system_power_efficient_wq, &extcon_data->work,
extcon_data->debounce_jiffies);
return IRQ_HANDLED;
}
static ssize_t extcon_gpio_print_state(struct extcon_dev *edev, char *buf)
{
struct device *dev = edev->dev.parent;
struct gpio_extcon_data *extcon_data = dev_get_drvdata(dev);
const char *state;
if (extcon_get_state(edev))
state = extcon_data->state_on;
else
state = extcon_data->state_off;
if (state)
return sprintf(buf, "%s\n", state);
return -EINVAL;
}
static int gpio_extcon_probe(struct platform_device *pdev)
{
struct gpio_extcon_platform_data *pdata = dev_get_platdata(&pdev->dev);
struct gpio_extcon_data *extcon_data;
int ret;
if (!pdata)
return -EBUSY;
if (!pdata->irq_flags) {
dev_err(&pdev->dev, "IRQ flag is not specified.\n");
return -EINVAL;
}
extcon_data = devm_kzalloc(&pdev->dev, sizeof(struct gpio_extcon_data),
GFP_KERNEL);
if (!extcon_data)
return -ENOMEM;
extcon_data->edev = devm_extcon_dev_allocate(&pdev->dev, NULL);
if (IS_ERR(extcon_data->edev)) {
dev_err(&pdev->dev, "failed to allocate extcon device\n");
return -ENOMEM;
}
extcon_data->edev->name = pdata->name;
extcon_data->gpio = pdata->gpio;
extcon_data->gpio_active_low = pdata->gpio_active_low;
extcon_data->state_on = pdata->state_on;
extcon_data->state_off = pdata->state_off;
extcon_data->check_on_resume = pdata->check_on_resume;
if (pdata->state_on && pdata->state_off)
extcon_data->edev->print_state = extcon_gpio_print_state;
ret = devm_gpio_request_one(&pdev->dev, extcon_data->gpio, GPIOF_DIR_IN,
pdev->name);
if (ret < 0)
return ret;
if (pdata->debounce) {
ret = gpio_set_debounce(extcon_data->gpio,
pdata->debounce * 1000);
if (ret < 0)
extcon_data->debounce_jiffies =
msecs_to_jiffies(pdata->debounce);
}
ret = devm_extcon_dev_register(&pdev->dev, extcon_data->edev);
if (ret < 0)
return ret;
INIT_DELAYED_WORK(&extcon_data->work, gpio_extcon_work);
extcon_data->irq = gpio_to_irq(extcon_data->gpio);
if (extcon_data->irq < 0)
return extcon_data->irq;
ret = request_any_context_irq(extcon_data->irq, gpio_irq_handler,
pdata->irq_flags, pdev->name,
extcon_data);
if (ret < 0)
return ret;
platform_set_drvdata(pdev, extcon_data);
/* Perform initial detection */
gpio_extcon_work(&extcon_data->work.work);
return 0;
}
static int gpio_extcon_remove(struct platform_device *pdev)
{
struct gpio_extcon_data *extcon_data = platform_get_drvdata(pdev);
cancel_delayed_work_sync(&extcon_data->work);
free_irq(extcon_data->irq, extcon_data);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int gpio_extcon_resume(struct device *dev)
{
struct gpio_extcon_data *extcon_data;
extcon_data = dev_get_drvdata(dev);
if (extcon_data->check_on_resume)
queue_delayed_work(system_power_efficient_wq,
&extcon_data->work, extcon_data->debounce_jiffies);
return 0;
}
#endif
static SIMPLE_DEV_PM_OPS(gpio_extcon_pm_ops, NULL, gpio_extcon_resume);
static struct platform_driver gpio_extcon_driver = {
.probe = gpio_extcon_probe,
.remove = gpio_extcon_remove,
.driver = {
.name = "extcon-gpio",
.owner = THIS_MODULE,
.pm = &gpio_extcon_pm_ops,
},
};
module_platform_driver(gpio_extcon_driver);
MODULE_AUTHOR("Mike Lockwood <lockwood@android.com>");
MODULE_DESCRIPTION("GPIO extcon driver");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,822 @@
/*
* extcon-max14577.c - MAX14577/77836 extcon driver to support MUIC
*
* Copyright (C) 2013,2014 Samsung Electrnoics
* Chanwoo Choi <cw00.choi@samsung.com>
* Krzysztof Kozlowski <k.kozlowski@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.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/mfd/max14577.h>
#include <linux/mfd/max14577-private.h>
#include <linux/extcon.h>
#define DELAY_MS_DEFAULT 17000 /* unit: millisecond */
enum max14577_muic_adc_debounce_time {
ADC_DEBOUNCE_TIME_5MS = 0,
ADC_DEBOUNCE_TIME_10MS,
ADC_DEBOUNCE_TIME_25MS,
ADC_DEBOUNCE_TIME_38_62MS,
};
enum max14577_muic_status {
MAX14577_MUIC_STATUS1 = 0,
MAX14577_MUIC_STATUS2 = 1,
MAX14577_MUIC_STATUS_END,
};
/**
* struct max14577_muic_irq
* @irq: the index of irq list of MUIC device.
* @name: the name of irq.
* @virq: the virtual irq to use irq domain
*/
struct max14577_muic_irq {
unsigned int irq;
const char *name;
unsigned int virq;
};
static struct max14577_muic_irq max14577_muic_irqs[] = {
{ MAX14577_IRQ_INT1_ADC, "muic-ADC" },
{ MAX14577_IRQ_INT1_ADCLOW, "muic-ADCLOW" },
{ MAX14577_IRQ_INT1_ADCERR, "muic-ADCError" },
{ MAX14577_IRQ_INT2_CHGTYP, "muic-CHGTYP" },
{ MAX14577_IRQ_INT2_CHGDETRUN, "muic-CHGDETRUN" },
{ MAX14577_IRQ_INT2_DCDTMR, "muic-DCDTMR" },
{ MAX14577_IRQ_INT2_DBCHG, "muic-DBCHG" },
{ MAX14577_IRQ_INT2_VBVOLT, "muic-VBVOLT" },
};
static struct max14577_muic_irq max77836_muic_irqs[] = {
{ MAX14577_IRQ_INT1_ADC, "muic-ADC" },
{ MAX14577_IRQ_INT1_ADCLOW, "muic-ADCLOW" },
{ MAX14577_IRQ_INT1_ADCERR, "muic-ADCError" },
{ MAX77836_IRQ_INT1_ADC1K, "muic-ADC1K" },
{ MAX14577_IRQ_INT2_CHGTYP, "muic-CHGTYP" },
{ MAX14577_IRQ_INT2_CHGDETRUN, "muic-CHGDETRUN" },
{ MAX14577_IRQ_INT2_DCDTMR, "muic-DCDTMR" },
{ MAX14577_IRQ_INT2_DBCHG, "muic-DBCHG" },
{ MAX14577_IRQ_INT2_VBVOLT, "muic-VBVOLT" },
{ MAX77836_IRQ_INT2_VIDRM, "muic-VIDRM" },
};
struct max14577_muic_info {
struct device *dev;
struct max14577 *max14577;
struct extcon_dev *edev;
int prev_cable_type;
int prev_chg_type;
u8 status[MAX14577_MUIC_STATUS_END];
struct max14577_muic_irq *muic_irqs;
unsigned int muic_irqs_num;
bool irq_adc;
bool irq_chg;
struct work_struct irq_work;
struct mutex mutex;
/*
* Use delayed workqueue to detect cable state and then
* notify cable state to notifiee/platform through uevent.
* After completing the booting of platform, the extcon provider
* driver should notify cable state to upper layer.
*/
struct delayed_work wq_detcable;
/*
* Default usb/uart path whether UART/USB or AUX_UART/AUX_USB
* h/w path of COMP2/COMN1 on CONTROL1 register.
*/
int path_usb;
int path_uart;
};
enum max14577_muic_cable_group {
MAX14577_CABLE_GROUP_ADC = 0,
MAX14577_CABLE_GROUP_CHG,
};
/* Define supported accessory type */
enum max14577_muic_acc_type {
MAX14577_MUIC_ADC_GROUND = 0x0,
MAX14577_MUIC_ADC_SEND_END_BUTTON,
MAX14577_MUIC_ADC_REMOTE_S1_BUTTON,
MAX14577_MUIC_ADC_REMOTE_S2_BUTTON,
MAX14577_MUIC_ADC_REMOTE_S3_BUTTON,
MAX14577_MUIC_ADC_REMOTE_S4_BUTTON,
MAX14577_MUIC_ADC_REMOTE_S5_BUTTON,
MAX14577_MUIC_ADC_REMOTE_S6_BUTTON,
MAX14577_MUIC_ADC_REMOTE_S7_BUTTON,
MAX14577_MUIC_ADC_REMOTE_S8_BUTTON,
MAX14577_MUIC_ADC_REMOTE_S9_BUTTON,
MAX14577_MUIC_ADC_REMOTE_S10_BUTTON,
MAX14577_MUIC_ADC_REMOTE_S11_BUTTON,
MAX14577_MUIC_ADC_REMOTE_S12_BUTTON,
MAX14577_MUIC_ADC_RESERVED_ACC_1,
MAX14577_MUIC_ADC_RESERVED_ACC_2,
MAX14577_MUIC_ADC_RESERVED_ACC_3,
MAX14577_MUIC_ADC_RESERVED_ACC_4,
MAX14577_MUIC_ADC_RESERVED_ACC_5,
MAX14577_MUIC_ADC_AUDIO_DEVICE_TYPE2,
MAX14577_MUIC_ADC_PHONE_POWERED_DEV,
MAX14577_MUIC_ADC_TTY_CONVERTER,
MAX14577_MUIC_ADC_UART_CABLE,
MAX14577_MUIC_ADC_CEA936A_TYPE1_CHG,
MAX14577_MUIC_ADC_FACTORY_MODE_USB_OFF,
MAX14577_MUIC_ADC_FACTORY_MODE_USB_ON,
MAX14577_MUIC_ADC_AV_CABLE_NOLOAD,
MAX14577_MUIC_ADC_CEA936A_TYPE2_CHG,
MAX14577_MUIC_ADC_FACTORY_MODE_UART_OFF,
MAX14577_MUIC_ADC_FACTORY_MODE_UART_ON,
MAX14577_MUIC_ADC_AUDIO_DEVICE_TYPE1, /* with Remote and Simple Ctrl */
MAX14577_MUIC_ADC_OPEN,
};
/* max14577 MUIC device support below list of accessories(external connector) */
enum {
EXTCON_CABLE_USB = 0,
EXTCON_CABLE_TA,
EXTCON_CABLE_FAST_CHARGER,
EXTCON_CABLE_SLOW_CHARGER,
EXTCON_CABLE_CHARGE_DOWNSTREAM,
EXTCON_CABLE_JIG_USB_ON,
EXTCON_CABLE_JIG_USB_OFF,
EXTCON_CABLE_JIG_UART_OFF,
EXTCON_CABLE_JIG_UART_ON,
_EXTCON_CABLE_NUM,
};
static const char *max14577_extcon_cable[] = {
[EXTCON_CABLE_USB] = "USB",
[EXTCON_CABLE_TA] = "TA",
[EXTCON_CABLE_FAST_CHARGER] = "Fast-charger",
[EXTCON_CABLE_SLOW_CHARGER] = "Slow-charger",
[EXTCON_CABLE_CHARGE_DOWNSTREAM] = "Charge-downstream",
[EXTCON_CABLE_JIG_USB_ON] = "JIG-USB-ON",
[EXTCON_CABLE_JIG_USB_OFF] = "JIG-USB-OFF",
[EXTCON_CABLE_JIG_UART_OFF] = "JIG-UART-OFF",
[EXTCON_CABLE_JIG_UART_ON] = "JIG-UART-ON",
NULL,
};
/*
* max14577_muic_set_debounce_time - Set the debounce time of ADC
* @info: the instance including private data of max14577 MUIC
* @time: the debounce time of ADC
*/
static int max14577_muic_set_debounce_time(struct max14577_muic_info *info,
enum max14577_muic_adc_debounce_time time)
{
u8 ret;
switch (time) {
case ADC_DEBOUNCE_TIME_5MS:
case ADC_DEBOUNCE_TIME_10MS:
case ADC_DEBOUNCE_TIME_25MS:
case ADC_DEBOUNCE_TIME_38_62MS:
ret = max14577_update_reg(info->max14577->regmap,
MAX14577_MUIC_REG_CONTROL3,
CTRL3_ADCDBSET_MASK,
time << CTRL3_ADCDBSET_SHIFT);
if (ret) {
dev_err(info->dev, "failed to set ADC debounce time\n");
return ret;
}
break;
default:
dev_err(info->dev, "invalid ADC debounce time\n");
return -EINVAL;
}
return 0;
};
/*
* max14577_muic_set_path - Set hardware line according to attached cable
* @info: the instance including private data of max14577 MUIC
* @value: the path according to attached cable
* @attached: the state of cable (true:attached, false:detached)
*
* The max14577 MUIC device share outside H/W line among a varity of cables
* so, this function set internal path of H/W line according to the type of
* attached cable.
*/
static int max14577_muic_set_path(struct max14577_muic_info *info,
u8 val, bool attached)
{
int ret = 0;
u8 ctrl1, ctrl2 = 0;
/* Set open state to path before changing hw path */
ret = max14577_update_reg(info->max14577->regmap,
MAX14577_MUIC_REG_CONTROL1,
CLEAR_IDBEN_MICEN_MASK, CTRL1_SW_OPEN);
if (ret < 0) {
dev_err(info->dev, "failed to update MUIC register\n");
return ret;
}
if (attached)
ctrl1 = val;
else
ctrl1 = CTRL1_SW_OPEN;
ret = max14577_update_reg(info->max14577->regmap,
MAX14577_MUIC_REG_CONTROL1,
CLEAR_IDBEN_MICEN_MASK, ctrl1);
if (ret < 0) {
dev_err(info->dev, "failed to update MUIC register\n");
return ret;
}
if (attached)
ctrl2 |= CTRL2_CPEN_MASK; /* LowPwr=0, CPEn=1 */
else
ctrl2 |= CTRL2_LOWPWR_MASK; /* LowPwr=1, CPEn=0 */
ret = max14577_update_reg(info->max14577->regmap,
MAX14577_REG_CONTROL2,
CTRL2_LOWPWR_MASK | CTRL2_CPEN_MASK, ctrl2);
if (ret < 0) {
dev_err(info->dev, "failed to update MUIC register\n");
return ret;
}
dev_dbg(info->dev,
"CONTROL1 : 0x%02x, CONTROL2 : 0x%02x, state : %s\n",
ctrl1, ctrl2, attached ? "attached" : "detached");
return 0;
}
/*
* max14577_muic_get_cable_type - Return cable type and check cable state
* @info: the instance including private data of max14577 MUIC
* @group: the path according to attached cable
* @attached: store cable state and return
*
* This function check the cable state either attached or detached,
* and then divide precise type of cable according to cable group.
* - max14577_CABLE_GROUP_ADC
* - max14577_CABLE_GROUP_CHG
*/
static int max14577_muic_get_cable_type(struct max14577_muic_info *info,
enum max14577_muic_cable_group group, bool *attached)
{
int cable_type = 0;
int adc;
int chg_type;
switch (group) {
case MAX14577_CABLE_GROUP_ADC:
/*
* Read ADC value to check cable type and decide cable state
* according to cable type
*/
adc = info->status[MAX14577_MUIC_STATUS1] & STATUS1_ADC_MASK;
adc >>= STATUS1_ADC_SHIFT;
/*
* Check current cable state/cable type and store cable type
* (info->prev_cable_type) for handling cable when cable is
* detached.
*/
if (adc == MAX14577_MUIC_ADC_OPEN) {
*attached = false;
cable_type = info->prev_cable_type;
info->prev_cable_type = MAX14577_MUIC_ADC_OPEN;
} else {
*attached = true;
cable_type = info->prev_cable_type = adc;
}
break;
case MAX14577_CABLE_GROUP_CHG:
/*
* Read charger type to check cable type and decide cable state
* according to type of charger cable.
*/
chg_type = info->status[MAX14577_MUIC_STATUS2] &
STATUS2_CHGTYP_MASK;
chg_type >>= STATUS2_CHGTYP_SHIFT;
if (chg_type == MAX14577_CHARGER_TYPE_NONE) {
*attached = false;
cable_type = info->prev_chg_type;
info->prev_chg_type = MAX14577_CHARGER_TYPE_NONE;
} else {
*attached = true;
/*
* Check current cable state/cable type and store cable
* type(info->prev_chg_type) for handling cable when
* charger cable is detached.
*/
cable_type = info->prev_chg_type = chg_type;
}
break;
default:
dev_err(info->dev, "Unknown cable group (%d)\n", group);
cable_type = -EINVAL;
break;
}
return cable_type;
}
static int max14577_muic_jig_handler(struct max14577_muic_info *info,
int cable_type, bool attached)
{
char cable_name[32];
int ret = 0;
u8 path = CTRL1_SW_OPEN;
dev_dbg(info->dev,
"external connector is %s (adc:0x%02x)\n",
attached ? "attached" : "detached", cable_type);
switch (cable_type) {
case MAX14577_MUIC_ADC_FACTORY_MODE_USB_OFF: /* ADC_JIG_USB_OFF */
/* PATH:AP_USB */
strcpy(cable_name, "JIG-USB-OFF");
path = CTRL1_SW_USB;
break;
case MAX14577_MUIC_ADC_FACTORY_MODE_USB_ON: /* ADC_JIG_USB_ON */
/* PATH:AP_USB */
strcpy(cable_name, "JIG-USB-ON");
path = CTRL1_SW_USB;
break;
case MAX14577_MUIC_ADC_FACTORY_MODE_UART_OFF: /* ADC_JIG_UART_OFF */
/* PATH:AP_UART */
strcpy(cable_name, "JIG-UART-OFF");
path = CTRL1_SW_UART;
break;
default:
dev_err(info->dev, "failed to detect %s jig cable\n",
attached ? "attached" : "detached");
return -EINVAL;
}
ret = max14577_muic_set_path(info, path, attached);
if (ret < 0)
return ret;
extcon_set_cable_state(info->edev, cable_name, attached);
return 0;
}
static int max14577_muic_adc_handler(struct max14577_muic_info *info)
{
int cable_type;
bool attached;
int ret = 0;
/* Check accessory state which is either detached or attached */
cable_type = max14577_muic_get_cable_type(info,
MAX14577_CABLE_GROUP_ADC, &attached);
dev_dbg(info->dev,
"external connector is %s (adc:0x%02x, prev_adc:0x%x)\n",
attached ? "attached" : "detached", cable_type,
info->prev_cable_type);
switch (cable_type) {
case MAX14577_MUIC_ADC_FACTORY_MODE_USB_OFF:
case MAX14577_MUIC_ADC_FACTORY_MODE_USB_ON:
case MAX14577_MUIC_ADC_FACTORY_MODE_UART_OFF:
/* JIG */
ret = max14577_muic_jig_handler(info, cable_type, attached);
if (ret < 0)
return ret;
break;
case MAX14577_MUIC_ADC_GROUND:
case MAX14577_MUIC_ADC_SEND_END_BUTTON:
case MAX14577_MUIC_ADC_REMOTE_S1_BUTTON:
case MAX14577_MUIC_ADC_REMOTE_S2_BUTTON:
case MAX14577_MUIC_ADC_REMOTE_S3_BUTTON:
case MAX14577_MUIC_ADC_REMOTE_S4_BUTTON:
case MAX14577_MUIC_ADC_REMOTE_S5_BUTTON:
case MAX14577_MUIC_ADC_REMOTE_S6_BUTTON:
case MAX14577_MUIC_ADC_REMOTE_S7_BUTTON:
case MAX14577_MUIC_ADC_REMOTE_S8_BUTTON:
case MAX14577_MUIC_ADC_REMOTE_S9_BUTTON:
case MAX14577_MUIC_ADC_REMOTE_S10_BUTTON:
case MAX14577_MUIC_ADC_REMOTE_S11_BUTTON:
case MAX14577_MUIC_ADC_REMOTE_S12_BUTTON:
case MAX14577_MUIC_ADC_RESERVED_ACC_1:
case MAX14577_MUIC_ADC_RESERVED_ACC_2:
case MAX14577_MUIC_ADC_RESERVED_ACC_3:
case MAX14577_MUIC_ADC_RESERVED_ACC_4:
case MAX14577_MUIC_ADC_RESERVED_ACC_5:
case MAX14577_MUIC_ADC_AUDIO_DEVICE_TYPE2:
case MAX14577_MUIC_ADC_PHONE_POWERED_DEV:
case MAX14577_MUIC_ADC_TTY_CONVERTER:
case MAX14577_MUIC_ADC_UART_CABLE:
case MAX14577_MUIC_ADC_CEA936A_TYPE1_CHG:
case MAX14577_MUIC_ADC_AV_CABLE_NOLOAD:
case MAX14577_MUIC_ADC_CEA936A_TYPE2_CHG:
case MAX14577_MUIC_ADC_FACTORY_MODE_UART_ON:
case MAX14577_MUIC_ADC_AUDIO_DEVICE_TYPE1:
/*
* This accessory isn't used in general case if it is specially
* needed to detect additional accessory, should implement
* proper operation when this accessory is attached/detached.
*/
dev_info(info->dev,
"accessory is %s but it isn't used (adc:0x%x)\n",
attached ? "attached" : "detached", cable_type);
return -EAGAIN;
default:
dev_err(info->dev,
"failed to detect %s accessory (adc:0x%x)\n",
attached ? "attached" : "detached", cable_type);
return -EINVAL;
}
return 0;
}
static int max14577_muic_chg_handler(struct max14577_muic_info *info)
{
int chg_type;
bool attached;
int ret = 0;
chg_type = max14577_muic_get_cable_type(info,
MAX14577_CABLE_GROUP_CHG, &attached);
dev_dbg(info->dev,
"external connector is %s(chg_type:0x%x, prev_chg_type:0x%x)\n",
attached ? "attached" : "detached",
chg_type, info->prev_chg_type);
switch (chg_type) {
case MAX14577_CHARGER_TYPE_USB:
/* PATH:AP_USB */
ret = max14577_muic_set_path(info, info->path_usb, attached);
if (ret < 0)
return ret;
extcon_set_cable_state(info->edev, "USB", attached);
break;
case MAX14577_CHARGER_TYPE_DEDICATED_CHG:
extcon_set_cable_state(info->edev, "TA", attached);
break;
case MAX14577_CHARGER_TYPE_DOWNSTREAM_PORT:
extcon_set_cable_state(info->edev,
"Charge-downstream", attached);
break;
case MAX14577_CHARGER_TYPE_SPECIAL_500MA:
extcon_set_cable_state(info->edev, "Slow-charger", attached);
break;
case MAX14577_CHARGER_TYPE_SPECIAL_1A:
extcon_set_cable_state(info->edev, "Fast-charger", attached);
break;
case MAX14577_CHARGER_TYPE_NONE:
case MAX14577_CHARGER_TYPE_DEAD_BATTERY:
break;
default:
dev_err(info->dev,
"failed to detect %s accessory (chg_type:0x%x)\n",
attached ? "attached" : "detached", chg_type);
return -EINVAL;
}
return 0;
}
static void max14577_muic_irq_work(struct work_struct *work)
{
struct max14577_muic_info *info = container_of(work,
struct max14577_muic_info, irq_work);
int ret = 0;
if (!info->edev)
return;
mutex_lock(&info->mutex);
ret = max14577_bulk_read(info->max14577->regmap,
MAX14577_MUIC_REG_STATUS1, info->status, 2);
if (ret) {
dev_err(info->dev, "failed to read MUIC register\n");
mutex_unlock(&info->mutex);
return;
}
if (info->irq_adc) {
ret = max14577_muic_adc_handler(info);
info->irq_adc = false;
}
if (info->irq_chg) {
ret = max14577_muic_chg_handler(info);
info->irq_chg = false;
}
if (ret < 0)
dev_err(info->dev, "failed to handle MUIC interrupt\n");
mutex_unlock(&info->mutex);
return;
}
/*
* Sets irq_adc or irq_chg in max14577_muic_info and returns 1.
* Returns 0 if irq_type does not match registered IRQ for this device type.
*/
static int max14577_parse_irq(struct max14577_muic_info *info, int irq_type)
{
switch (irq_type) {
case MAX14577_IRQ_INT1_ADC:
case MAX14577_IRQ_INT1_ADCLOW:
case MAX14577_IRQ_INT1_ADCERR:
/* Handle all of accessory except for
type of charger accessory */
info->irq_adc = true;
return 1;
case MAX14577_IRQ_INT2_CHGTYP:
case MAX14577_IRQ_INT2_CHGDETRUN:
case MAX14577_IRQ_INT2_DCDTMR:
case MAX14577_IRQ_INT2_DBCHG:
case MAX14577_IRQ_INT2_VBVOLT:
/* Handle charger accessory */
info->irq_chg = true;
return 1;
default:
return 0;
}
}
/*
* Sets irq_adc or irq_chg in max14577_muic_info and returns 1.
* Returns 0 if irq_type does not match registered IRQ for this device type.
*/
static int max77836_parse_irq(struct max14577_muic_info *info, int irq_type)
{
/* First check common max14577 interrupts */
if (max14577_parse_irq(info, irq_type))
return 1;
switch (irq_type) {
case MAX77836_IRQ_INT1_ADC1K:
info->irq_adc = true;
return 1;
case MAX77836_IRQ_INT2_VIDRM:
/* Handle charger accessory */
info->irq_chg = true;
return 1;
default:
return 0;
}
}
static irqreturn_t max14577_muic_irq_handler(int irq, void *data)
{
struct max14577_muic_info *info = data;
int i, irq_type = -1;
bool irq_parsed;
/*
* We may be called multiple times for different nested IRQ-s.
* Including changes in INT1_ADC and INT2_CGHTYP at once.
* However we only need to know whether it was ADC, charger
* or both interrupts so decode IRQ and turn on proper flags.
*/
for (i = 0; i < info->muic_irqs_num; i++)
if (irq == info->muic_irqs[i].virq)
irq_type = info->muic_irqs[i].irq;
switch (info->max14577->dev_type) {
case MAXIM_DEVICE_TYPE_MAX77836:
irq_parsed = max77836_parse_irq(info, irq_type);
break;
case MAXIM_DEVICE_TYPE_MAX14577:
default:
irq_parsed = max14577_parse_irq(info, irq_type);
break;
}
if (!irq_parsed) {
dev_err(info->dev, "muic interrupt: irq %d occurred, skipped\n",
irq_type);
return IRQ_HANDLED;
}
schedule_work(&info->irq_work);
return IRQ_HANDLED;
}
static int max14577_muic_detect_accessory(struct max14577_muic_info *info)
{
int ret = 0;
int adc;
int chg_type;
bool attached;
mutex_lock(&info->mutex);
/* Read STATUSx register to detect accessory */
ret = max14577_bulk_read(info->max14577->regmap,
MAX14577_MUIC_REG_STATUS1, info->status, 2);
if (ret) {
dev_err(info->dev, "failed to read MUIC register\n");
mutex_unlock(&info->mutex);
return ret;
}
adc = max14577_muic_get_cable_type(info, MAX14577_CABLE_GROUP_ADC,
&attached);
if (attached && adc != MAX14577_MUIC_ADC_OPEN) {
ret = max14577_muic_adc_handler(info);
if (ret < 0) {
dev_err(info->dev, "Cannot detect accessory\n");
mutex_unlock(&info->mutex);
return ret;
}
}
chg_type = max14577_muic_get_cable_type(info, MAX14577_CABLE_GROUP_CHG,
&attached);
if (attached && chg_type != MAX14577_CHARGER_TYPE_NONE) {
ret = max14577_muic_chg_handler(info);
if (ret < 0) {
dev_err(info->dev, "Cannot detect charger accessory\n");
mutex_unlock(&info->mutex);
return ret;
}
}
mutex_unlock(&info->mutex);
return 0;
}
static void max14577_muic_detect_cable_wq(struct work_struct *work)
{
struct max14577_muic_info *info = container_of(to_delayed_work(work),
struct max14577_muic_info, wq_detcable);
max14577_muic_detect_accessory(info);
}
static int max14577_muic_probe(struct platform_device *pdev)
{
struct max14577 *max14577 = dev_get_drvdata(pdev->dev.parent);
struct max14577_muic_info *info;
int delay_jiffies;
int ret;
int i;
u8 id;
info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
if (!info)
return -ENOMEM;
info->dev = &pdev->dev;
info->max14577 = max14577;
platform_set_drvdata(pdev, info);
mutex_init(&info->mutex);
INIT_WORK(&info->irq_work, max14577_muic_irq_work);
switch (max14577->dev_type) {
case MAXIM_DEVICE_TYPE_MAX77836:
info->muic_irqs = max77836_muic_irqs;
info->muic_irqs_num = ARRAY_SIZE(max77836_muic_irqs);
break;
case MAXIM_DEVICE_TYPE_MAX14577:
default:
info->muic_irqs = max14577_muic_irqs;
info->muic_irqs_num = ARRAY_SIZE(max14577_muic_irqs);
}
/* Support irq domain for max14577 MUIC device */
for (i = 0; i < info->muic_irqs_num; i++) {
struct max14577_muic_irq *muic_irq = &info->muic_irqs[i];
unsigned int virq = 0;
virq = regmap_irq_get_virq(max14577->irq_data, muic_irq->irq);
if (virq <= 0)
return -EINVAL;
muic_irq->virq = virq;
ret = devm_request_threaded_irq(&pdev->dev, virq, NULL,
max14577_muic_irq_handler,
IRQF_NO_SUSPEND,
muic_irq->name, info);
if (ret) {
dev_err(&pdev->dev,
"failed: irq request (IRQ: %d,"
" error :%d)\n",
muic_irq->irq, ret);
return ret;
}
}
/* Initialize extcon device */
info->edev = devm_extcon_dev_allocate(&pdev->dev,
max14577_extcon_cable);
if (IS_ERR(info->edev)) {
dev_err(&pdev->dev, "failed to allocate memory for extcon\n");
return -ENOMEM;
}
info->edev->name = dev_name(&pdev->dev);
ret = devm_extcon_dev_register(&pdev->dev, info->edev);
if (ret) {
dev_err(&pdev->dev, "failed to register extcon device\n");
return ret;
}
/* Default h/w line path */
info->path_usb = CTRL1_SW_USB;
info->path_uart = CTRL1_SW_UART;
delay_jiffies = msecs_to_jiffies(DELAY_MS_DEFAULT);
/* Set initial path for UART */
max14577_muic_set_path(info, info->path_uart, true);
/* Check revision number of MUIC device*/
ret = max14577_read_reg(info->max14577->regmap,
MAX14577_REG_DEVICEID, &id);
if (ret < 0) {
dev_err(&pdev->dev, "failed to read revision number\n");
return ret;
}
dev_info(info->dev, "device ID : 0x%x\n", id);
/* Set ADC debounce time */
max14577_muic_set_debounce_time(info, ADC_DEBOUNCE_TIME_25MS);
/*
* Detect accessory after completing the initialization of platform
*
* - Use delayed workqueue to detect cable state and then
* notify cable state to notifiee/platform through uevent.
* After completing the booting of platform, the extcon provider
* driver should notify cable state to upper layer.
*/
INIT_DELAYED_WORK(&info->wq_detcable, max14577_muic_detect_cable_wq);
queue_delayed_work(system_power_efficient_wq, &info->wq_detcable,
delay_jiffies);
return ret;
}
static int max14577_muic_remove(struct platform_device *pdev)
{
struct max14577_muic_info *info = platform_get_drvdata(pdev);
cancel_work_sync(&info->irq_work);
return 0;
}
static const struct platform_device_id max14577_muic_id[] = {
{ "max14577-muic", MAXIM_DEVICE_TYPE_MAX14577, },
{ "max77836-muic", MAXIM_DEVICE_TYPE_MAX77836, },
{ }
};
MODULE_DEVICE_TABLE(platform, max14577_muic_id);
static struct platform_driver max14577_muic_driver = {
.driver = {
.name = "max14577-muic",
.owner = THIS_MODULE,
},
.probe = max14577_muic_probe,
.remove = max14577_muic_remove,
.id_table = max14577_muic_id,
};
module_platform_driver(max14577_muic_driver);
MODULE_DESCRIPTION("Maxim 14577/77836 Extcon driver");
MODULE_AUTHOR("Chanwoo Choi <cw00.choi@samsung.com>, Krzysztof Kozlowski <k.kozlowski@samsung.com>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:extcon-max14577");

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,805 @@
/*
* extcon-max8997.c - MAX8997 extcon driver to support MAX8997 MUIC
*
* Copyright (C) 2012 Samsung Electronics
* Donggeun Kim <dg77.kim@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.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/kobject.h>
#include <linux/mfd/max8997.h>
#include <linux/mfd/max8997-private.h>
#include <linux/extcon.h>
#include <linux/irqdomain.h>
#define DEV_NAME "max8997-muic"
#define DELAY_MS_DEFAULT 20000 /* unit: millisecond */
enum max8997_muic_adc_debounce_time {
ADC_DEBOUNCE_TIME_0_5MS = 0, /* 0.5ms */
ADC_DEBOUNCE_TIME_10MS, /* 10ms */
ADC_DEBOUNCE_TIME_25MS, /* 25ms */
ADC_DEBOUNCE_TIME_38_62MS, /* 38.62ms */
};
struct max8997_muic_irq {
unsigned int irq;
const char *name;
unsigned int virq;
};
static struct max8997_muic_irq muic_irqs[] = {
{ MAX8997_MUICIRQ_ADCError, "muic-ADCERROR" },
{ MAX8997_MUICIRQ_ADCLow, "muic-ADCLOW" },
{ MAX8997_MUICIRQ_ADC, "muic-ADC" },
{ MAX8997_MUICIRQ_VBVolt, "muic-VBVOLT" },
{ MAX8997_MUICIRQ_DBChg, "muic-DBCHG" },
{ MAX8997_MUICIRQ_DCDTmr, "muic-DCDTMR" },
{ MAX8997_MUICIRQ_ChgDetRun, "muic-CHGDETRUN" },
{ MAX8997_MUICIRQ_ChgTyp, "muic-CHGTYP" },
{ MAX8997_MUICIRQ_OVP, "muic-OVP" },
};
/* Define supported cable type */
enum max8997_muic_acc_type {
MAX8997_MUIC_ADC_GROUND = 0x0,
MAX8997_MUIC_ADC_MHL, /* MHL*/
MAX8997_MUIC_ADC_REMOTE_S1_BUTTON,
MAX8997_MUIC_ADC_REMOTE_S2_BUTTON,
MAX8997_MUIC_ADC_REMOTE_S3_BUTTON,
MAX8997_MUIC_ADC_REMOTE_S4_BUTTON,
MAX8997_MUIC_ADC_REMOTE_S5_BUTTON,
MAX8997_MUIC_ADC_REMOTE_S6_BUTTON,
MAX8997_MUIC_ADC_REMOTE_S7_BUTTON,
MAX8997_MUIC_ADC_REMOTE_S8_BUTTON,
MAX8997_MUIC_ADC_REMOTE_S9_BUTTON,
MAX8997_MUIC_ADC_REMOTE_S10_BUTTON,
MAX8997_MUIC_ADC_REMOTE_S11_BUTTON,
MAX8997_MUIC_ADC_REMOTE_S12_BUTTON,
MAX8997_MUIC_ADC_RESERVED_ACC_1,
MAX8997_MUIC_ADC_RESERVED_ACC_2,
MAX8997_MUIC_ADC_RESERVED_ACC_3,
MAX8997_MUIC_ADC_RESERVED_ACC_4,
MAX8997_MUIC_ADC_RESERVED_ACC_5,
MAX8997_MUIC_ADC_CEA936_AUDIO,
MAX8997_MUIC_ADC_PHONE_POWERED_DEV,
MAX8997_MUIC_ADC_TTY_CONVERTER,
MAX8997_MUIC_ADC_UART_CABLE,
MAX8997_MUIC_ADC_CEA936A_TYPE1_CHG,
MAX8997_MUIC_ADC_FACTORY_MODE_USB_OFF, /* JIG-USB-OFF */
MAX8997_MUIC_ADC_FACTORY_MODE_USB_ON, /* JIG-USB-ON */
MAX8997_MUIC_ADC_AV_CABLE_NOLOAD, /* DESKDOCK */
MAX8997_MUIC_ADC_CEA936A_TYPE2_CHG,
MAX8997_MUIC_ADC_FACTORY_MODE_UART_OFF, /* JIG-UART */
MAX8997_MUIC_ADC_FACTORY_MODE_UART_ON, /* CARDOCK */
MAX8997_MUIC_ADC_AUDIO_MODE_REMOTE,
MAX8997_MUIC_ADC_OPEN, /* OPEN */
};
enum max8997_muic_cable_group {
MAX8997_CABLE_GROUP_ADC = 0,
MAX8997_CABLE_GROUP_ADC_GND,
MAX8997_CABLE_GROUP_CHG,
MAX8997_CABLE_GROUP_VBVOLT,
};
enum max8997_muic_usb_type {
MAX8997_USB_HOST,
MAX8997_USB_DEVICE,
};
enum max8997_muic_charger_type {
MAX8997_CHARGER_TYPE_NONE = 0,
MAX8997_CHARGER_TYPE_USB,
MAX8997_CHARGER_TYPE_DOWNSTREAM_PORT,
MAX8997_CHARGER_TYPE_DEDICATED_CHG,
MAX8997_CHARGER_TYPE_500MA,
MAX8997_CHARGER_TYPE_1A,
MAX8997_CHARGER_TYPE_DEAD_BATTERY = 7,
};
struct max8997_muic_info {
struct device *dev;
struct i2c_client *muic;
struct extcon_dev *edev;
int prev_cable_type;
int prev_chg_type;
u8 status[2];
int irq;
struct work_struct irq_work;
struct mutex mutex;
struct max8997_muic_platform_data *muic_pdata;
enum max8997_muic_charger_type pre_charger_type;
/*
* Use delayed workqueue to detect cable state and then
* notify cable state to notifiee/platform through uevent.
* After completing the booting of platform, the extcon provider
* driver should notify cable state to upper layer.
*/
struct delayed_work wq_detcable;
/*
* Default usb/uart path whether UART/USB or AUX_UART/AUX_USB
* h/w path of COMP2/COMN1 on CONTROL1 register.
*/
int path_usb;
int path_uart;
};
enum {
EXTCON_CABLE_USB = 0,
EXTCON_CABLE_USB_HOST,
EXTCON_CABLE_TA,
EXTCON_CABLE_FAST_CHARGER,
EXTCON_CABLE_SLOW_CHARGER,
EXTCON_CABLE_CHARGE_DOWNSTREAM,
EXTCON_CABLE_MHL,
EXTCON_CABLE_DOCK_DESK,
EXTCON_CABLE_DOCK_CARD,
EXTCON_CABLE_JIG,
_EXTCON_CABLE_NUM,
};
static const char *max8997_extcon_cable[] = {
[EXTCON_CABLE_USB] = "USB",
[EXTCON_CABLE_USB_HOST] = "USB-Host",
[EXTCON_CABLE_TA] = "TA",
[EXTCON_CABLE_FAST_CHARGER] = "Fast-charger",
[EXTCON_CABLE_SLOW_CHARGER] = "Slow-charger",
[EXTCON_CABLE_CHARGE_DOWNSTREAM] = "Charge-downstream",
[EXTCON_CABLE_MHL] = "MHL",
[EXTCON_CABLE_DOCK_DESK] = "Dock-Desk",
[EXTCON_CABLE_DOCK_CARD] = "Dock-Card",
[EXTCON_CABLE_JIG] = "JIG",
NULL,
};
/*
* max8997_muic_set_debounce_time - Set the debounce time of ADC
* @info: the instance including private data of max8997 MUIC
* @time: the debounce time of ADC
*/
static int max8997_muic_set_debounce_time(struct max8997_muic_info *info,
enum max8997_muic_adc_debounce_time time)
{
int ret;
switch (time) {
case ADC_DEBOUNCE_TIME_0_5MS:
case ADC_DEBOUNCE_TIME_10MS:
case ADC_DEBOUNCE_TIME_25MS:
case ADC_DEBOUNCE_TIME_38_62MS:
ret = max8997_update_reg(info->muic,
MAX8997_MUIC_REG_CONTROL3,
time << CONTROL3_ADCDBSET_SHIFT,
CONTROL3_ADCDBSET_MASK);
if (ret) {
dev_err(info->dev, "failed to set ADC debounce time\n");
return ret;
}
break;
default:
dev_err(info->dev, "invalid ADC debounce time\n");
return -EINVAL;
}
return 0;
};
/*
* max8997_muic_set_path - Set hardware line according to attached cable
* @info: the instance including private data of max8997 MUIC
* @value: the path according to attached cable
* @attached: the state of cable (true:attached, false:detached)
*
* The max8997 MUIC device share outside H/W line among a varity of cables,
* so this function set internal path of H/W line according to the type of
* attached cable.
*/
static int max8997_muic_set_path(struct max8997_muic_info *info,
u8 val, bool attached)
{
int ret = 0;
u8 ctrl1, ctrl2 = 0;
if (attached)
ctrl1 = val;
else
ctrl1 = CONTROL1_SW_OPEN;
ret = max8997_update_reg(info->muic,
MAX8997_MUIC_REG_CONTROL1, ctrl1, COMP_SW_MASK);
if (ret < 0) {
dev_err(info->dev, "failed to update MUIC register\n");
return ret;
}
if (attached)
ctrl2 |= CONTROL2_CPEN_MASK; /* LowPwr=0, CPEn=1 */
else
ctrl2 |= CONTROL2_LOWPWR_MASK; /* LowPwr=1, CPEn=0 */
ret = max8997_update_reg(info->muic,
MAX8997_MUIC_REG_CONTROL2, ctrl2,
CONTROL2_LOWPWR_MASK | CONTROL2_CPEN_MASK);
if (ret < 0) {
dev_err(info->dev, "failed to update MUIC register\n");
return ret;
}
dev_info(info->dev,
"CONTROL1 : 0x%02x, CONTROL2 : 0x%02x, state : %s\n",
ctrl1, ctrl2, attached ? "attached" : "detached");
return 0;
}
/*
* max8997_muic_get_cable_type - Return cable type and check cable state
* @info: the instance including private data of max8997 MUIC
* @group: the path according to attached cable
* @attached: store cable state and return
*
* This function check the cable state either attached or detached,
* and then divide precise type of cable according to cable group.
* - MAX8997_CABLE_GROUP_ADC
* - MAX8997_CABLE_GROUP_CHG
*/
static int max8997_muic_get_cable_type(struct max8997_muic_info *info,
enum max8997_muic_cable_group group, bool *attached)
{
int cable_type = 0;
int adc;
int chg_type;
switch (group) {
case MAX8997_CABLE_GROUP_ADC:
/*
* Read ADC value to check cable type and decide cable state
* according to cable type
*/
adc = info->status[0] & STATUS1_ADC_MASK;
adc >>= STATUS1_ADC_SHIFT;
/*
* Check current cable state/cable type and store cable type
* (info->prev_cable_type) for handling cable when cable is
* detached.
*/
if (adc == MAX8997_MUIC_ADC_OPEN) {
*attached = false;
cable_type = info->prev_cable_type;
info->prev_cable_type = MAX8997_MUIC_ADC_OPEN;
} else {
*attached = true;
cable_type = info->prev_cable_type = adc;
}
break;
case MAX8997_CABLE_GROUP_CHG:
/*
* Read charger type to check cable type and decide cable state
* according to type of charger cable.
*/
chg_type = info->status[1] & STATUS2_CHGTYP_MASK;
chg_type >>= STATUS2_CHGTYP_SHIFT;
if (chg_type == MAX8997_CHARGER_TYPE_NONE) {
*attached = false;
cable_type = info->prev_chg_type;
info->prev_chg_type = MAX8997_CHARGER_TYPE_NONE;
} else {
*attached = true;
/*
* Check current cable state/cable type and store cable
* type(info->prev_chg_type) for handling cable when
* charger cable is detached.
*/
cable_type = info->prev_chg_type = chg_type;
}
break;
default:
dev_err(info->dev, "Unknown cable group (%d)\n", group);
cable_type = -EINVAL;
break;
}
return cable_type;
}
static int max8997_muic_handle_usb(struct max8997_muic_info *info,
enum max8997_muic_usb_type usb_type, bool attached)
{
int ret = 0;
if (usb_type == MAX8997_USB_HOST) {
ret = max8997_muic_set_path(info, info->path_usb, attached);
if (ret < 0) {
dev_err(info->dev, "failed to update muic register\n");
return ret;
}
}
switch (usb_type) {
case MAX8997_USB_HOST:
extcon_set_cable_state(info->edev, "USB-Host", attached);
break;
case MAX8997_USB_DEVICE:
extcon_set_cable_state(info->edev, "USB", attached);
break;
default:
dev_err(info->dev, "failed to detect %s usb cable\n",
attached ? "attached" : "detached");
return -EINVAL;
}
return 0;
}
static int max8997_muic_handle_dock(struct max8997_muic_info *info,
int cable_type, bool attached)
{
int ret = 0;
ret = max8997_muic_set_path(info, CONTROL1_SW_AUDIO, attached);
if (ret) {
dev_err(info->dev, "failed to update muic register\n");
return ret;
}
switch (cable_type) {
case MAX8997_MUIC_ADC_AV_CABLE_NOLOAD:
extcon_set_cable_state(info->edev, "Dock-desk", attached);
break;
case MAX8997_MUIC_ADC_FACTORY_MODE_UART_ON:
extcon_set_cable_state(info->edev, "Dock-card", attached);
break;
default:
dev_err(info->dev, "failed to detect %s dock device\n",
attached ? "attached" : "detached");
return -EINVAL;
}
return 0;
}
static int max8997_muic_handle_jig_uart(struct max8997_muic_info *info,
bool attached)
{
int ret = 0;
/* switch to UART */
ret = max8997_muic_set_path(info, info->path_uart, attached);
if (ret) {
dev_err(info->dev, "failed to update muic register\n");
return ret;
}
extcon_set_cable_state(info->edev, "JIG", attached);
return 0;
}
static int max8997_muic_adc_handler(struct max8997_muic_info *info)
{
int cable_type;
bool attached;
int ret = 0;
/* Check cable state which is either detached or attached */
cable_type = max8997_muic_get_cable_type(info,
MAX8997_CABLE_GROUP_ADC, &attached);
switch (cable_type) {
case MAX8997_MUIC_ADC_GROUND:
ret = max8997_muic_handle_usb(info, MAX8997_USB_HOST, attached);
if (ret < 0)
return ret;
break;
case MAX8997_MUIC_ADC_MHL:
extcon_set_cable_state(info->edev, "MHL", attached);
break;
case MAX8997_MUIC_ADC_FACTORY_MODE_USB_OFF:
case MAX8997_MUIC_ADC_FACTORY_MODE_USB_ON:
ret = max8997_muic_handle_usb(info,
MAX8997_USB_DEVICE, attached);
if (ret < 0)
return ret;
break;
case MAX8997_MUIC_ADC_AV_CABLE_NOLOAD:
case MAX8997_MUIC_ADC_FACTORY_MODE_UART_ON:
ret = max8997_muic_handle_dock(info, cable_type, attached);
if (ret < 0)
return ret;
break;
case MAX8997_MUIC_ADC_FACTORY_MODE_UART_OFF:
ret = max8997_muic_handle_jig_uart(info, attached);
break;
case MAX8997_MUIC_ADC_REMOTE_S1_BUTTON:
case MAX8997_MUIC_ADC_REMOTE_S2_BUTTON:
case MAX8997_MUIC_ADC_REMOTE_S3_BUTTON:
case MAX8997_MUIC_ADC_REMOTE_S4_BUTTON:
case MAX8997_MUIC_ADC_REMOTE_S5_BUTTON:
case MAX8997_MUIC_ADC_REMOTE_S6_BUTTON:
case MAX8997_MUIC_ADC_REMOTE_S7_BUTTON:
case MAX8997_MUIC_ADC_REMOTE_S8_BUTTON:
case MAX8997_MUIC_ADC_REMOTE_S9_BUTTON:
case MAX8997_MUIC_ADC_REMOTE_S10_BUTTON:
case MAX8997_MUIC_ADC_REMOTE_S11_BUTTON:
case MAX8997_MUIC_ADC_REMOTE_S12_BUTTON:
case MAX8997_MUIC_ADC_RESERVED_ACC_1:
case MAX8997_MUIC_ADC_RESERVED_ACC_2:
case MAX8997_MUIC_ADC_RESERVED_ACC_3:
case MAX8997_MUIC_ADC_RESERVED_ACC_4:
case MAX8997_MUIC_ADC_RESERVED_ACC_5:
case MAX8997_MUIC_ADC_CEA936_AUDIO:
case MAX8997_MUIC_ADC_PHONE_POWERED_DEV:
case MAX8997_MUIC_ADC_TTY_CONVERTER:
case MAX8997_MUIC_ADC_UART_CABLE:
case MAX8997_MUIC_ADC_CEA936A_TYPE1_CHG:
case MAX8997_MUIC_ADC_CEA936A_TYPE2_CHG:
case MAX8997_MUIC_ADC_AUDIO_MODE_REMOTE:
/*
* This cable isn't used in general case if it is specially
* needed to detect additional cable, should implement
* proper operation when this cable is attached/detached.
*/
dev_info(info->dev,
"cable is %s but it isn't used (type:0x%x)\n",
attached ? "attached" : "detached", cable_type);
return -EAGAIN;
default:
dev_err(info->dev,
"failed to detect %s unknown cable (type:0x%x)\n",
attached ? "attached" : "detached", cable_type);
return -EINVAL;
}
return 0;
}
static int max8997_muic_chg_handler(struct max8997_muic_info *info)
{
int chg_type;
bool attached;
int adc;
chg_type = max8997_muic_get_cable_type(info,
MAX8997_CABLE_GROUP_CHG, &attached);
switch (chg_type) {
case MAX8997_CHARGER_TYPE_NONE:
break;
case MAX8997_CHARGER_TYPE_USB:
adc = info->status[0] & STATUS1_ADC_MASK;
adc >>= STATUS1_ADC_SHIFT;
if ((adc & STATUS1_ADC_MASK) == MAX8997_MUIC_ADC_OPEN) {
max8997_muic_handle_usb(info,
MAX8997_USB_DEVICE, attached);
}
break;
case MAX8997_CHARGER_TYPE_DOWNSTREAM_PORT:
extcon_set_cable_state(info->edev,
"Charge-downstream", attached);
break;
case MAX8997_CHARGER_TYPE_DEDICATED_CHG:
extcon_set_cable_state(info->edev, "TA", attached);
break;
case MAX8997_CHARGER_TYPE_500MA:
extcon_set_cable_state(info->edev, "Slow-charger", attached);
break;
case MAX8997_CHARGER_TYPE_1A:
extcon_set_cable_state(info->edev, "Fast-charger", attached);
break;
default:
dev_err(info->dev,
"failed to detect %s unknown chg cable (type:0x%x)\n",
attached ? "attached" : "detached", chg_type);
return -EINVAL;
}
return 0;
}
static void max8997_muic_irq_work(struct work_struct *work)
{
struct max8997_muic_info *info = container_of(work,
struct max8997_muic_info, irq_work);
int irq_type = 0;
int i, ret;
if (!info->edev)
return;
mutex_lock(&info->mutex);
for (i = 0; i < ARRAY_SIZE(muic_irqs); i++)
if (info->irq == muic_irqs[i].virq)
irq_type = muic_irqs[i].irq;
ret = max8997_bulk_read(info->muic, MAX8997_MUIC_REG_STATUS1,
2, info->status);
if (ret) {
dev_err(info->dev, "failed to read muic register\n");
mutex_unlock(&info->mutex);
return;
}
switch (irq_type) {
case MAX8997_MUICIRQ_ADCError:
case MAX8997_MUICIRQ_ADCLow:
case MAX8997_MUICIRQ_ADC:
/* Handle all of cable except for charger cable */
ret = max8997_muic_adc_handler(info);
break;
case MAX8997_MUICIRQ_VBVolt:
case MAX8997_MUICIRQ_DBChg:
case MAX8997_MUICIRQ_DCDTmr:
case MAX8997_MUICIRQ_ChgDetRun:
case MAX8997_MUICIRQ_ChgTyp:
/* Handle charger cable */
ret = max8997_muic_chg_handler(info);
break;
case MAX8997_MUICIRQ_OVP:
break;
default:
dev_info(info->dev, "misc interrupt: irq %d occurred\n",
irq_type);
mutex_unlock(&info->mutex);
return;
}
if (ret < 0)
dev_err(info->dev, "failed to handle MUIC interrupt\n");
mutex_unlock(&info->mutex);
return;
}
static irqreturn_t max8997_muic_irq_handler(int irq, void *data)
{
struct max8997_muic_info *info = data;
dev_dbg(info->dev, "irq:%d\n", irq);
info->irq = irq;
schedule_work(&info->irq_work);
return IRQ_HANDLED;
}
static int max8997_muic_detect_dev(struct max8997_muic_info *info)
{
int ret = 0;
int adc;
int chg_type;
bool attached;
mutex_lock(&info->mutex);
/* Read STATUSx register to detect accessory */
ret = max8997_bulk_read(info->muic,
MAX8997_MUIC_REG_STATUS1, 2, info->status);
if (ret) {
dev_err(info->dev, "failed to read MUIC register\n");
mutex_unlock(&info->mutex);
return ret;
}
adc = max8997_muic_get_cable_type(info, MAX8997_CABLE_GROUP_ADC,
&attached);
if (attached && adc != MAX8997_MUIC_ADC_OPEN) {
ret = max8997_muic_adc_handler(info);
if (ret < 0) {
dev_err(info->dev, "Cannot detect ADC cable\n");
mutex_unlock(&info->mutex);
return ret;
}
}
chg_type = max8997_muic_get_cable_type(info, MAX8997_CABLE_GROUP_CHG,
&attached);
if (attached && chg_type != MAX8997_CHARGER_TYPE_NONE) {
ret = max8997_muic_chg_handler(info);
if (ret < 0) {
dev_err(info->dev, "Cannot detect charger cable\n");
mutex_unlock(&info->mutex);
return ret;
}
}
mutex_unlock(&info->mutex);
return 0;
}
static void max8997_muic_detect_cable_wq(struct work_struct *work)
{
struct max8997_muic_info *info = container_of(to_delayed_work(work),
struct max8997_muic_info, wq_detcable);
int ret;
ret = max8997_muic_detect_dev(info);
if (ret < 0)
dev_err(info->dev, "failed to detect cable type\n");
}
static int max8997_muic_probe(struct platform_device *pdev)
{
struct max8997_dev *max8997 = dev_get_drvdata(pdev->dev.parent);
struct max8997_platform_data *pdata = dev_get_platdata(max8997->dev);
struct max8997_muic_info *info;
int delay_jiffies;
int ret, i;
info = devm_kzalloc(&pdev->dev, sizeof(struct max8997_muic_info),
GFP_KERNEL);
if (!info)
return -ENOMEM;
info->dev = &pdev->dev;
info->muic = max8997->muic;
platform_set_drvdata(pdev, info);
mutex_init(&info->mutex);
INIT_WORK(&info->irq_work, max8997_muic_irq_work);
for (i = 0; i < ARRAY_SIZE(muic_irqs); i++) {
struct max8997_muic_irq *muic_irq = &muic_irqs[i];
unsigned int virq = 0;
virq = irq_create_mapping(max8997->irq_domain, muic_irq->irq);
if (!virq) {
ret = -EINVAL;
goto err_irq;
}
muic_irq->virq = virq;
ret = request_threaded_irq(virq, NULL,
max8997_muic_irq_handler,
IRQF_NO_SUSPEND,
muic_irq->name, info);
if (ret) {
dev_err(&pdev->dev,
"failed: irq request (IRQ: %d,"
" error :%d)\n",
muic_irq->irq, ret);
goto err_irq;
}
}
/* External connector */
info->edev = devm_extcon_dev_allocate(&pdev->dev, max8997_extcon_cable);
if (IS_ERR(info->edev)) {
dev_err(&pdev->dev, "failed to allocate memory for extcon\n");
ret = -ENOMEM;
goto err_irq;
}
info->edev->name = DEV_NAME;
ret = devm_extcon_dev_register(&pdev->dev, info->edev);
if (ret) {
dev_err(&pdev->dev, "failed to register extcon device\n");
goto err_irq;
}
if (pdata && pdata->muic_pdata) {
struct max8997_muic_platform_data *muic_pdata
= pdata->muic_pdata;
/* Initialize registers according to platform data */
for (i = 0; i < muic_pdata->num_init_data; i++) {
max8997_write_reg(info->muic,
muic_pdata->init_data[i].addr,
muic_pdata->init_data[i].data);
}
/*
* Default usb/uart path whether UART/USB or AUX_UART/AUX_USB
* h/w path of COMP2/COMN1 on CONTROL1 register.
*/
if (muic_pdata->path_uart)
info->path_uart = muic_pdata->path_uart;
else
info->path_uart = CONTROL1_SW_UART;
if (muic_pdata->path_usb)
info->path_usb = muic_pdata->path_usb;
else
info->path_usb = CONTROL1_SW_USB;
/*
* Default delay time for detecting cable state
* after certain time.
*/
if (muic_pdata->detcable_delay_ms)
delay_jiffies =
msecs_to_jiffies(muic_pdata->detcable_delay_ms);
else
delay_jiffies = msecs_to_jiffies(DELAY_MS_DEFAULT);
} else {
info->path_uart = CONTROL1_SW_UART;
info->path_usb = CONTROL1_SW_USB;
delay_jiffies = msecs_to_jiffies(DELAY_MS_DEFAULT);
}
/* Set initial path for UART */
max8997_muic_set_path(info, info->path_uart, true);
/* Set ADC debounce time */
max8997_muic_set_debounce_time(info, ADC_DEBOUNCE_TIME_25MS);
/*
* Detect accessory after completing the initialization of platform
*
* - Use delayed workqueue to detect cable state and then
* notify cable state to notifiee/platform through uevent.
* After completing the booting of platform, the extcon provider
* driver should notify cable state to upper layer.
*/
INIT_DELAYED_WORK(&info->wq_detcable, max8997_muic_detect_cable_wq);
queue_delayed_work(system_power_efficient_wq, &info->wq_detcable,
delay_jiffies);
return 0;
err_irq:
while (--i >= 0)
free_irq(muic_irqs[i].virq, info);
return ret;
}
static int max8997_muic_remove(struct platform_device *pdev)
{
struct max8997_muic_info *info = platform_get_drvdata(pdev);
int i;
for (i = 0; i < ARRAY_SIZE(muic_irqs); i++)
free_irq(muic_irqs[i].virq, info);
cancel_work_sync(&info->irq_work);
return 0;
}
static struct platform_driver max8997_muic_driver = {
.driver = {
.name = DEV_NAME,
.owner = THIS_MODULE,
},
.probe = max8997_muic_probe,
.remove = max8997_muic_remove,
};
module_platform_driver(max8997_muic_driver);
MODULE_DESCRIPTION("Maxim MAX8997 Extcon driver");
MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,305 @@
/*
* Palmas USB transceiver driver
*
* Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.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.
*
* Author: Graeme Gregory <gg@slimlogic.co.uk>
* Author: Kishon Vijay Abraham I <kishon@ti.com>
*
* Based on twl6030_usb.c
*
* Author: Hema HK <hemahk@ti.com>
*
* 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/module.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/mfd/palmas.h>
#include <linux/of.h>
#include <linux/of_platform.h>
static const char *palmas_extcon_cable[] = {
[0] = "USB",
[1] = "USB-HOST",
NULL,
};
static const int mutually_exclusive[] = {0x3, 0x0};
static void palmas_usb_wakeup(struct palmas *palmas, int enable)
{
if (enable)
palmas_write(palmas, PALMAS_USB_OTG_BASE, PALMAS_USB_WAKEUP,
PALMAS_USB_WAKEUP_ID_WK_UP_COMP);
else
palmas_write(palmas, PALMAS_USB_OTG_BASE, PALMAS_USB_WAKEUP, 0);
}
static irqreturn_t palmas_vbus_irq_handler(int irq, void *_palmas_usb)
{
struct palmas_usb *palmas_usb = _palmas_usb;
unsigned int vbus_line_state;
palmas_read(palmas_usb->palmas, PALMAS_INTERRUPT_BASE,
PALMAS_INT3_LINE_STATE, &vbus_line_state);
if (vbus_line_state & PALMAS_INT3_LINE_STATE_VBUS) {
if (palmas_usb->linkstat != PALMAS_USB_STATE_VBUS) {
palmas_usb->linkstat = PALMAS_USB_STATE_VBUS;
extcon_set_cable_state(palmas_usb->edev, "USB", true);
dev_info(palmas_usb->dev, "USB cable is attached\n");
} else {
dev_dbg(palmas_usb->dev,
"Spurious connect event detected\n");
}
} else if (!(vbus_line_state & PALMAS_INT3_LINE_STATE_VBUS)) {
if (palmas_usb->linkstat == PALMAS_USB_STATE_VBUS) {
palmas_usb->linkstat = PALMAS_USB_STATE_DISCONNECT;
extcon_set_cable_state(palmas_usb->edev, "USB", false);
dev_info(palmas_usb->dev, "USB cable is detached\n");
} else {
dev_dbg(palmas_usb->dev,
"Spurious disconnect event detected\n");
}
}
return IRQ_HANDLED;
}
static irqreturn_t palmas_id_irq_handler(int irq, void *_palmas_usb)
{
unsigned int set, id_src;
struct palmas_usb *palmas_usb = _palmas_usb;
palmas_read(palmas_usb->palmas, PALMAS_USB_OTG_BASE,
PALMAS_USB_ID_INT_LATCH_SET, &set);
palmas_read(palmas_usb->palmas, PALMAS_USB_OTG_BASE,
PALMAS_USB_ID_INT_SRC, &id_src);
if ((set & PALMAS_USB_ID_INT_SRC_ID_GND) &&
(id_src & PALMAS_USB_ID_INT_SRC_ID_GND)) {
palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE,
PALMAS_USB_ID_INT_LATCH_CLR,
PALMAS_USB_ID_INT_EN_HI_CLR_ID_GND);
palmas_usb->linkstat = PALMAS_USB_STATE_ID;
extcon_set_cable_state(palmas_usb->edev, "USB-HOST", true);
dev_info(palmas_usb->dev, "USB-HOST cable is attached\n");
} else if ((set & PALMAS_USB_ID_INT_SRC_ID_FLOAT) &&
(id_src & PALMAS_USB_ID_INT_SRC_ID_FLOAT)) {
palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE,
PALMAS_USB_ID_INT_LATCH_CLR,
PALMAS_USB_ID_INT_EN_HI_CLR_ID_FLOAT);
palmas_usb->linkstat = PALMAS_USB_STATE_DISCONNECT;
extcon_set_cable_state(palmas_usb->edev, "USB-HOST", false);
dev_info(palmas_usb->dev, "USB-HOST cable is detached\n");
} else if ((palmas_usb->linkstat == PALMAS_USB_STATE_ID) &&
(!(set & PALMAS_USB_ID_INT_SRC_ID_GND))) {
palmas_usb->linkstat = PALMAS_USB_STATE_DISCONNECT;
extcon_set_cable_state(palmas_usb->edev, "USB-HOST", false);
dev_info(palmas_usb->dev, "USB-HOST cable is detached\n");
} else if ((palmas_usb->linkstat == PALMAS_USB_STATE_DISCONNECT) &&
(id_src & PALMAS_USB_ID_INT_SRC_ID_GND)) {
palmas_usb->linkstat = PALMAS_USB_STATE_ID;
extcon_set_cable_state(palmas_usb->edev, "USB-HOST", true);
dev_info(palmas_usb->dev, " USB-HOST cable is attached\n");
}
return IRQ_HANDLED;
}
static void palmas_enable_irq(struct palmas_usb *palmas_usb)
{
palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE,
PALMAS_USB_VBUS_CTRL_SET,
PALMAS_USB_VBUS_CTRL_SET_VBUS_ACT_COMP);
palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE,
PALMAS_USB_ID_CTRL_SET, PALMAS_USB_ID_CTRL_SET_ID_ACT_COMP);
palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE,
PALMAS_USB_ID_INT_EN_HI_SET,
PALMAS_USB_ID_INT_EN_HI_SET_ID_GND |
PALMAS_USB_ID_INT_EN_HI_SET_ID_FLOAT);
if (palmas_usb->enable_vbus_detection)
palmas_vbus_irq_handler(palmas_usb->vbus_irq, palmas_usb);
/* cold plug for host mode needs this delay */
if (palmas_usb->enable_id_detection) {
msleep(30);
palmas_id_irq_handler(palmas_usb->id_irq, palmas_usb);
}
}
static int palmas_usb_probe(struct platform_device *pdev)
{
struct palmas *palmas = dev_get_drvdata(pdev->dev.parent);
struct palmas_usb_platform_data *pdata = dev_get_platdata(&pdev->dev);
struct device_node *node = pdev->dev.of_node;
struct palmas_usb *palmas_usb;
int status;
palmas_usb = devm_kzalloc(&pdev->dev, sizeof(*palmas_usb), GFP_KERNEL);
if (!palmas_usb)
return -ENOMEM;
if (node && !pdata) {
palmas_usb->wakeup = of_property_read_bool(node, "ti,wakeup");
palmas_usb->enable_id_detection = of_property_read_bool(node,
"ti,enable-id-detection");
palmas_usb->enable_vbus_detection = of_property_read_bool(node,
"ti,enable-vbus-detection");
} else {
palmas_usb->wakeup = true;
palmas_usb->enable_id_detection = true;
palmas_usb->enable_vbus_detection = true;
if (pdata)
palmas_usb->wakeup = pdata->wakeup;
}
palmas->usb = palmas_usb;
palmas_usb->palmas = palmas;
palmas_usb->dev = &pdev->dev;
palmas_usb->id_otg_irq = regmap_irq_get_virq(palmas->irq_data,
PALMAS_ID_OTG_IRQ);
palmas_usb->id_irq = regmap_irq_get_virq(palmas->irq_data,
PALMAS_ID_IRQ);
palmas_usb->vbus_otg_irq = regmap_irq_get_virq(palmas->irq_data,
PALMAS_VBUS_OTG_IRQ);
palmas_usb->vbus_irq = regmap_irq_get_virq(palmas->irq_data,
PALMAS_VBUS_IRQ);
palmas_usb_wakeup(palmas, palmas_usb->wakeup);
platform_set_drvdata(pdev, palmas_usb);
palmas_usb->edev = devm_extcon_dev_allocate(&pdev->dev,
palmas_extcon_cable);
if (IS_ERR(palmas_usb->edev)) {
dev_err(&pdev->dev, "failed to allocate extcon device\n");
return -ENOMEM;
}
palmas_usb->edev->name = kstrdup(node->name, GFP_KERNEL);
palmas_usb->edev->mutually_exclusive = mutually_exclusive;
status = devm_extcon_dev_register(&pdev->dev, palmas_usb->edev);
if (status) {
dev_err(&pdev->dev, "failed to register extcon device\n");
kfree(palmas_usb->edev->name);
return status;
}
if (palmas_usb->enable_id_detection) {
status = devm_request_threaded_irq(palmas_usb->dev,
palmas_usb->id_irq,
NULL, palmas_id_irq_handler,
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING |
IRQF_ONESHOT | IRQF_EARLY_RESUME,
"palmas_usb_id", palmas_usb);
if (status < 0) {
dev_err(&pdev->dev, "can't get IRQ %d, err %d\n",
palmas_usb->id_irq, status);
kfree(palmas_usb->edev->name);
return status;
}
}
if (palmas_usb->enable_vbus_detection) {
status = devm_request_threaded_irq(palmas_usb->dev,
palmas_usb->vbus_irq, NULL,
palmas_vbus_irq_handler,
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING |
IRQF_ONESHOT | IRQF_EARLY_RESUME,
"palmas_usb_vbus", palmas_usb);
if (status < 0) {
dev_err(&pdev->dev, "can't get IRQ %d, err %d\n",
palmas_usb->vbus_irq, status);
kfree(palmas_usb->edev->name);
return status;
}
}
palmas_enable_irq(palmas_usb);
device_set_wakeup_capable(&pdev->dev, true);
return 0;
}
static int palmas_usb_remove(struct platform_device *pdev)
{
struct palmas_usb *palmas_usb = platform_get_drvdata(pdev);
kfree(palmas_usb->edev->name);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int palmas_usb_suspend(struct device *dev)
{
struct palmas_usb *palmas_usb = dev_get_drvdata(dev);
if (device_may_wakeup(dev)) {
if (palmas_usb->enable_vbus_detection)
enable_irq_wake(palmas_usb->vbus_irq);
if (palmas_usb->enable_id_detection)
enable_irq_wake(palmas_usb->id_irq);
}
return 0;
}
static int palmas_usb_resume(struct device *dev)
{
struct palmas_usb *palmas_usb = dev_get_drvdata(dev);
if (device_may_wakeup(dev)) {
if (palmas_usb->enable_vbus_detection)
disable_irq_wake(palmas_usb->vbus_irq);
if (palmas_usb->enable_id_detection)
disable_irq_wake(palmas_usb->id_irq);
}
return 0;
};
#endif
static SIMPLE_DEV_PM_OPS(palmas_pm_ops, palmas_usb_suspend, palmas_usb_resume);
static const struct of_device_id of_palmas_match_tbl[] = {
{ .compatible = "ti,palmas-usb", },
{ .compatible = "ti,palmas-usb-vid", },
{ .compatible = "ti,twl6035-usb", },
{ .compatible = "ti,twl6035-usb-vid", },
{ /* end */ }
};
static struct platform_driver palmas_usb_driver = {
.probe = palmas_usb_probe,
.remove = palmas_usb_remove,
.driver = {
.name = "palmas-usb",
.of_match_table = of_palmas_match_tbl,
.owner = THIS_MODULE,
.pm = &palmas_pm_ops,
},
};
module_platform_driver(palmas_usb_driver);
MODULE_ALIAS("platform:palmas-usb");
MODULE_AUTHOR("Graeme Gregory <gg@slimlogic.co.uk>");
MODULE_DESCRIPTION("Palmas USB transceiver driver");
MODULE_LICENSE("GPL");
MODULE_DEVICE_TABLE(of, of_palmas_match_tbl);

View file

@ -0,0 +1,740 @@
/*
* extcon-rt8973a.c - Richtek RT8973A extcon driver to support USB switches
*
* Copyright (c) 2014 Samsung Electronics Co., Ltd
* Author: Chanwoo Choi <cw00.choi@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.
*/
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/irqdomain.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/extcon.h>
#include "extcon-rt8973a.h"
#define DELAY_MS_DEFAULT 20000 /* unit: millisecond */
struct muic_irq {
unsigned int irq;
const char *name;
unsigned int virq;
};
struct reg_data {
u8 reg;
u8 mask;
u8 val;
bool invert;
};
struct rt8973a_muic_info {
struct device *dev;
struct extcon_dev *edev;
struct i2c_client *i2c;
struct regmap *regmap;
struct regmap_irq_chip_data *irq_data;
struct muic_irq *muic_irqs;
unsigned int num_muic_irqs;
int irq;
bool irq_attach;
bool irq_detach;
bool irq_ovp;
bool irq_otp;
struct work_struct irq_work;
struct reg_data *reg_data;
unsigned int num_reg_data;
bool auto_config;
struct mutex mutex;
/*
* Use delayed workqueue to detect cable state and then
* notify cable state to notifiee/platform through uevent.
* After completing the booting of platform, the extcon provider
* driver should notify cable state to upper layer.
*/
struct delayed_work wq_detcable;
};
/* Default value of RT8973A register to bring up MUIC device. */
static struct reg_data rt8973a_reg_data[] = {
{
.reg = RT8973A_REG_CONTROL1,
.mask = RT8973A_REG_CONTROL1_ADC_EN_MASK
| RT8973A_REG_CONTROL1_USB_CHD_EN_MASK
| RT8973A_REG_CONTROL1_CHGTYP_MASK
| RT8973A_REG_CONTROL1_SWITCH_OPEN_MASK
| RT8973A_REG_CONTROL1_AUTO_CONFIG_MASK
| RT8973A_REG_CONTROL1_INTM_MASK,
.val = RT8973A_REG_CONTROL1_ADC_EN_MASK
| RT8973A_REG_CONTROL1_USB_CHD_EN_MASK
| RT8973A_REG_CONTROL1_CHGTYP_MASK,
.invert = false,
},
{ /* sentinel */ }
};
/* List of detectable cables */
enum {
EXTCON_CABLE_USB = 0,
EXTCON_CABLE_USB_HOST,
EXTCON_CABLE_TA,
EXTCON_CABLE_JIG_OFF_USB,
EXTCON_CABLE_JIG_ON_USB,
EXTCON_CABLE_JIG_OFF_UART,
EXTCON_CABLE_JIG_ON_UART,
EXTCON_CABLE_END,
};
static const char *rt8973a_extcon_cable[] = {
[EXTCON_CABLE_USB] = "USB",
[EXTCON_CABLE_USB_HOST] = "USB-Host",
[EXTCON_CABLE_TA] = "TA",
[EXTCON_CABLE_JIG_OFF_USB] = "JIG-USB-OFF",
[EXTCON_CABLE_JIG_ON_USB] = "JIG-USB-ON",
[EXTCON_CABLE_JIG_OFF_UART] = "JIG-UART-OFF",
[EXTCON_CABLE_JIG_ON_UART] = "JIG-UART-ON",
NULL,
};
/* Define OVP (Over Voltage Protection), OTP (Over Temperature Protection) */
enum rt8973a_event_type {
RT8973A_EVENT_ATTACH = 1,
RT8973A_EVENT_DETACH,
RT8973A_EVENT_OVP,
RT8973A_EVENT_OTP,
};
/* Define supported accessory type */
enum rt8973a_muic_acc_type {
RT8973A_MUIC_ADC_OTG = 0x0,
RT8973A_MUIC_ADC_AUDIO_SEND_END_BUTTON,
RT8973A_MUIC_ADC_AUDIO_REMOTE_S1_BUTTON,
RT8973A_MUIC_ADC_AUDIO_REMOTE_S2_BUTTON,
RT8973A_MUIC_ADC_AUDIO_REMOTE_S3_BUTTON,
RT8973A_MUIC_ADC_AUDIO_REMOTE_S4_BUTTON,
RT8973A_MUIC_ADC_AUDIO_REMOTE_S5_BUTTON,
RT8973A_MUIC_ADC_AUDIO_REMOTE_S6_BUTTON,
RT8973A_MUIC_ADC_AUDIO_REMOTE_S7_BUTTON,
RT8973A_MUIC_ADC_AUDIO_REMOTE_S8_BUTTON,
RT8973A_MUIC_ADC_AUDIO_REMOTE_S9_BUTTON,
RT8973A_MUIC_ADC_AUDIO_REMOTE_S10_BUTTON,
RT8973A_MUIC_ADC_AUDIO_REMOTE_S11_BUTTON,
RT8973A_MUIC_ADC_AUDIO_REMOTE_S12_BUTTON,
RT8973A_MUIC_ADC_RESERVED_ACC_1,
RT8973A_MUIC_ADC_RESERVED_ACC_2,
RT8973A_MUIC_ADC_RESERVED_ACC_3,
RT8973A_MUIC_ADC_RESERVED_ACC_4,
RT8973A_MUIC_ADC_RESERVED_ACC_5,
RT8973A_MUIC_ADC_AUDIO_TYPE2,
RT8973A_MUIC_ADC_PHONE_POWERED_DEV,
RT8973A_MUIC_ADC_UNKNOWN_ACC_1,
RT8973A_MUIC_ADC_UNKNOWN_ACC_2,
RT8973A_MUIC_ADC_TA,
RT8973A_MUIC_ADC_FACTORY_MODE_BOOT_OFF_USB,
RT8973A_MUIC_ADC_FACTORY_MODE_BOOT_ON_USB,
RT8973A_MUIC_ADC_UNKNOWN_ACC_3,
RT8973A_MUIC_ADC_UNKNOWN_ACC_4,
RT8973A_MUIC_ADC_FACTORY_MODE_BOOT_OFF_UART,
RT8973A_MUIC_ADC_FACTORY_MODE_BOOT_ON_UART,
RT8973A_MUIC_ADC_UNKNOWN_ACC_5,
RT8973A_MUIC_ADC_OPEN = 0x1f,
/* The below accessories has same ADC value (0x1f).
So, Device type1 is used to separate specific accessory. */
/* |---------|--ADC| */
/* | [7:5]|[4:0]| */
RT8973A_MUIC_ADC_USB = 0x3f, /* | 001|11111| */
};
/* List of supported interrupt for RT8973A */
static struct muic_irq rt8973a_muic_irqs[] = {
{ RT8973A_INT1_ATTACH, "muic-attach" },
{ RT8973A_INT1_DETACH, "muic-detach" },
{ RT8973A_INT1_CHGDET, "muic-chgdet" },
{ RT8973A_INT1_DCD_T, "muic-dcd-t" },
{ RT8973A_INT1_OVP, "muic-ovp" },
{ RT8973A_INT1_CONNECT, "muic-connect" },
{ RT8973A_INT1_ADC_CHG, "muic-adc-chg" },
{ RT8973A_INT1_OTP, "muic-otp" },
{ RT8973A_INT2_UVLO, "muic-uvlo" },
{ RT8973A_INT2_POR, "muic-por" },
{ RT8973A_INT2_OTP_FET, "muic-otp-fet" },
{ RT8973A_INT2_OVP_FET, "muic-ovp-fet" },
{ RT8973A_INT2_OCP_LATCH, "muic-ocp-latch" },
{ RT8973A_INT2_OCP, "muic-ocp" },
{ RT8973A_INT2_OVP_OCP, "muic-ovp-ocp" },
};
/* Define interrupt list of RT8973A to register regmap_irq */
static const struct regmap_irq rt8973a_irqs[] = {
/* INT1 interrupts */
{ .reg_offset = 0, .mask = RT8973A_INT1_ATTACH_MASK, },
{ .reg_offset = 0, .mask = RT8973A_INT1_DETACH_MASK, },
{ .reg_offset = 0, .mask = RT8973A_INT1_CHGDET_MASK, },
{ .reg_offset = 0, .mask = RT8973A_INT1_DCD_T_MASK, },
{ .reg_offset = 0, .mask = RT8973A_INT1_OVP_MASK, },
{ .reg_offset = 0, .mask = RT8973A_INT1_CONNECT_MASK, },
{ .reg_offset = 0, .mask = RT8973A_INT1_ADC_CHG_MASK, },
{ .reg_offset = 0, .mask = RT8973A_INT1_OTP_MASK, },
/* INT2 interrupts */
{ .reg_offset = 1, .mask = RT8973A_INT2_UVLOT_MASK,},
{ .reg_offset = 1, .mask = RT8973A_INT2_POR_MASK, },
{ .reg_offset = 1, .mask = RT8973A_INT2_OTP_FET_MASK, },
{ .reg_offset = 1, .mask = RT8973A_INT2_OVP_FET_MASK, },
{ .reg_offset = 1, .mask = RT8973A_INT2_OCP_LATCH_MASK, },
{ .reg_offset = 1, .mask = RT8973A_INT2_OCP_MASK, },
{ .reg_offset = 1, .mask = RT8973A_INT2_OVP_OCP_MASK, },
};
static const struct regmap_irq_chip rt8973a_muic_irq_chip = {
.name = "rt8973a",
.status_base = RT8973A_REG_INT1,
.mask_base = RT8973A_REG_INTM1,
.mask_invert = false,
.num_regs = 2,
.irqs = rt8973a_irqs,
.num_irqs = ARRAY_SIZE(rt8973a_irqs),
};
/* Define regmap configuration of RT8973A for I2C communication */
static bool rt8973a_muic_volatile_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case RT8973A_REG_INTM1:
case RT8973A_REG_INTM2:
return true;
default:
break;
}
return false;
}
static const struct regmap_config rt8973a_muic_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.volatile_reg = rt8973a_muic_volatile_reg,
.max_register = RT8973A_REG_END,
};
/* Change DM_CON/DP_CON/VBUSIN switch according to cable type */
static int rt8973a_muic_set_path(struct rt8973a_muic_info *info,
unsigned int con_sw, bool attached)
{
int ret;
/*
* Don't need to set h/w path according to cable type
* if Auto-configuration mode of CONTROL1 register is true.
*/
if (info->auto_config)
return 0;
if (!attached)
con_sw = DM_DP_SWITCH_UART;
switch (con_sw) {
case DM_DP_SWITCH_OPEN:
case DM_DP_SWITCH_USB:
case DM_DP_SWITCH_UART:
ret = regmap_update_bits(info->regmap, RT8973A_REG_MANUAL_SW1,
RT8973A_REG_MANUAL_SW1_DP_MASK |
RT8973A_REG_MANUAL_SW1_DM_MASK,
con_sw);
if (ret < 0) {
dev_err(info->dev,
"cannot update DM_CON/DP_CON switch\n");
return ret;
}
break;
default:
dev_err(info->dev, "Unknown DM_CON/DP_CON switch type (%d)\n",
con_sw);
return -EINVAL;
}
return 0;
}
static int rt8973a_muic_get_cable_type(struct rt8973a_muic_info *info)
{
unsigned int adc, dev1;
int ret, cable_type;
/* Read ADC value according to external cable or button */
ret = regmap_read(info->regmap, RT8973A_REG_ADC, &adc);
if (ret) {
dev_err(info->dev, "failed to read ADC register\n");
return ret;
}
cable_type = adc & RT8973A_REG_ADC_MASK;
/* Read Device 1 reigster to identify correct cable type */
ret = regmap_read(info->regmap, RT8973A_REG_DEV1, &dev1);
if (ret) {
dev_err(info->dev, "failed to read DEV1 register\n");
return ret;
}
switch (adc) {
case RT8973A_MUIC_ADC_OPEN:
if (dev1 & RT8973A_REG_DEV1_USB_MASK)
cable_type = RT8973A_MUIC_ADC_USB;
else if (dev1 & RT8973A_REG_DEV1_DCPORT_MASK)
cable_type = RT8973A_MUIC_ADC_TA;
else
cable_type = RT8973A_MUIC_ADC_OPEN;
break;
default:
break;
}
return cable_type;
}
static int rt8973a_muic_cable_handler(struct rt8973a_muic_info *info,
enum rt8973a_event_type event)
{
static unsigned int prev_cable_type;
const char **cable_names = info->edev->supported_cable;
unsigned int con_sw = DM_DP_SWITCH_UART;
int ret, idx = 0, cable_type;
bool attached = false;
if (!cable_names)
return 0;
switch (event) {
case RT8973A_EVENT_ATTACH:
cable_type = rt8973a_muic_get_cable_type(info);
attached = true;
break;
case RT8973A_EVENT_DETACH:
cable_type = prev_cable_type;
attached = false;
break;
case RT8973A_EVENT_OVP:
case RT8973A_EVENT_OTP:
dev_warn(info->dev,
"happen Over %s issue. Need to disconnect all cables\n",
event == RT8973A_EVENT_OVP ? "Voltage" : "Temperature");
cable_type = prev_cable_type;
attached = false;
break;
default:
dev_err(info->dev,
"Cannot handle this event (event:%d)\n", event);
return -EINVAL;
}
prev_cable_type = cable_type;
switch (cable_type) {
case RT8973A_MUIC_ADC_OTG:
idx = EXTCON_CABLE_USB_HOST;
con_sw = DM_DP_SWITCH_USB;
break;
case RT8973A_MUIC_ADC_TA:
idx = EXTCON_CABLE_TA;
con_sw = DM_DP_SWITCH_OPEN;
break;
case RT8973A_MUIC_ADC_FACTORY_MODE_BOOT_OFF_USB:
idx = EXTCON_CABLE_JIG_OFF_USB;
con_sw = DM_DP_SWITCH_UART;
break;
case RT8973A_MUIC_ADC_FACTORY_MODE_BOOT_ON_USB:
idx = EXTCON_CABLE_JIG_ON_USB;
con_sw = DM_DP_SWITCH_UART;
break;
case RT8973A_MUIC_ADC_FACTORY_MODE_BOOT_OFF_UART:
idx = EXTCON_CABLE_JIG_OFF_UART;
con_sw = DM_DP_SWITCH_UART;
break;
case RT8973A_MUIC_ADC_FACTORY_MODE_BOOT_ON_UART:
idx = EXTCON_CABLE_JIG_ON_UART;
con_sw = DM_DP_SWITCH_UART;
break;
case RT8973A_MUIC_ADC_USB:
idx = EXTCON_CABLE_USB;
con_sw = DM_DP_SWITCH_USB;
break;
case RT8973A_MUIC_ADC_OPEN:
return 0;
case RT8973A_MUIC_ADC_UNKNOWN_ACC_1:
case RT8973A_MUIC_ADC_UNKNOWN_ACC_2:
case RT8973A_MUIC_ADC_UNKNOWN_ACC_3:
case RT8973A_MUIC_ADC_UNKNOWN_ACC_4:
case RT8973A_MUIC_ADC_UNKNOWN_ACC_5:
dev_warn(info->dev,
"Unknown accessory type (adc:0x%x)\n", cable_type);
return 0;
case RT8973A_MUIC_ADC_AUDIO_SEND_END_BUTTON:
case RT8973A_MUIC_ADC_AUDIO_REMOTE_S1_BUTTON:
case RT8973A_MUIC_ADC_AUDIO_REMOTE_S2_BUTTON:
case RT8973A_MUIC_ADC_AUDIO_REMOTE_S3_BUTTON:
case RT8973A_MUIC_ADC_AUDIO_REMOTE_S4_BUTTON:
case RT8973A_MUIC_ADC_AUDIO_REMOTE_S5_BUTTON:
case RT8973A_MUIC_ADC_AUDIO_REMOTE_S6_BUTTON:
case RT8973A_MUIC_ADC_AUDIO_REMOTE_S7_BUTTON:
case RT8973A_MUIC_ADC_AUDIO_REMOTE_S8_BUTTON:
case RT8973A_MUIC_ADC_AUDIO_REMOTE_S9_BUTTON:
case RT8973A_MUIC_ADC_AUDIO_REMOTE_S10_BUTTON:
case RT8973A_MUIC_ADC_AUDIO_REMOTE_S11_BUTTON:
case RT8973A_MUIC_ADC_AUDIO_REMOTE_S12_BUTTON:
case RT8973A_MUIC_ADC_AUDIO_TYPE2:
dev_warn(info->dev,
"Audio device/button type (adc:0x%x)\n", cable_type);
return 0;
case RT8973A_MUIC_ADC_RESERVED_ACC_1:
case RT8973A_MUIC_ADC_RESERVED_ACC_2:
case RT8973A_MUIC_ADC_RESERVED_ACC_3:
case RT8973A_MUIC_ADC_RESERVED_ACC_4:
case RT8973A_MUIC_ADC_RESERVED_ACC_5:
case RT8973A_MUIC_ADC_PHONE_POWERED_DEV:
return 0;
default:
dev_err(info->dev,
"Cannot handle this cable_type (adc:0x%x)\n",
cable_type);
return -EINVAL;
}
/* Change internal hardware path(DM_CON/DP_CON) */
ret = rt8973a_muic_set_path(info, con_sw, attached);
if (ret < 0)
return ret;
/* Change the state of external accessory */
extcon_set_cable_state(info->edev, cable_names[idx], attached);
return 0;
}
static void rt8973a_muic_irq_work(struct work_struct *work)
{
struct rt8973a_muic_info *info = container_of(work,
struct rt8973a_muic_info, irq_work);
int ret = 0;
if (!info->edev)
return;
mutex_lock(&info->mutex);
/* Detect attached or detached cables */
if (info->irq_attach) {
ret = rt8973a_muic_cable_handler(info, RT8973A_EVENT_ATTACH);
info->irq_attach = false;
}
if (info->irq_detach) {
ret = rt8973a_muic_cable_handler(info, RT8973A_EVENT_DETACH);
info->irq_detach = false;
}
if (info->irq_ovp) {
ret = rt8973a_muic_cable_handler(info, RT8973A_EVENT_OVP);
info->irq_ovp = false;
}
if (info->irq_otp) {
ret = rt8973a_muic_cable_handler(info, RT8973A_EVENT_OTP);
info->irq_otp = false;
}
if (ret < 0)
dev_err(info->dev, "failed to handle MUIC interrupt\n");
mutex_unlock(&info->mutex);
}
static irqreturn_t rt8973a_muic_irq_handler(int irq, void *data)
{
struct rt8973a_muic_info *info = data;
int i, irq_type = -1;
for (i = 0; i < info->num_muic_irqs; i++)
if (irq == info->muic_irqs[i].virq)
irq_type = info->muic_irqs[i].irq;
switch (irq_type) {
case RT8973A_INT1_ATTACH:
info->irq_attach = true;
break;
case RT8973A_INT1_DETACH:
info->irq_detach = true;
break;
case RT8973A_INT1_OVP:
info->irq_ovp = true;
break;
case RT8973A_INT1_OTP:
info->irq_otp = true;
break;
case RT8973A_INT1_CHGDET:
case RT8973A_INT1_DCD_T:
case RT8973A_INT1_CONNECT:
case RT8973A_INT1_ADC_CHG:
case RT8973A_INT2_UVLO:
case RT8973A_INT2_POR:
case RT8973A_INT2_OTP_FET:
case RT8973A_INT2_OVP_FET:
case RT8973A_INT2_OCP_LATCH:
case RT8973A_INT2_OCP:
case RT8973A_INT2_OVP_OCP:
default:
dev_dbg(info->dev,
"Cannot handle this interrupt (%d)\n", irq_type);
break;
}
schedule_work(&info->irq_work);
return IRQ_HANDLED;
}
static void rt8973a_muic_detect_cable_wq(struct work_struct *work)
{
struct rt8973a_muic_info *info = container_of(to_delayed_work(work),
struct rt8973a_muic_info, wq_detcable);
int ret;
/* Notify the state of connector cable or not */
ret = rt8973a_muic_cable_handler(info, RT8973A_EVENT_ATTACH);
if (ret < 0)
dev_warn(info->dev, "failed to detect cable state\n");
}
static void rt8973a_init_dev_type(struct rt8973a_muic_info *info)
{
unsigned int data, vendor_id, version_id;
int i, ret;
/* To test I2C, Print version_id and vendor_id of RT8973A */
ret = regmap_read(info->regmap, RT8973A_REG_DEVICE_ID, &data);
if (ret) {
dev_err(info->dev,
"failed to read DEVICE_ID register: %d\n", ret);
return;
}
vendor_id = ((data & RT8973A_REG_DEVICE_ID_VENDOR_MASK) >>
RT8973A_REG_DEVICE_ID_VENDOR_SHIFT);
version_id = ((data & RT8973A_REG_DEVICE_ID_VERSION_MASK) >>
RT8973A_REG_DEVICE_ID_VERSION_SHIFT);
dev_info(info->dev, "Device type: version: 0x%x, vendor: 0x%x\n",
version_id, vendor_id);
/* Initiazle the register of RT8973A device to bring-up */
for (i = 0; i < info->num_reg_data; i++) {
u8 reg = info->reg_data[i].reg;
u8 mask = info->reg_data[i].mask;
u8 val = 0;
if (info->reg_data[i].invert)
val = ~info->reg_data[i].val;
else
val = info->reg_data[i].val;
regmap_update_bits(info->regmap, reg, mask, val);
}
/* Check whether RT8973A is auto swithcing mode or not */
ret = regmap_read(info->regmap, RT8973A_REG_CONTROL1, &data);
if (ret) {
dev_err(info->dev,
"failed to read CONTROL1 register: %d\n", ret);
return;
}
data &= RT8973A_REG_CONTROL1_AUTO_CONFIG_MASK;
if (data) {
info->auto_config = true;
dev_info(info->dev,
"Enable Auto-configuration for internal path\n");
}
}
static int rt8973a_muic_i2c_probe(struct i2c_client *i2c,
const struct i2c_device_id *id)
{
struct device_node *np = i2c->dev.of_node;
struct rt8973a_muic_info *info;
int i, ret, irq_flags;
if (!np)
return -EINVAL;
info = devm_kzalloc(&i2c->dev, sizeof(*info), GFP_KERNEL);
if (!info) {
dev_err(&i2c->dev, "failed to allocate memory\n");
return -ENOMEM;
}
i2c_set_clientdata(i2c, info);
info->dev = &i2c->dev;
info->i2c = i2c;
info->irq = i2c->irq;
info->muic_irqs = rt8973a_muic_irqs;
info->num_muic_irqs = ARRAY_SIZE(rt8973a_muic_irqs);
info->reg_data = rt8973a_reg_data;
info->num_reg_data = ARRAY_SIZE(rt8973a_reg_data);
mutex_init(&info->mutex);
INIT_WORK(&info->irq_work, rt8973a_muic_irq_work);
info->regmap = devm_regmap_init_i2c(i2c, &rt8973a_muic_regmap_config);
if (IS_ERR(info->regmap)) {
ret = PTR_ERR(info->regmap);
dev_err(info->dev, "failed to allocate register map: %d\n",
ret);
return ret;
}
/* Support irq domain for RT8973A MUIC device */
irq_flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT | IRQF_SHARED;
ret = regmap_add_irq_chip(info->regmap, info->irq, irq_flags, 0,
&rt8973a_muic_irq_chip, &info->irq_data);
if (ret != 0) {
dev_err(info->dev, "failed to add irq_chip (irq:%d, err:%d)\n",
info->irq, ret);
return ret;
}
for (i = 0; i < info->num_muic_irqs; i++) {
struct muic_irq *muic_irq = &info->muic_irqs[i];
unsigned int virq = 0;
virq = regmap_irq_get_virq(info->irq_data, muic_irq->irq);
if (virq <= 0)
return -EINVAL;
muic_irq->virq = virq;
ret = devm_request_threaded_irq(info->dev, virq, NULL,
rt8973a_muic_irq_handler,
IRQF_NO_SUSPEND,
muic_irq->name, info);
if (ret) {
dev_err(info->dev,
"failed: irq request (IRQ: %d, error :%d)\n",
muic_irq->irq, ret);
return ret;
}
}
/* Allocate extcon device */
info->edev = devm_extcon_dev_allocate(info->dev, rt8973a_extcon_cable);
if (IS_ERR(info->edev)) {
dev_err(info->dev, "failed to allocate memory for extcon\n");
return -ENOMEM;
}
info->edev->name = np->name;
/* Register extcon device */
ret = devm_extcon_dev_register(info->dev, info->edev);
if (ret) {
dev_err(info->dev, "failed to register extcon device\n");
return ret;
}
/*
* Detect accessory after completing the initialization of platform
*
* - Use delayed workqueue to detect cable state and then
* notify cable state to notifiee/platform through uevent.
* After completing the booting of platform, the extcon provider
* driver should notify cable state to upper layer.
*/
INIT_DELAYED_WORK(&info->wq_detcable, rt8973a_muic_detect_cable_wq);
queue_delayed_work(system_power_efficient_wq, &info->wq_detcable,
msecs_to_jiffies(DELAY_MS_DEFAULT));
/* Initialize RT8973A device and print vendor id and version id */
rt8973a_init_dev_type(info);
return 0;
}
static int rt8973a_muic_i2c_remove(struct i2c_client *i2c)
{
struct rt8973a_muic_info *info = i2c_get_clientdata(i2c);
regmap_del_irq_chip(info->irq, info->irq_data);
return 0;
}
static struct of_device_id rt8973a_dt_match[] = {
{ .compatible = "richtek,rt8973a-muic" },
{ },
};
#ifdef CONFIG_PM_SLEEP
static int rt8973a_muic_suspend(struct device *dev)
{
struct i2c_client *i2c = container_of(dev, struct i2c_client, dev);
struct rt8973a_muic_info *info = i2c_get_clientdata(i2c);
enable_irq_wake(info->irq);
return 0;
}
static int rt8973a_muic_resume(struct device *dev)
{
struct i2c_client *i2c = container_of(dev, struct i2c_client, dev);
struct rt8973a_muic_info *info = i2c_get_clientdata(i2c);
disable_irq_wake(info->irq);
return 0;
}
#endif
static SIMPLE_DEV_PM_OPS(rt8973a_muic_pm_ops,
rt8973a_muic_suspend, rt8973a_muic_resume);
static const struct i2c_device_id rt8973a_i2c_id[] = {
{ "rt8973a", TYPE_RT8973A },
{ }
};
MODULE_DEVICE_TABLE(i2c, rt8973a_i2c_id);
static struct i2c_driver rt8973a_muic_i2c_driver = {
.driver = {
.name = "rt8973a",
.owner = THIS_MODULE,
.pm = &rt8973a_muic_pm_ops,
.of_match_table = rt8973a_dt_match,
},
.probe = rt8973a_muic_i2c_probe,
.remove = rt8973a_muic_i2c_remove,
.id_table = rt8973a_i2c_id,
};
static int __init rt8973a_muic_i2c_init(void)
{
return i2c_add_driver(&rt8973a_muic_i2c_driver);
}
subsys_initcall(rt8973a_muic_i2c_init);
MODULE_DESCRIPTION("Richtek RT8973A Extcon driver");
MODULE_AUTHOR("Chanwoo Choi <cw00.choi@samsung.com>");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,203 @@
/*
* rt8973a.h
*
* Copyright (c) 2014 Samsung Electronics Co., Ltd
*
* 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.
*/
#ifndef __LINUX_EXTCON_RT8973A_H
#define __LINUX_EXTCON_RT8973A_H
enum rt8973a_types {
TYPE_RT8973A,
};
/* RT8973A registers */
enum rt8973A_reg {
RT8973A_REG_DEVICE_ID = 0x1,
RT8973A_REG_CONTROL1,
RT8973A_REG_INT1,
RT8973A_REG_INT2,
RT8973A_REG_INTM1,
RT8973A_REG_INTM2,
RT8973A_REG_ADC,
RT8973A_REG_RSVD_1,
RT8973A_REG_RSVD_2,
RT8973A_REG_DEV1,
RT8973A_REG_DEV2,
RT8973A_REG_RSVD_3,
RT8973A_REG_RSVD_4,
RT8973A_REG_RSVD_5,
RT8973A_REG_RSVD_6,
RT8973A_REG_RSVD_7,
RT8973A_REG_RSVD_8,
RT8973A_REG_RSVD_9,
RT8973A_REG_MANUAL_SW1,
RT8973A_REG_MANUAL_SW2,
RT8973A_REG_RSVD_10,
RT8973A_REG_RSVD_11,
RT8973A_REG_RSVD_12,
RT8973A_REG_RSVD_13,
RT8973A_REG_RSVD_14,
RT8973A_REG_RSVD_15,
RT8973A_REG_RESET,
RT8973A_REG_END,
};
/* Define RT8973A MASK/SHIFT constant */
#define RT8973A_REG_DEVICE_ID_VENDOR_SHIFT 0
#define RT8973A_REG_DEVICE_ID_VERSION_SHIFT 3
#define RT8973A_REG_DEVICE_ID_VENDOR_MASK (0x7 << RT8973A_REG_DEVICE_ID_VENDOR_SHIFT)
#define RT8973A_REG_DEVICE_ID_VERSION_MASK (0x1f << RT8973A_REG_DEVICE_ID_VERSION_SHIFT)
#define RT8973A_REG_CONTROL1_INTM_SHIFT 0
#define RT8973A_REG_CONTROL1_AUTO_CONFIG_SHIFT 2
#define RT8973A_REG_CONTROL1_I2C_RST_EN_SHIFT 3
#define RT8973A_REG_CONTROL1_SWITCH_OPEN_SHIFT 4
#define RT8973A_REG_CONTROL1_CHGTYP_SHIFT 5
#define RT8973A_REG_CONTROL1_USB_CHD_EN_SHIFT 6
#define RT8973A_REG_CONTROL1_ADC_EN_SHIFT 7
#define RT8973A_REG_CONTROL1_INTM_MASK (0x1 << RT8973A_REG_CONTROL1_INTM_SHIFT)
#define RT8973A_REG_CONTROL1_AUTO_CONFIG_MASK (0x1 << RT8973A_REG_CONTROL1_AUTO_CONFIG_SHIFT)
#define RT8973A_REG_CONTROL1_I2C_RST_EN_MASK (0x1 << RT8973A_REG_CONTROL1_I2C_RST_EN_SHIFT)
#define RT8973A_REG_CONTROL1_SWITCH_OPEN_MASK (0x1 << RT8973A_REG_CONTROL1_SWITCH_OPEN_SHIFT)
#define RT8973A_REG_CONTROL1_CHGTYP_MASK (0x1 << RT8973A_REG_CONTROL1_CHGTYP_SHIFT)
#define RT8973A_REG_CONTROL1_USB_CHD_EN_MASK (0x1 << RT8973A_REG_CONTROL1_USB_CHD_EN_SHIFT)
#define RT8973A_REG_CONTROL1_ADC_EN_MASK (0x1 << RT8973A_REG_CONTROL1_ADC_EN_SHIFT)
#define RT9873A_REG_INTM1_ATTACH_SHIFT 0
#define RT9873A_REG_INTM1_DETACH_SHIFT 1
#define RT9873A_REG_INTM1_CHGDET_SHIFT 2
#define RT9873A_REG_INTM1_DCD_T_SHIFT 3
#define RT9873A_REG_INTM1_OVP_SHIFT 4
#define RT9873A_REG_INTM1_CONNECT_SHIFT 5
#define RT9873A_REG_INTM1_ADC_CHG_SHIFT 6
#define RT9873A_REG_INTM1_OTP_SHIFT 7
#define RT9873A_REG_INTM1_ATTACH_MASK (0x1 << RT9873A_REG_INTM1_ATTACH_SHIFT)
#define RT9873A_REG_INTM1_DETACH_MASK (0x1 << RT9873A_REG_INTM1_DETACH_SHIFT)
#define RT9873A_REG_INTM1_CHGDET_MASK (0x1 << RT9873A_REG_INTM1_CHGDET_SHIFT)
#define RT9873A_REG_INTM1_DCD_T_MASK (0x1 << RT9873A_REG_INTM1_DCD_T_SHIFT)
#define RT9873A_REG_INTM1_OVP_MASK (0x1 << RT9873A_REG_INTM1_OVP_SHIFT)
#define RT9873A_REG_INTM1_CONNECT_MASK (0x1 << RT9873A_REG_INTM1_CONNECT_SHIFT)
#define RT9873A_REG_INTM1_ADC_CHG_MASK (0x1 << RT9873A_REG_INTM1_ADC_CHG_SHIFT)
#define RT9873A_REG_INTM1_OTP_MASK (0x1 << RT9873A_REG_INTM1_OTP_SHIFT)
#define RT9873A_REG_INTM2_UVLO_SHIFT 1
#define RT9873A_REG_INTM2_POR_SHIFT 2
#define RT9873A_REG_INTM2_OTP_FET_SHIFT 3
#define RT9873A_REG_INTM2_OVP_FET_SHIFT 4
#define RT9873A_REG_INTM2_OCP_LATCH_SHIFT 5
#define RT9873A_REG_INTM2_OCP_SHIFT 6
#define RT9873A_REG_INTM2_OVP_OCP_SHIFT 7
#define RT9873A_REG_INTM2_UVLO_MASK (0x1 << RT9873A_REG_INTM2_UVLO_SHIFT)
#define RT9873A_REG_INTM2_POR_MASK (0x1 << RT9873A_REG_INTM2_POR_SHIFT)
#define RT9873A_REG_INTM2_OTP_FET_MASK (0x1 << RT9873A_REG_INTM2_OTP_FET_SHIFT)
#define RT9873A_REG_INTM2_OVP_FET_MASK (0x1 << RT9873A_REG_INTM2_OVP_FET_SHIFT)
#define RT9873A_REG_INTM2_OCP_LATCH_MASK (0x1 << RT9873A_REG_INTM2_OCP_LATCH_SHIFT)
#define RT9873A_REG_INTM2_OCP_MASK (0x1 << RT9873A_REG_INTM2_OCP_SHIFT)
#define RT9873A_REG_INTM2_OVP_OCP_MASK (0x1 << RT9873A_REG_INTM2_OVP_OCP_SHIFT)
#define RT8973A_REG_ADC_SHIFT 0
#define RT8973A_REG_ADC_MASK (0x1f << RT8973A_REG_ADC_SHIFT)
#define RT8973A_REG_DEV1_OTG_SHIFT 0
#define RT8973A_REG_DEV1_SDP_SHIFT 2
#define RT8973A_REG_DEV1_UART_SHIFT 3
#define RT8973A_REG_DEV1_CAR_KIT_TYPE1_SHIFT 4
#define RT8973A_REG_DEV1_CDPORT_SHIFT 5
#define RT8973A_REG_DEV1_DCPORT_SHIFT 6
#define RT8973A_REG_DEV1_OTG_MASK (0x1 << RT8973A_REG_DEV1_OTG_SHIFT)
#define RT8973A_REG_DEV1_SDP_MASK (0x1 << RT8973A_REG_DEV1_SDP_SHIFT)
#define RT8973A_REG_DEV1_UART_MASK (0x1 << RT8973A_REG_DEV1_UART_SHIFT)
#define RT8973A_REG_DEV1_CAR_KIT_TYPE1_MASK (0x1 << RT8973A_REG_DEV1_CAR_KIT_TYPE1_SHIFT)
#define RT8973A_REG_DEV1_CDPORT_MASK (0x1 << RT8973A_REG_DEV1_CDPORT_SHIFT)
#define RT8973A_REG_DEV1_DCPORT_MASK (0x1 << RT8973A_REG_DEV1_DCPORT_SHIFT)
#define RT8973A_REG_DEV1_USB_MASK (RT8973A_REG_DEV1_SDP_MASK \
| RT8973A_REG_DEV1_CDPORT_MASK)
#define RT8973A_REG_DEV2_JIG_USB_ON_SHIFT 0
#define RT8973A_REG_DEV2_JIG_USB_OFF_SHIFT 1
#define RT8973A_REG_DEV2_JIG_UART_ON_SHIFT 2
#define RT8973A_REG_DEV2_JIG_UART_OFF_SHIFT 3
#define RT8973A_REG_DEV2_JIG_USB_ON_MASK (0x1 << RT8973A_REG_DEV2_JIG_USB_ON_SHIFT)
#define RT8973A_REG_DEV2_JIG_USB_OFF_MASK (0x1 << RT8973A_REG_DEV2_JIG_USB_OFF_SHIFT)
#define RT8973A_REG_DEV2_JIG_UART_ON_MASK (0x1 << RT8973A_REG_DEV2_JIG_UART_ON_SHIFT)
#define RT8973A_REG_DEV2_JIG_UART_OFF_MASK (0x1 << RT8973A_REG_DEV2_JIG_UART_OFF_SHIFT)
#define RT8973A_REG_MANUAL_SW1_DP_SHIFT 2
#define RT8973A_REG_MANUAL_SW1_DM_SHIFT 5
#define RT8973A_REG_MANUAL_SW1_DP_MASK (0x7 << RT8973A_REG_MANUAL_SW1_DP_SHIFT)
#define RT8973A_REG_MANUAL_SW1_DM_MASK (0x7 << RT8973A_REG_MANUAL_SW1_DM_SHIFT)
#define DM_DP_CON_SWITCH_OPEN 0x0
#define DM_DP_CON_SWITCH_USB 0x1
#define DM_DP_CON_SWITCH_UART 0x3
#define DM_DP_SWITCH_OPEN ((DM_DP_CON_SWITCH_OPEN << RT8973A_REG_MANUAL_SW1_DP_SHIFT) \
| (DM_DP_CON_SWITCH_OPEN << RT8973A_REG_MANUAL_SW1_DM_SHIFT))
#define DM_DP_SWITCH_USB ((DM_DP_CON_SWITCH_USB << RT8973A_REG_MANUAL_SW1_DP_SHIFT) \
| (DM_DP_CON_SWITCH_USB << RT8973A_REG_MANUAL_SW1_DM_SHIFT))
#define DM_DP_SWITCH_UART ((DM_DP_CON_SWITCH_UART << RT8973A_REG_MANUAL_SW1_DP_SHIFT) \
| (DM_DP_CON_SWITCH_UART << RT8973A_REG_MANUAL_SW1_DM_SHIFT))
#define RT8973A_REG_MANUAL_SW2_FET_ON_SHIFT 0
#define RT8973A_REG_MANUAL_SW2_JIG_ON_SHIFT 2
#define RT8973A_REG_MANUAL_SW2_BOOT_SW_SHIFT 3
#define RT8973A_REG_MANUAL_SW2_FET_ON_MASK (0x1 << RT8973A_REG_MANUAL_SW2_FET_ON_SHIFT)
#define RT8973A_REG_MANUAL_SW2_JIG_ON_MASK (0x1 << RT8973A_REG_MANUAL_SW2_JIG_ON_SHIFT)
#define RT8973A_REG_MANUAL_SW2_BOOT_SW_MASK (0x1 << RT8973A_REG_MANUAL_SW2_BOOT_SW_SHIFT)
#define RT8973A_REG_MANUAL_SW2_FET_ON 0
#define RT8973A_REG_MANUAL_SW2_FET_OFF 0x1
#define RT8973A_REG_MANUAL_SW2_JIG_OFF 0
#define RT8973A_REG_MANUAL_SW2_JIG_ON 0x1
#define RT8973A_REG_MANUAL_SW2_BOOT_SW_ON 0
#define RT8973A_REG_MANUAL_SW2_BOOT_SW_OFF 0x1
#define RT8973A_REG_RESET_SHIFT 0
#define RT8973A_REG_RESET_MASK (0x1 << RT8973A_REG_RESET_SHIFT)
#define RT8973A_REG_RESET 0x1
/* RT8973A Interrupts */
enum rt8973a_irq {
/* Interrupt1*/
RT8973A_INT1_ATTACH,
RT8973A_INT1_DETACH,
RT8973A_INT1_CHGDET,
RT8973A_INT1_DCD_T,
RT8973A_INT1_OVP,
RT8973A_INT1_CONNECT,
RT8973A_INT1_ADC_CHG,
RT8973A_INT1_OTP,
/* Interrupt2*/
RT8973A_INT2_UVLO,
RT8973A_INT2_POR,
RT8973A_INT2_OTP_FET,
RT8973A_INT2_OVP_FET,
RT8973A_INT2_OCP_LATCH,
RT8973A_INT2_OCP,
RT8973A_INT2_OVP_OCP,
RT8973A_NUM,
};
#define RT8973A_INT1_ATTACH_MASK BIT(0)
#define RT8973A_INT1_DETACH_MASK BIT(1)
#define RT8973A_INT1_CHGDET_MASK BIT(2)
#define RT8973A_INT1_DCD_T_MASK BIT(3)
#define RT8973A_INT1_OVP_MASK BIT(4)
#define RT8973A_INT1_CONNECT_MASK BIT(5)
#define RT8973A_INT1_ADC_CHG_MASK BIT(6)
#define RT8973A_INT1_OTP_MASK BIT(7)
#define RT8973A_INT2_UVLOT_MASK BIT(0)
#define RT8973A_INT2_POR_MASK BIT(1)
#define RT8973A_INT2_OTP_FET_MASK BIT(2)
#define RT8973A_INT2_OVP_FET_MASK BIT(3)
#define RT8973A_INT2_OCP_LATCH_MASK BIT(4)
#define RT8973A_INT2_OCP_MASK BIT(5)
#define RT8973A_INT2_OVP_OCP_MASK BIT(6)
#endif /* __LINUX_EXTCON_RT8973A_H */

View file

@ -0,0 +1,718 @@
/*
* extcon-sm5502.c - Silicon Mitus SM5502 extcon drvier to support USB switches
*
* Copyright (c) 2014 Samsung Electronics Co., Ltd
* Author: Chanwoo Choi <cw00.choi@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.
*/
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/irqdomain.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/extcon.h>
#include "extcon-sm5502.h"
#define DELAY_MS_DEFAULT 17000 /* unit: millisecond */
struct muic_irq {
unsigned int irq;
const char *name;
unsigned int virq;
};
struct reg_data {
u8 reg;
unsigned int val;
bool invert;
};
struct sm5502_muic_info {
struct device *dev;
struct extcon_dev *edev;
struct i2c_client *i2c;
struct regmap *regmap;
struct regmap_irq_chip_data *irq_data;
struct muic_irq *muic_irqs;
unsigned int num_muic_irqs;
int irq;
bool irq_attach;
bool irq_detach;
struct work_struct irq_work;
struct reg_data *reg_data;
unsigned int num_reg_data;
struct mutex mutex;
/*
* Use delayed workqueue to detect cable state and then
* notify cable state to notifiee/platform through uevent.
* After completing the booting of platform, the extcon provider
* driver should notify cable state to upper layer.
*/
struct delayed_work wq_detcable;
};
/* Default value of SM5502 register to bring up MUIC device. */
static struct reg_data sm5502_reg_data[] = {
{
.reg = SM5502_REG_CONTROL,
.val = SM5502_REG_CONTROL_MASK_INT_MASK,
.invert = false,
}, {
.reg = SM5502_REG_INTMASK1,
.val = SM5502_REG_INTM1_KP_MASK
| SM5502_REG_INTM1_LKP_MASK
| SM5502_REG_INTM1_LKR_MASK,
.invert = true,
}, {
.reg = SM5502_REG_INTMASK2,
.val = SM5502_REG_INTM2_VBUS_DET_MASK
| SM5502_REG_INTM2_REV_ACCE_MASK
| SM5502_REG_INTM2_ADC_CHG_MASK
| SM5502_REG_INTM2_STUCK_KEY_MASK
| SM5502_REG_INTM2_STUCK_KEY_RCV_MASK
| SM5502_REG_INTM2_MHL_MASK,
.invert = true,
},
{ }
};
/* List of detectable cables */
enum {
EXTCON_CABLE_USB = 0,
EXTCON_CABLE_USB_HOST,
EXTCON_CABLE_TA,
EXTCON_CABLE_END,
};
static const char *sm5502_extcon_cable[] = {
[EXTCON_CABLE_USB] = "USB",
[EXTCON_CABLE_USB_HOST] = "USB-Host",
[EXTCON_CABLE_TA] = "TA",
NULL,
};
/* Define supported accessory type */
enum sm5502_muic_acc_type {
SM5502_MUIC_ADC_GROUND = 0x0,
SM5502_MUIC_ADC_SEND_END_BUTTON,
SM5502_MUIC_ADC_REMOTE_S1_BUTTON,
SM5502_MUIC_ADC_REMOTE_S2_BUTTON,
SM5502_MUIC_ADC_REMOTE_S3_BUTTON,
SM5502_MUIC_ADC_REMOTE_S4_BUTTON,
SM5502_MUIC_ADC_REMOTE_S5_BUTTON,
SM5502_MUIC_ADC_REMOTE_S6_BUTTON,
SM5502_MUIC_ADC_REMOTE_S7_BUTTON,
SM5502_MUIC_ADC_REMOTE_S8_BUTTON,
SM5502_MUIC_ADC_REMOTE_S9_BUTTON,
SM5502_MUIC_ADC_REMOTE_S10_BUTTON,
SM5502_MUIC_ADC_REMOTE_S11_BUTTON,
SM5502_MUIC_ADC_REMOTE_S12_BUTTON,
SM5502_MUIC_ADC_RESERVED_ACC_1,
SM5502_MUIC_ADC_RESERVED_ACC_2,
SM5502_MUIC_ADC_RESERVED_ACC_3,
SM5502_MUIC_ADC_RESERVED_ACC_4,
SM5502_MUIC_ADC_RESERVED_ACC_5,
SM5502_MUIC_ADC_AUDIO_TYPE2,
SM5502_MUIC_ADC_PHONE_POWERED_DEV,
SM5502_MUIC_ADC_TTY_CONVERTER,
SM5502_MUIC_ADC_UART_CABLE,
SM5502_MUIC_ADC_TYPE1_CHARGER,
SM5502_MUIC_ADC_FACTORY_MODE_BOOT_OFF_USB,
SM5502_MUIC_ADC_FACTORY_MODE_BOOT_ON_USB,
SM5502_MUIC_ADC_AUDIO_VIDEO_CABLE,
SM5502_MUIC_ADC_TYPE2_CHARGER,
SM5502_MUIC_ADC_FACTORY_MODE_BOOT_OFF_UART,
SM5502_MUIC_ADC_FACTORY_MODE_BOOT_ON_UART,
SM5502_MUIC_ADC_AUDIO_TYPE1,
SM5502_MUIC_ADC_OPEN = 0x1f,
/* The below accessories have same ADC value (0x1f or 0x1e).
So, Device type1 is used to separate specific accessory. */
/* |---------|--ADC| */
/* | [7:5]|[4:0]| */
SM5502_MUIC_ADC_AUDIO_TYPE1_FULL_REMOTE = 0x3e, /* | 001|11110| */
SM5502_MUIC_ADC_AUDIO_TYPE1_SEND_END = 0x5e, /* | 010|11110| */
/* |Dev Type1|--ADC| */
SM5502_MUIC_ADC_OPEN_USB = 0x5f, /* | 010|11111| */
SM5502_MUIC_ADC_OPEN_TA = 0xdf, /* | 110|11111| */
SM5502_MUIC_ADC_OPEN_USB_OTG = 0xff, /* | 111|11111| */
};
/* List of supported interrupt for SM5502 */
static struct muic_irq sm5502_muic_irqs[] = {
{ SM5502_IRQ_INT1_ATTACH, "muic-attach" },
{ SM5502_IRQ_INT1_DETACH, "muic-detach" },
{ SM5502_IRQ_INT1_KP, "muic-kp" },
{ SM5502_IRQ_INT1_LKP, "muic-lkp" },
{ SM5502_IRQ_INT1_LKR, "muic-lkr" },
{ SM5502_IRQ_INT1_OVP_EVENT, "muic-ovp-event" },
{ SM5502_IRQ_INT1_OCP_EVENT, "muic-ocp-event" },
{ SM5502_IRQ_INT1_OVP_OCP_DIS, "muic-ovp-ocp-dis" },
{ SM5502_IRQ_INT2_VBUS_DET, "muic-vbus-det" },
{ SM5502_IRQ_INT2_REV_ACCE, "muic-rev-acce" },
{ SM5502_IRQ_INT2_ADC_CHG, "muic-adc-chg" },
{ SM5502_IRQ_INT2_STUCK_KEY, "muic-stuck-key" },
{ SM5502_IRQ_INT2_STUCK_KEY_RCV, "muic-stuck-key-rcv" },
{ SM5502_IRQ_INT2_MHL, "muic-mhl" },
};
/* Define interrupt list of SM5502 to register regmap_irq */
static const struct regmap_irq sm5502_irqs[] = {
/* INT1 interrupts */
{ .reg_offset = 0, .mask = SM5502_IRQ_INT1_ATTACH_MASK, },
{ .reg_offset = 0, .mask = SM5502_IRQ_INT1_DETACH_MASK, },
{ .reg_offset = 0, .mask = SM5502_IRQ_INT1_KP_MASK, },
{ .reg_offset = 0, .mask = SM5502_IRQ_INT1_LKP_MASK, },
{ .reg_offset = 0, .mask = SM5502_IRQ_INT1_LKR_MASK, },
{ .reg_offset = 0, .mask = SM5502_IRQ_INT1_OVP_EVENT_MASK, },
{ .reg_offset = 0, .mask = SM5502_IRQ_INT1_OCP_EVENT_MASK, },
{ .reg_offset = 0, .mask = SM5502_IRQ_INT1_OVP_OCP_DIS_MASK, },
/* INT2 interrupts */
{ .reg_offset = 1, .mask = SM5502_IRQ_INT2_VBUS_DET_MASK,},
{ .reg_offset = 1, .mask = SM5502_IRQ_INT2_REV_ACCE_MASK, },
{ .reg_offset = 1, .mask = SM5502_IRQ_INT2_ADC_CHG_MASK, },
{ .reg_offset = 1, .mask = SM5502_IRQ_INT2_STUCK_KEY_MASK, },
{ .reg_offset = 1, .mask = SM5502_IRQ_INT2_STUCK_KEY_RCV_MASK, },
{ .reg_offset = 1, .mask = SM5502_IRQ_INT2_MHL_MASK, },
};
static const struct regmap_irq_chip sm5502_muic_irq_chip = {
.name = "sm5502",
.status_base = SM5502_REG_INT1,
.mask_base = SM5502_REG_INTMASK1,
.mask_invert = false,
.num_regs = 2,
.irqs = sm5502_irqs,
.num_irqs = ARRAY_SIZE(sm5502_irqs),
};
/* Define regmap configuration of SM5502 for I2C communication */
static bool sm5502_muic_volatile_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case SM5502_REG_INTMASK1:
case SM5502_REG_INTMASK2:
return true;
default:
break;
}
return false;
}
static const struct regmap_config sm5502_muic_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.volatile_reg = sm5502_muic_volatile_reg,
.max_register = SM5502_REG_END,
};
/* Change DM_CON/DP_CON/VBUSIN switch according to cable type */
static int sm5502_muic_set_path(struct sm5502_muic_info *info,
unsigned int con_sw, unsigned int vbus_sw,
bool attached)
{
int ret;
if (!attached) {
con_sw = DM_DP_SWITCH_OPEN;
vbus_sw = VBUSIN_SWITCH_OPEN;
}
switch (con_sw) {
case DM_DP_SWITCH_OPEN:
case DM_DP_SWITCH_USB:
case DM_DP_SWITCH_AUDIO:
case DM_DP_SWITCH_UART:
ret = regmap_update_bits(info->regmap, SM5502_REG_MANUAL_SW1,
SM5502_REG_MANUAL_SW1_DP_MASK |
SM5502_REG_MANUAL_SW1_DM_MASK,
con_sw);
if (ret < 0) {
dev_err(info->dev,
"cannot update DM_CON/DP_CON switch\n");
return ret;
}
break;
default:
dev_err(info->dev, "Unknown DM_CON/DP_CON switch type (%d)\n",
con_sw);
return -EINVAL;
};
switch (vbus_sw) {
case VBUSIN_SWITCH_OPEN:
case VBUSIN_SWITCH_VBUSOUT:
case VBUSIN_SWITCH_MIC:
case VBUSIN_SWITCH_VBUSOUT_WITH_USB:
ret = regmap_update_bits(info->regmap, SM5502_REG_MANUAL_SW1,
SM5502_REG_MANUAL_SW1_VBUSIN_MASK,
vbus_sw);
if (ret < 0) {
dev_err(info->dev,
"cannot update VBUSIN switch\n");
return ret;
}
break;
default:
dev_err(info->dev, "Unknown VBUS switch type (%d)\n", vbus_sw);
return -EINVAL;
};
return 0;
}
/* Return cable type of attached or detached accessories */
static unsigned int sm5502_muic_get_cable_type(struct sm5502_muic_info *info)
{
unsigned int cable_type = -1, adc, dev_type1;
int ret;
/* Read ADC value according to external cable or button */
ret = regmap_read(info->regmap, SM5502_REG_ADC, &adc);
if (ret) {
dev_err(info->dev, "failed to read ADC register\n");
return ret;
}
/*
* If ADC is SM5502_MUIC_ADC_GROUND(0x0), external cable hasn't
* connected with to MUIC device.
*/
cable_type = adc & SM5502_REG_ADC_MASK;
if (cable_type == SM5502_MUIC_ADC_GROUND)
return SM5502_MUIC_ADC_GROUND;
switch (cable_type) {
case SM5502_MUIC_ADC_GROUND:
case SM5502_MUIC_ADC_SEND_END_BUTTON:
case SM5502_MUIC_ADC_REMOTE_S1_BUTTON:
case SM5502_MUIC_ADC_REMOTE_S2_BUTTON:
case SM5502_MUIC_ADC_REMOTE_S3_BUTTON:
case SM5502_MUIC_ADC_REMOTE_S4_BUTTON:
case SM5502_MUIC_ADC_REMOTE_S5_BUTTON:
case SM5502_MUIC_ADC_REMOTE_S6_BUTTON:
case SM5502_MUIC_ADC_REMOTE_S7_BUTTON:
case SM5502_MUIC_ADC_REMOTE_S8_BUTTON:
case SM5502_MUIC_ADC_REMOTE_S9_BUTTON:
case SM5502_MUIC_ADC_REMOTE_S10_BUTTON:
case SM5502_MUIC_ADC_REMOTE_S11_BUTTON:
case SM5502_MUIC_ADC_REMOTE_S12_BUTTON:
case SM5502_MUIC_ADC_RESERVED_ACC_1:
case SM5502_MUIC_ADC_RESERVED_ACC_2:
case SM5502_MUIC_ADC_RESERVED_ACC_3:
case SM5502_MUIC_ADC_RESERVED_ACC_4:
case SM5502_MUIC_ADC_RESERVED_ACC_5:
case SM5502_MUIC_ADC_AUDIO_TYPE2:
case SM5502_MUIC_ADC_PHONE_POWERED_DEV:
case SM5502_MUIC_ADC_TTY_CONVERTER:
case SM5502_MUIC_ADC_UART_CABLE:
case SM5502_MUIC_ADC_TYPE1_CHARGER:
case SM5502_MUIC_ADC_FACTORY_MODE_BOOT_OFF_USB:
case SM5502_MUIC_ADC_FACTORY_MODE_BOOT_ON_USB:
case SM5502_MUIC_ADC_AUDIO_VIDEO_CABLE:
case SM5502_MUIC_ADC_TYPE2_CHARGER:
case SM5502_MUIC_ADC_FACTORY_MODE_BOOT_OFF_UART:
case SM5502_MUIC_ADC_FACTORY_MODE_BOOT_ON_UART:
break;
case SM5502_MUIC_ADC_AUDIO_TYPE1:
/*
* Check whether cable type is
* SM5502_MUIC_ADC_AUDIO_TYPE1_FULL_REMOTE
* or SM5502_MUIC_ADC_AUDIO_TYPE1_SEND_END
* by using Button event.
*/
break;
case SM5502_MUIC_ADC_OPEN:
ret = regmap_read(info->regmap, SM5502_REG_DEV_TYPE1,
&dev_type1);
if (ret) {
dev_err(info->dev, "failed to read DEV_TYPE1 reg\n");
return ret;
}
switch (dev_type1) {
case SM5502_REG_DEV_TYPE1_USB_SDP_MASK:
cable_type = SM5502_MUIC_ADC_OPEN_USB;
break;
case SM5502_REG_DEV_TYPE1_DEDICATED_CHG_MASK:
cable_type = SM5502_MUIC_ADC_OPEN_TA;
break;
case SM5502_REG_DEV_TYPE1_USB_OTG_MASK:
cable_type = SM5502_MUIC_ADC_OPEN_USB_OTG;
break;
default:
dev_dbg(info->dev,
"cannot identify the cable type: adc(0x%x) "
"dev_type1(0x%x)\n", adc, dev_type1);
return -EINVAL;
};
break;
default:
dev_err(info->dev,
"failed to identify the cable type: adc(0x%x)\n", adc);
return -EINVAL;
};
return cable_type;
}
static int sm5502_muic_cable_handler(struct sm5502_muic_info *info,
bool attached)
{
static unsigned int prev_cable_type = SM5502_MUIC_ADC_GROUND;
const char **cable_names = info->edev->supported_cable;
unsigned int cable_type = SM5502_MUIC_ADC_GROUND;
unsigned int con_sw = DM_DP_SWITCH_OPEN;
unsigned int vbus_sw = VBUSIN_SWITCH_OPEN;
unsigned int idx = 0;
int ret;
if (!cable_names)
return 0;
/* Get the type of attached or detached cable */
if (attached)
cable_type = sm5502_muic_get_cable_type(info);
else
cable_type = prev_cable_type;
prev_cable_type = cable_type;
switch (cable_type) {
case SM5502_MUIC_ADC_OPEN_USB:
idx = EXTCON_CABLE_USB;
con_sw = DM_DP_SWITCH_USB;
vbus_sw = VBUSIN_SWITCH_VBUSOUT_WITH_USB;
break;
case SM5502_MUIC_ADC_OPEN_TA:
idx = EXTCON_CABLE_TA;
con_sw = DM_DP_SWITCH_OPEN;
vbus_sw = VBUSIN_SWITCH_VBUSOUT;
break;
case SM5502_MUIC_ADC_OPEN_USB_OTG:
idx = EXTCON_CABLE_USB_HOST;
con_sw = DM_DP_SWITCH_USB;
vbus_sw = VBUSIN_SWITCH_OPEN;
break;
default:
dev_dbg(info->dev,
"cannot handle this cable_type (0x%x)\n", cable_type);
return 0;
};
/* Change internal hardware path(DM_CON/DP_CON, VBUSIN) */
ret = sm5502_muic_set_path(info, con_sw, vbus_sw, attached);
if (ret < 0)
return ret;
/* Change the state of external accessory */
extcon_set_cable_state(info->edev, cable_names[idx], attached);
return 0;
}
static void sm5502_muic_irq_work(struct work_struct *work)
{
struct sm5502_muic_info *info = container_of(work,
struct sm5502_muic_info, irq_work);
int ret = 0;
if (!info->edev)
return;
mutex_lock(&info->mutex);
/* Detect attached or detached cables */
if (info->irq_attach) {
ret = sm5502_muic_cable_handler(info, true);
info->irq_attach = false;
}
if (info->irq_detach) {
ret = sm5502_muic_cable_handler(info, false);
info->irq_detach = false;
}
if (ret < 0)
dev_err(info->dev, "failed to handle MUIC interrupt\n");
mutex_unlock(&info->mutex);
}
/*
* Sets irq_attach or irq_detach in sm5502_muic_info and returns 0.
* Returns -ESRCH if irq_type does not match registered IRQ for this dev type.
*/
static int sm5502_parse_irq(struct sm5502_muic_info *info, int irq_type)
{
switch (irq_type) {
case SM5502_IRQ_INT1_ATTACH:
info->irq_attach = true;
break;
case SM5502_IRQ_INT1_DETACH:
info->irq_detach = true;
break;
case SM5502_IRQ_INT1_KP:
case SM5502_IRQ_INT1_LKP:
case SM5502_IRQ_INT1_LKR:
case SM5502_IRQ_INT1_OVP_EVENT:
case SM5502_IRQ_INT1_OCP_EVENT:
case SM5502_IRQ_INT1_OVP_OCP_DIS:
case SM5502_IRQ_INT2_VBUS_DET:
case SM5502_IRQ_INT2_REV_ACCE:
case SM5502_IRQ_INT2_ADC_CHG:
case SM5502_IRQ_INT2_STUCK_KEY:
case SM5502_IRQ_INT2_STUCK_KEY_RCV:
case SM5502_IRQ_INT2_MHL:
default:
break;
}
return 0;
}
static irqreturn_t sm5502_muic_irq_handler(int irq, void *data)
{
struct sm5502_muic_info *info = data;
int i, irq_type = -1, ret;
for (i = 0; i < info->num_muic_irqs; i++)
if (irq == info->muic_irqs[i].virq)
irq_type = info->muic_irqs[i].irq;
ret = sm5502_parse_irq(info, irq_type);
if (ret < 0) {
dev_warn(info->dev, "cannot handle is interrupt:%d\n",
irq_type);
return IRQ_HANDLED;
}
schedule_work(&info->irq_work);
return IRQ_HANDLED;
}
static void sm5502_muic_detect_cable_wq(struct work_struct *work)
{
struct sm5502_muic_info *info = container_of(to_delayed_work(work),
struct sm5502_muic_info, wq_detcable);
int ret;
/* Notify the state of connector cable or not */
ret = sm5502_muic_cable_handler(info, true);
if (ret < 0)
dev_warn(info->dev, "failed to detect cable state\n");
}
static void sm5502_init_dev_type(struct sm5502_muic_info *info)
{
unsigned int reg_data, vendor_id, version_id;
int i, ret;
/* To test I2C, Print version_id and vendor_id of SM5502 */
ret = regmap_read(info->regmap, SM5502_REG_DEVICE_ID, &reg_data);
if (ret) {
dev_err(info->dev,
"failed to read DEVICE_ID register: %d\n", ret);
return;
}
vendor_id = ((reg_data & SM5502_REG_DEVICE_ID_VENDOR_MASK) >>
SM5502_REG_DEVICE_ID_VENDOR_SHIFT);
version_id = ((reg_data & SM5502_REG_DEVICE_ID_VERSION_MASK) >>
SM5502_REG_DEVICE_ID_VERSION_SHIFT);
dev_info(info->dev, "Device type: version: 0x%x, vendor: 0x%x\n",
version_id, vendor_id);
/* Initiazle the register of SM5502 device to bring-up */
for (i = 0; i < info->num_reg_data; i++) {
unsigned int val = 0;
if (!info->reg_data[i].invert)
val |= ~info->reg_data[i].val;
else
val = info->reg_data[i].val;
regmap_write(info->regmap, info->reg_data[i].reg, val);
}
}
static int sm5022_muic_i2c_probe(struct i2c_client *i2c,
const struct i2c_device_id *id)
{
struct device_node *np = i2c->dev.of_node;
struct sm5502_muic_info *info;
int i, ret, irq_flags;
if (!np)
return -EINVAL;
info = devm_kzalloc(&i2c->dev, sizeof(*info), GFP_KERNEL);
if (!info)
return -ENOMEM;
i2c_set_clientdata(i2c, info);
info->dev = &i2c->dev;
info->i2c = i2c;
info->irq = i2c->irq;
info->muic_irqs = sm5502_muic_irqs;
info->num_muic_irqs = ARRAY_SIZE(sm5502_muic_irqs);
info->reg_data = sm5502_reg_data;
info->num_reg_data = ARRAY_SIZE(sm5502_reg_data);
mutex_init(&info->mutex);
INIT_WORK(&info->irq_work, sm5502_muic_irq_work);
info->regmap = devm_regmap_init_i2c(i2c, &sm5502_muic_regmap_config);
if (IS_ERR(info->regmap)) {
ret = PTR_ERR(info->regmap);
dev_err(info->dev, "failed to allocate register map: %d\n",
ret);
return ret;
}
/* Support irq domain for SM5502 MUIC device */
irq_flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT | IRQF_SHARED;
ret = regmap_add_irq_chip(info->regmap, info->irq, irq_flags, 0,
&sm5502_muic_irq_chip, &info->irq_data);
if (ret != 0) {
dev_err(info->dev, "failed to request IRQ %d: %d\n",
info->irq, ret);
return ret;
}
for (i = 0; i < info->num_muic_irqs; i++) {
struct muic_irq *muic_irq = &info->muic_irqs[i];
unsigned int virq = 0;
virq = regmap_irq_get_virq(info->irq_data, muic_irq->irq);
if (virq <= 0)
return -EINVAL;
muic_irq->virq = virq;
ret = devm_request_threaded_irq(info->dev, virq, NULL,
sm5502_muic_irq_handler,
IRQF_NO_SUSPEND,
muic_irq->name, info);
if (ret) {
dev_err(info->dev,
"failed: irq request (IRQ: %d, error :%d)\n",
muic_irq->irq, ret);
return ret;
}
}
/* Allocate extcon device */
info->edev = devm_extcon_dev_allocate(info->dev, sm5502_extcon_cable);
if (IS_ERR(info->edev)) {
dev_err(info->dev, "failed to allocate memory for extcon\n");
return -ENOMEM;
}
info->edev->name = np->name;
/* Register extcon device */
ret = devm_extcon_dev_register(info->dev, info->edev);
if (ret) {
dev_err(info->dev, "failed to register extcon device\n");
return ret;
}
/*
* Detect accessory after completing the initialization of platform
*
* - Use delayed workqueue to detect cable state and then
* notify cable state to notifiee/platform through uevent.
* After completing the booting of platform, the extcon provider
* driver should notify cable state to upper layer.
*/
INIT_DELAYED_WORK(&info->wq_detcable, sm5502_muic_detect_cable_wq);
queue_delayed_work(system_power_efficient_wq, &info->wq_detcable,
msecs_to_jiffies(DELAY_MS_DEFAULT));
/* Initialize SM5502 device and print vendor id and version id */
sm5502_init_dev_type(info);
return 0;
}
static int sm5502_muic_i2c_remove(struct i2c_client *i2c)
{
struct sm5502_muic_info *info = i2c_get_clientdata(i2c);
regmap_del_irq_chip(info->irq, info->irq_data);
return 0;
}
static struct of_device_id sm5502_dt_match[] = {
{ .compatible = "siliconmitus,sm5502-muic" },
{ },
};
#ifdef CONFIG_PM_SLEEP
static int sm5502_muic_suspend(struct device *dev)
{
struct i2c_client *i2c = container_of(dev, struct i2c_client, dev);
struct sm5502_muic_info *info = i2c_get_clientdata(i2c);
enable_irq_wake(info->irq);
return 0;
}
static int sm5502_muic_resume(struct device *dev)
{
struct i2c_client *i2c = container_of(dev, struct i2c_client, dev);
struct sm5502_muic_info *info = i2c_get_clientdata(i2c);
disable_irq_wake(info->irq);
return 0;
}
#endif
static SIMPLE_DEV_PM_OPS(sm5502_muic_pm_ops,
sm5502_muic_suspend, sm5502_muic_resume);
static const struct i2c_device_id sm5502_i2c_id[] = {
{ "sm5502", TYPE_SM5502 },
{ }
};
MODULE_DEVICE_TABLE(i2c, sm5502_i2c_id);
static struct i2c_driver sm5502_muic_i2c_driver = {
.driver = {
.name = "sm5502",
.owner = THIS_MODULE,
.pm = &sm5502_muic_pm_ops,
.of_match_table = sm5502_dt_match,
},
.probe = sm5022_muic_i2c_probe,
.remove = sm5502_muic_i2c_remove,
.id_table = sm5502_i2c_id,
};
static int __init sm5502_muic_i2c_init(void)
{
return i2c_add_driver(&sm5502_muic_i2c_driver);
}
subsys_initcall(sm5502_muic_i2c_init);
MODULE_DESCRIPTION("Silicon Mitus SM5502 Extcon driver");
MODULE_AUTHOR("Chanwoo Choi <cw00.choi@samsung.com>");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,282 @@
/*
* sm5502.h
*
* Copyright (c) 2014 Samsung Electronics Co., Ltd
*
* 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.
*/
#ifndef __LINUX_EXTCON_SM5502_H
#define __LINUX_EXTCON_SM5502_H
enum sm5502_types {
TYPE_SM5502,
};
/* SM5502 registers */
enum sm5502_reg {
SM5502_REG_DEVICE_ID = 0x01,
SM5502_REG_CONTROL,
SM5502_REG_INT1,
SM5502_REG_INT2,
SM5502_REG_INTMASK1,
SM5502_REG_INTMASK2,
SM5502_REG_ADC,
SM5502_REG_TIMING_SET1,
SM5502_REG_TIMING_SET2,
SM5502_REG_DEV_TYPE1,
SM5502_REG_DEV_TYPE2,
SM5502_REG_BUTTON1,
SM5502_REG_BUTTON2,
SM5502_REG_CAR_KIT_STATUS,
SM5502_REG_RSVD1,
SM5502_REG_RSVD2,
SM5502_REG_RSVD3,
SM5502_REG_RSVD4,
SM5502_REG_MANUAL_SW1,
SM5502_REG_MANUAL_SW2,
SM5502_REG_DEV_TYPE3,
SM5502_REG_RSVD5,
SM5502_REG_RSVD6,
SM5502_REG_RSVD7,
SM5502_REG_RSVD8,
SM5502_REG_RSVD9,
SM5502_REG_RESET,
SM5502_REG_RSVD10,
SM5502_REG_RESERVED_ID1,
SM5502_REG_RSVD11,
SM5502_REG_RSVD12,
SM5502_REG_RESERVED_ID2,
SM5502_REG_RSVD13,
SM5502_REG_OCP,
SM5502_REG_RSVD14,
SM5502_REG_RSVD15,
SM5502_REG_RSVD16,
SM5502_REG_RSVD17,
SM5502_REG_RSVD18,
SM5502_REG_RSVD19,
SM5502_REG_RSVD20,
SM5502_REG_RSVD21,
SM5502_REG_RSVD22,
SM5502_REG_RSVD23,
SM5502_REG_RSVD24,
SM5502_REG_RSVD25,
SM5502_REG_RSVD26,
SM5502_REG_RSVD27,
SM5502_REG_RSVD28,
SM5502_REG_RSVD29,
SM5502_REG_RSVD30,
SM5502_REG_RSVD31,
SM5502_REG_RSVD32,
SM5502_REG_RSVD33,
SM5502_REG_RSVD34,
SM5502_REG_RSVD35,
SM5502_REG_RSVD36,
SM5502_REG_RESERVED_ID3,
SM5502_REG_END,
};
/* Define SM5502 MASK/SHIFT constant */
#define SM5502_REG_DEVICE_ID_VENDOR_SHIFT 0
#define SM5502_REG_DEVICE_ID_VERSION_SHIFT 3
#define SM5502_REG_DEVICE_ID_VENDOR_MASK (0x3 << SM5502_REG_DEVICE_ID_VENDOR_SHIFT)
#define SM5502_REG_DEVICE_ID_VERSION_MASK (0x1f << SM5502_REG_DEVICE_ID_VERSION_SHIFT)
#define SM5502_REG_CONTROL_MASK_INT_SHIFT 0
#define SM5502_REG_CONTROL_WAIT_SHIFT 1
#define SM5502_REG_CONTROL_MANUAL_SW_SHIFT 2
#define SM5502_REG_CONTROL_RAW_DATA_SHIFT 3
#define SM5502_REG_CONTROL_SW_OPEN_SHIFT 4
#define SM5502_REG_CONTROL_MASK_INT_MASK (0x1 << SM5502_REG_CONTROL_MASK_INT_SHIFT)
#define SM5502_REG_CONTROL_WAIT_MASK (0x1 << SM5502_REG_CONTROL_WAIT_SHIFT)
#define SM5502_REG_CONTROL_MANUAL_SW_MASK (0x1 << SM5502_REG_CONTROL_MANUAL_SW_SHIFT)
#define SM5502_REG_CONTROL_RAW_DATA_MASK (0x1 << SM5502_REG_CONTROL_RAW_DATA_SHIFT)
#define SM5502_REG_CONTROL_SW_OPEN_MASK (0x1 << SM5502_REG_CONTROL_SW_OPEN_SHIFT)
#define SM5502_REG_INTM1_ATTACH_SHIFT 0
#define SM5502_REG_INTM1_DETACH_SHIFT 1
#define SM5502_REG_INTM1_KP_SHIFT 2
#define SM5502_REG_INTM1_LKP_SHIFT 3
#define SM5502_REG_INTM1_LKR_SHIFT 4
#define SM5502_REG_INTM1_OVP_EVENT_SHIFT 5
#define SM5502_REG_INTM1_OCP_EVENT_SHIFT 6
#define SM5502_REG_INTM1_OVP_OCP_DIS_SHIFT 7
#define SM5502_REG_INTM1_ATTACH_MASK (0x1 << SM5502_REG_INTM1_ATTACH_SHIFT)
#define SM5502_REG_INTM1_DETACH_MASK (0x1 << SM5502_REG_INTM1_DETACH_SHIFT)
#define SM5502_REG_INTM1_KP_MASK (0x1 << SM5502_REG_INTM1_KP_SHIFT)
#define SM5502_REG_INTM1_LKP_MASK (0x1 << SM5502_REG_INTM1_LKP_SHIFT)
#define SM5502_REG_INTM1_LKR_MASK (0x1 << SM5502_REG_INTM1_LKR_SHIFT)
#define SM5502_REG_INTM1_OVP_EVENT_MASK (0x1 << SM5502_REG_INTM1_OVP_EVENT_SHIFT)
#define SM5502_REG_INTM1_OCP_EVENT_MASK (0x1 << SM5502_REG_INTM1_OCP_EVENT_SHIFT)
#define SM5502_REG_INTM1_OVP_OCP_DIS_MASK (0x1 << SM5502_REG_INTM1_OVP_OCP_DIS_SHIFT)
#define SM5502_REG_INTM2_VBUS_DET_SHIFT 0
#define SM5502_REG_INTM2_REV_ACCE_SHIFT 1
#define SM5502_REG_INTM2_ADC_CHG_SHIFT 2
#define SM5502_REG_INTM2_STUCK_KEY_SHIFT 3
#define SM5502_REG_INTM2_STUCK_KEY_RCV_SHIFT 4
#define SM5502_REG_INTM2_MHL_SHIFT 5
#define SM5502_REG_INTM2_VBUS_DET_MASK (0x1 << SM5502_REG_INTM2_VBUS_DET_SHIFT)
#define SM5502_REG_INTM2_REV_ACCE_MASK (0x1 << SM5502_REG_INTM2_REV_ACCE_SHIFT)
#define SM5502_REG_INTM2_ADC_CHG_MASK (0x1 << SM5502_REG_INTM2_ADC_CHG_SHIFT)
#define SM5502_REG_INTM2_STUCK_KEY_MASK (0x1 << SM5502_REG_INTM2_STUCK_KEY_SHIFT)
#define SM5502_REG_INTM2_STUCK_KEY_RCV_MASK (0x1 << SM5502_REG_INTM2_STUCK_KEY_RCV_SHIFT)
#define SM5502_REG_INTM2_MHL_MASK (0x1 << SM5502_REG_INTM2_MHL_SHIFT)
#define SM5502_REG_ADC_SHIFT 0
#define SM5502_REG_ADC_MASK (0x1f << SM5502_REG_ADC_SHIFT)
#define SM5502_REG_TIMING_SET1_KEY_PRESS_SHIFT 4
#define SM5502_REG_TIMING_SET1_KEY_PRESS_MASK (0xf << SM5502_REG_TIMING_SET1_KEY_PRESS_SHIFT)
#define TIMING_KEY_PRESS_100MS 0x0
#define TIMING_KEY_PRESS_200MS 0x1
#define TIMING_KEY_PRESS_300MS 0x2
#define TIMING_KEY_PRESS_400MS 0x3
#define TIMING_KEY_PRESS_500MS 0x4
#define TIMING_KEY_PRESS_600MS 0x5
#define TIMING_KEY_PRESS_700MS 0x6
#define TIMING_KEY_PRESS_800MS 0x7
#define TIMING_KEY_PRESS_900MS 0x8
#define TIMING_KEY_PRESS_1000MS 0x9
#define SM5502_REG_TIMING_SET1_ADC_DET_SHIFT 0
#define SM5502_REG_TIMING_SET1_ADC_DET_MASK (0xf << SM5502_REG_TIMING_SET1_ADC_DET_SHIFT)
#define TIMING_ADC_DET_50MS 0x0
#define TIMING_ADC_DET_100MS 0x1
#define TIMING_ADC_DET_150MS 0x2
#define TIMING_ADC_DET_200MS 0x3
#define TIMING_ADC_DET_300MS 0x4
#define TIMING_ADC_DET_400MS 0x5
#define TIMING_ADC_DET_500MS 0x6
#define TIMING_ADC_DET_600MS 0x7
#define TIMING_ADC_DET_700MS 0x8
#define TIMING_ADC_DET_800MS 0x9
#define TIMING_ADC_DET_900MS 0xA
#define TIMING_ADC_DET_1000MS 0xB
#define SM5502_REG_TIMING_SET2_SW_WAIT_SHIFT 4
#define SM5502_REG_TIMING_SET2_SW_WAIT_MASK (0xf << SM5502_REG_TIMING_SET2_SW_WAIT_SHIFT)
#define TIMING_SW_WAIT_10MS 0x0
#define TIMING_SW_WAIT_30MS 0x1
#define TIMING_SW_WAIT_50MS 0x2
#define TIMING_SW_WAIT_70MS 0x3
#define TIMING_SW_WAIT_90MS 0x4
#define TIMING_SW_WAIT_110MS 0x5
#define TIMING_SW_WAIT_130MS 0x6
#define TIMING_SW_WAIT_150MS 0x7
#define TIMING_SW_WAIT_170MS 0x8
#define TIMING_SW_WAIT_190MS 0x9
#define TIMING_SW_WAIT_210MS 0xA
#define SM5502_REG_TIMING_SET2_LONG_KEY_SHIFT 0
#define SM5502_REG_TIMING_SET2_LONG_KEY_MASK (0xf << SM5502_REG_TIMING_SET2_LONG_KEY_SHIFT)
#define TIMING_LONG_KEY_300MS 0x0
#define TIMING_LONG_KEY_400MS 0x1
#define TIMING_LONG_KEY_500MS 0x2
#define TIMING_LONG_KEY_600MS 0x3
#define TIMING_LONG_KEY_700MS 0x4
#define TIMING_LONG_KEY_800MS 0x5
#define TIMING_LONG_KEY_900MS 0x6
#define TIMING_LONG_KEY_1000MS 0x7
#define TIMING_LONG_KEY_1100MS 0x8
#define TIMING_LONG_KEY_1200MS 0x9
#define TIMING_LONG_KEY_1300MS 0xA
#define TIMING_LONG_KEY_1400MS 0xB
#define TIMING_LONG_KEY_1500MS 0xC
#define SM5502_REG_DEV_TYPE1_AUDIO_TYPE1_SHIFT 0
#define SM5502_REG_DEV_TYPE1_AUDIO_TYPE2_SHIFT 1
#define SM5502_REG_DEV_TYPE1_USB_SDP_SHIFT 2
#define SM5502_REG_DEV_TYPE1_UART_SHIFT 3
#define SM5502_REG_DEV_TYPE1_CAR_KIT_CHARGER_SHIFT 4
#define SM5502_REG_DEV_TYPE1_USB_CHG_SHIFT 5
#define SM5502_REG_DEV_TYPE1_DEDICATED_CHG_SHIFT 6
#define SM5502_REG_DEV_TYPE1_USB_OTG_SHIFT 7
#define SM5502_REG_DEV_TYPE1_AUDIO_TYPE1_MASK (0x1 << SM5502_REG_DEV_TYPE1_AUDIO_TYPE1_SHIFT)
#define SM5502_REG_DEV_TYPE1_AUDIO_TYPE1__MASK (0x1 << SM5502_REG_DEV_TYPE1_AUDIO_TYPE2_SHIFT)
#define SM5502_REG_DEV_TYPE1_USB_SDP_MASK (0x1 << SM5502_REG_DEV_TYPE1_USB_SDP_SHIFT)
#define SM5502_REG_DEV_TYPE1_UART_MASK (0x1 << SM5502_REG_DEV_TYPE1_UART_SHIFT)
#define SM5502_REG_DEV_TYPE1_CAR_KIT_CHARGER_MASK (0x1 << SM5502_REG_DEV_TYPE1_CAR_KIT_CHARGER_SHIFT)
#define SM5502_REG_DEV_TYPE1_USB_CHG_MASK (0x1 << SM5502_REG_DEV_TYPE1_USB_CHG_SHIFT)
#define SM5502_REG_DEV_TYPE1_DEDICATED_CHG_MASK (0x1 << SM5502_REG_DEV_TYPE1_DEDICATED_CHG_SHIFT)
#define SM5502_REG_DEV_TYPE1_USB_OTG_MASK (0x1 << SM5502_REG_DEV_TYPE1_USB_OTG_SHIFT)
#define SM5502_REG_DEV_TYPE2_JIG_USB_ON_SHIFT 0
#define SM5502_REG_DEV_TYPE2_JIG_USB_OFF_SHIFT 1
#define SM5502_REG_DEV_TYPE2_JIG_UART_ON_SHIFT 2
#define SM5502_REG_DEV_TYPE2_JIG_UART_OFF_SHIFT 3
#define SM5502_REG_DEV_TYPE2_PPD_SHIFT 4
#define SM5502_REG_DEV_TYPE2_TTY_SHIFT 5
#define SM5502_REG_DEV_TYPE2_AV_CABLE_SHIFT 6
#define SM5502_REG_DEV_TYPE2_JIG_USB_ON_MASK (0x1 << SM5502_REG_DEV_TYPE2_JIG_USB_ON_SHIFT)
#define SM5502_REG_DEV_TYPE2_JIG_USB_OFF_MASK (0x1 << SM5502_REG_DEV_TYPE2_JIG_USB_OFF_SHIFT)
#define SM5502_REG_DEV_TYPE2_JIG_UART_ON_MASK (0x1 << SM5502_REG_DEV_TYPE2_JIG_UART_ON_SHIFT)
#define SM5502_REG_DEV_TYPE2_JIG_UART_OFF_MASK (0x1 << SM5502_REG_DEV_TYPE2_JIG_UART_OFF_SHIFT)
#define SM5502_REG_DEV_TYPE2_PPD_MASK (0x1 << SM5502_REG_DEV_TYPE2_PPD_SHIFT)
#define SM5502_REG_DEV_TYPE2_TTY_MASK (0x1 << SM5502_REG_DEV_TYPE2_TTY_SHIFT)
#define SM5502_REG_DEV_TYPE2_AV_CABLE_MASK (0x1 << SM5502_REG_DEV_TYPE2_AV_CABLE_SHIFT)
#define SM5502_REG_MANUAL_SW1_VBUSIN_SHIFT 0
#define SM5502_REG_MANUAL_SW1_DP_SHIFT 2
#define SM5502_REG_MANUAL_SW1_DM_SHIFT 5
#define SM5502_REG_MANUAL_SW1_VBUSIN_MASK (0x3 << SM5502_REG_MANUAL_SW1_VBUSIN_SHIFT)
#define SM5502_REG_MANUAL_SW1_DP_MASK (0x7 << SM5502_REG_MANUAL_SW1_DP_SHIFT)
#define SM5502_REG_MANUAL_SW1_DM_MASK (0x7 << SM5502_REG_MANUAL_SW1_DM_SHIFT)
#define VBUSIN_SWITCH_OPEN 0x0
#define VBUSIN_SWITCH_VBUSOUT 0x1
#define VBUSIN_SWITCH_MIC 0x2
#define VBUSIN_SWITCH_VBUSOUT_WITH_USB 0x3
#define DM_DP_CON_SWITCH_OPEN 0x0
#define DM_DP_CON_SWITCH_USB 0x1
#define DM_DP_CON_SWITCH_AUDIO 0x2
#define DM_DP_CON_SWITCH_UART 0x3
#define DM_DP_SWITCH_OPEN ((DM_DP_CON_SWITCH_OPEN <<SM5502_REG_MANUAL_SW1_DP_SHIFT) \
| (DM_DP_CON_SWITCH_OPEN <<SM5502_REG_MANUAL_SW1_DM_SHIFT))
#define DM_DP_SWITCH_USB ((DM_DP_CON_SWITCH_USB <<SM5502_REG_MANUAL_SW1_DP_SHIFT) \
| (DM_DP_CON_SWITCH_USB <<SM5502_REG_MANUAL_SW1_DM_SHIFT))
#define DM_DP_SWITCH_AUDIO ((DM_DP_CON_SWITCH_AUDIO <<SM5502_REG_MANUAL_SW1_DP_SHIFT) \
| (DM_DP_CON_SWITCH_AUDIO <<SM5502_REG_MANUAL_SW1_DM_SHIFT))
#define DM_DP_SWITCH_UART ((DM_DP_CON_SWITCH_UART <<SM5502_REG_MANUAL_SW1_DP_SHIFT) \
| (DM_DP_CON_SWITCH_UART <<SM5502_REG_MANUAL_SW1_DM_SHIFT))
/* SM5502 Interrupts */
enum sm5502_irq {
/* INT1 */
SM5502_IRQ_INT1_ATTACH,
SM5502_IRQ_INT1_DETACH,
SM5502_IRQ_INT1_KP,
SM5502_IRQ_INT1_LKP,
SM5502_IRQ_INT1_LKR,
SM5502_IRQ_INT1_OVP_EVENT,
SM5502_IRQ_INT1_OCP_EVENT,
SM5502_IRQ_INT1_OVP_OCP_DIS,
/* INT2 */
SM5502_IRQ_INT2_VBUS_DET,
SM5502_IRQ_INT2_REV_ACCE,
SM5502_IRQ_INT2_ADC_CHG,
SM5502_IRQ_INT2_STUCK_KEY,
SM5502_IRQ_INT2_STUCK_KEY_RCV,
SM5502_IRQ_INT2_MHL,
SM5502_IRQ_NUM,
};
#define SM5502_IRQ_INT1_ATTACH_MASK BIT(0)
#define SM5502_IRQ_INT1_DETACH_MASK BIT(1)
#define SM5502_IRQ_INT1_KP_MASK BIT(2)
#define SM5502_IRQ_INT1_LKP_MASK BIT(3)
#define SM5502_IRQ_INT1_LKR_MASK BIT(4)
#define SM5502_IRQ_INT1_OVP_EVENT_MASK BIT(5)
#define SM5502_IRQ_INT1_OCP_EVENT_MASK BIT(6)
#define SM5502_IRQ_INT1_OVP_OCP_DIS_MASK BIT(7)
#define SM5502_IRQ_INT2_VBUS_DET_MASK BIT(0)
#define SM5502_IRQ_INT2_REV_ACCE_MASK BIT(1)
#define SM5502_IRQ_INT2_ADC_CHG_MASK BIT(2)
#define SM5502_IRQ_INT2_STUCK_KEY_MASK BIT(3)
#define SM5502_IRQ_INT2_STUCK_KEY_RCV_MASK BIT(4)
#define SM5502_IRQ_INT2_MHL_MASK BIT(5)
#endif /* __LINUX_EXTCON_SM5502_H */