extcon: Add LC824206XA microUSB switch driver
Add a new driver for the ON Semiconductor LC824206XA microUSB switch and accessory detector chip. ON Semiconductor has an "Advance Information" datasheet available (ENA2222-D.PDF), but no full datasheet. So there is no documentation available for the registers. This driver is based on the register info from the extcon-fsa9285.c driver, from the Lollipop Android sources for the Lenovo Yoga Tablet 2 (Pro) 830 / 1050 / 1380 models. Note despite the name this is actually a driver for the LC824206XA not the FSA9285. This has only been tested on a Lenovo Yoga Tablet 2 Pro 1380 and using the driver on other setups may require additional work. So far this driver is only used on x86/ACPI (non devicetree) devs. Therefor there is no devicetree bindings documentation for this driver's "onnn,enable-miclr-for-dcp" property since this is not used in actual devicetree files and the dt bindings maintainers have requested properties with no actual dt users to _not_ be added to the dt bindings. Link: https://lore.kernel.org/lkml/20240512204019.58121-1-hdegoede@redhat.com/ Signed-off-by: Hans de Goede <hdegoede@redhat.com> Signed-off-by: Chanwoo Choi <cw00.choi@samsung.com>
This commit is contained in:
parent
47ac09b91b
commit
9e1897cb95
@ -75,6 +75,17 @@ config EXTCON_INTEL_MRFLD
|
||||
Say Y here to enable extcon support for charger detection / control
|
||||
on the Intel Merrifield Basin Cove PMIC.
|
||||
|
||||
config EXTCON_LC824206XA
|
||||
tristate "LC824206XA extcon Support"
|
||||
depends on I2C
|
||||
depends on POWER_SUPPLY
|
||||
help
|
||||
Say Y here to enable support for the ON Semiconductor LC824206XA
|
||||
microUSB switch and accessory detector chip. The LC824206XA is a USB
|
||||
port accessory detector and switch. The LC824206XA is fully controlled
|
||||
using I2C and enables USB data, stereo and mono audio, video,
|
||||
microphone and UART data to use a common connector port.
|
||||
|
||||
config EXTCON_MAX14577
|
||||
tristate "Maxim MAX14577/77836 EXTCON Support"
|
||||
depends on MFD_MAX14577
|
||||
|
@ -12,6 +12,7 @@ obj-$(CONFIG_EXTCON_GPIO) += extcon-gpio.o
|
||||
obj-$(CONFIG_EXTCON_INTEL_INT3496) += extcon-intel-int3496.o
|
||||
obj-$(CONFIG_EXTCON_INTEL_CHT_WC) += extcon-intel-cht-wc.o
|
||||
obj-$(CONFIG_EXTCON_INTEL_MRFLD) += extcon-intel-mrfld.o
|
||||
obj-$(CONFIG_EXTCON_LC824206XA) += extcon-lc824206xa.o
|
||||
obj-$(CONFIG_EXTCON_MAX14577) += extcon-max14577.o
|
||||
obj-$(CONFIG_EXTCON_MAX3355) += extcon-max3355.o
|
||||
obj-$(CONFIG_EXTCON_MAX77693) += extcon-max77693.o
|
||||
|
500
drivers/extcon/extcon-lc824206xa.c
Normal file
500
drivers/extcon/extcon-lc824206xa.c
Normal file
@ -0,0 +1,500 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* ON Semiconductor LC824206XA Micro USB Switch driver
|
||||
*
|
||||
* Copyright (c) 2024 Hans de Goede <hansg@kernel.org>
|
||||
*
|
||||
* ON Semiconductor has an "Advance Information" datasheet available
|
||||
* (ENA2222-D.PDF), but no full datasheet. So there is no documentation
|
||||
* available for the registers.
|
||||
*
|
||||
* This driver is based on the register info from the extcon-fsa9285.c driver,
|
||||
* from the Lollipop Android sources for the Lenovo Yoga Tablet 2 (Pro)
|
||||
* 830 / 1050 / 1380 models. Note despite the name this is actually a driver
|
||||
* for the LC824206XA not the FSA9285. The Android sources can be downloaded
|
||||
* from Lenovo's support page for these tablets, filename:
|
||||
* yoga_tab_2_osc_android_to_lollipop_201505.rar.
|
||||
*/
|
||||
|
||||
#include <linux/bits.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/extcon-provider.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/power_supply.h>
|
||||
#include <linux/property.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
/*
|
||||
* Register defines as mentioned above there is no datasheet with register
|
||||
* info, so this may not be 100% accurate.
|
||||
*/
|
||||
#define REG00 0x00
|
||||
#define REG00_INIT_VALUE 0x01
|
||||
|
||||
#define REG_STATUS 0x01
|
||||
#define STATUS_OVP BIT(0)
|
||||
#define STATUS_DATA_SHORT BIT(1)
|
||||
#define STATUS_VBUS_PRESENT BIT(2)
|
||||
#define STATUS_USB_ID GENMASK(7, 3)
|
||||
#define STATUS_USB_ID_GND 0x80
|
||||
#define STATUS_USB_ID_ACA 0xf0
|
||||
#define STATUS_USB_ID_FLOAT 0xf8
|
||||
|
||||
/*
|
||||
* This controls the DP/DM muxes + other switches,
|
||||
* meaning of individual bits is unknown.
|
||||
*/
|
||||
#define REG_SWITCH_CONTROL 0x02
|
||||
#define SWITCH_STEREO_MIC 0xc8
|
||||
#define SWITCH_USB_HOST 0xec
|
||||
#define SWITCH_DISCONNECTED 0xf8
|
||||
#define SWITCH_USB_DEVICE 0xfc
|
||||
|
||||
/* 5 bits? ADC 0x10 GND, 0x1a-0x1f ACA, 0x1f float */
|
||||
#define REG_ID_PIN_ADC_VALUE 0x03
|
||||
|
||||
/* Masks for all 3 interrupt registers */
|
||||
#define INTR_ID_PIN_CHANGE BIT(0)
|
||||
#define INTR_VBUS_CHANGE BIT(1)
|
||||
/* Both of these get set after a continuous mode ADC conversion */
|
||||
#define INTR_ID_PIN_ADC_INT1 BIT(2)
|
||||
#define INTR_ID_PIN_ADC_INT2 BIT(3)
|
||||
/* Charger type available in reg 0x09 */
|
||||
#define INTR_CHARGER_DET_DONE BIT(4)
|
||||
#define INTR_OVP BIT(5)
|
||||
|
||||
/* There are 7 interrupt sources, bit 6 use is unknown (OCP?) */
|
||||
#define INTR_ALL GENMASK(6, 0)
|
||||
|
||||
/* Unmask interrupts this driver cares about */
|
||||
#define INTR_MASK \
|
||||
(INTR_ALL & ~(INTR_ID_PIN_CHANGE | INTR_VBUS_CHANGE | INTR_CHARGER_DET_DONE))
|
||||
|
||||
/* Active (event happened and not cleared yet) interrupts */
|
||||
#define REG_INTR_STATUS 0x04
|
||||
|
||||
/*
|
||||
* Writing a 1 to a bit here clears it in INTR_STATUS. These bits do NOT
|
||||
* auto-reset to 0, so these must be set to 0 manually after clearing.
|
||||
*/
|
||||
#define REG_INTR_CLEAR 0x05
|
||||
|
||||
/* Interrupts which bit is set to 1 here will not raise the HW IRQ */
|
||||
#define REG_INTR_MASK 0x06
|
||||
|
||||
/* ID pin ADC control, meaning of individual bits is unknown */
|
||||
#define REG_ID_PIN_ADC_CTRL 0x07
|
||||
#define ID_PIN_ADC_AUTO 0x40
|
||||
#define ID_PIN_ADC_CONTINUOUS 0x44
|
||||
|
||||
#define REG_CHARGER_DET 0x08
|
||||
#define CHARGER_DET_ON BIT(0)
|
||||
#define CHARGER_DET_CDP_ON BIT(1)
|
||||
#define CHARGER_DET_CDP_VAL BIT(2)
|
||||
|
||||
#define REG_CHARGER_TYPE 0x09
|
||||
#define CHARGER_TYPE_UNKNOWN 0x00
|
||||
#define CHARGER_TYPE_DCP 0x01
|
||||
#define CHARGER_TYPE_SDP_OR_CDP 0x04
|
||||
#define CHARGER_TYPE_QC 0x06
|
||||
|
||||
#define REG10 0x10
|
||||
#define REG10_INIT_VALUE 0x00
|
||||
|
||||
struct lc824206xa_data {
|
||||
struct work_struct work;
|
||||
struct i2c_client *client;
|
||||
struct extcon_dev *edev;
|
||||
struct power_supply *psy;
|
||||
struct regulator *vbus_boost;
|
||||
unsigned int usb_type;
|
||||
unsigned int cable;
|
||||
unsigned int previous_cable;
|
||||
u8 switch_control;
|
||||
u8 previous_switch_control;
|
||||
bool vbus_ok;
|
||||
bool vbus_boost_enabled;
|
||||
bool fastcharge_over_miclr;
|
||||
};
|
||||
|
||||
static const unsigned int lc824206xa_cables[] = {
|
||||
EXTCON_USB_HOST,
|
||||
EXTCON_CHG_USB_SDP,
|
||||
EXTCON_CHG_USB_CDP,
|
||||
EXTCON_CHG_USB_DCP,
|
||||
EXTCON_CHG_USB_ACA,
|
||||
EXTCON_CHG_USB_FAST,
|
||||
EXTCON_NONE,
|
||||
};
|
||||
|
||||
/* read/write reg helpers to add error logging to smbus byte functions */
|
||||
static int lc824206xa_read_reg(struct lc824206xa_data *data, u8 reg)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = i2c_smbus_read_byte_data(data->client, reg);
|
||||
if (ret < 0)
|
||||
dev_err(&data->client->dev, "Error %d reading reg 0x%02x\n", ret, reg);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int lc824206xa_write_reg(struct lc824206xa_data *data, u8 reg, u8 val)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = i2c_smbus_write_byte_data(data->client, reg, val);
|
||||
if (ret < 0)
|
||||
dev_err(&data->client->dev, "Error %d writing reg 0x%02x\n", ret, reg);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int lc824206xa_get_id(struct lc824206xa_data *data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = lc824206xa_write_reg(data, REG_ID_PIN_ADC_CTRL, ID_PIN_ADC_CONTINUOUS);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = lc824206xa_read_reg(data, REG_ID_PIN_ADC_VALUE);
|
||||
|
||||
lc824206xa_write_reg(data, REG_ID_PIN_ADC_CTRL, ID_PIN_ADC_AUTO);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void lc824206xa_set_vbus_boost(struct lc824206xa_data *data, bool enable)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (data->vbus_boost_enabled == enable)
|
||||
return;
|
||||
|
||||
if (enable)
|
||||
ret = regulator_enable(data->vbus_boost);
|
||||
else
|
||||
ret = regulator_disable(data->vbus_boost);
|
||||
|
||||
if (ret == 0)
|
||||
data->vbus_boost_enabled = enable;
|
||||
else
|
||||
dev_err(&data->client->dev, "Error updating Vbus boost regulator: %d\n", ret);
|
||||
}
|
||||
|
||||
static void lc824206xa_charger_detect(struct lc824206xa_data *data)
|
||||
{
|
||||
int charger_type, ret;
|
||||
|
||||
charger_type = lc824206xa_read_reg(data, REG_CHARGER_TYPE);
|
||||
if (charger_type < 0)
|
||||
return;
|
||||
|
||||
dev_dbg(&data->client->dev, "charger type 0x%02x\n", charger_type);
|
||||
|
||||
switch (charger_type) {
|
||||
case CHARGER_TYPE_UNKNOWN:
|
||||
data->usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN;
|
||||
/* Treat as SDP */
|
||||
data->cable = EXTCON_CHG_USB_SDP;
|
||||
data->switch_control = SWITCH_USB_DEVICE;
|
||||
break;
|
||||
case CHARGER_TYPE_SDP_OR_CDP:
|
||||
data->usb_type = POWER_SUPPLY_USB_TYPE_SDP;
|
||||
data->cable = EXTCON_CHG_USB_SDP;
|
||||
data->switch_control = SWITCH_USB_DEVICE;
|
||||
|
||||
ret = lc824206xa_write_reg(data, REG_CHARGER_DET,
|
||||
CHARGER_DET_CDP_ON | CHARGER_DET_ON);
|
||||
if (ret < 0)
|
||||
break;
|
||||
|
||||
msleep(100);
|
||||
ret = lc824206xa_read_reg(data, REG_CHARGER_DET);
|
||||
if (ret >= 0 && (ret & CHARGER_DET_CDP_VAL)) {
|
||||
data->usb_type = POWER_SUPPLY_USB_TYPE_CDP;
|
||||
data->cable = EXTCON_CHG_USB_CDP;
|
||||
}
|
||||
|
||||
lc824206xa_write_reg(data, REG_CHARGER_DET, CHARGER_DET_ON);
|
||||
break;
|
||||
case CHARGER_TYPE_DCP:
|
||||
data->usb_type = POWER_SUPPLY_USB_TYPE_DCP;
|
||||
data->cable = EXTCON_CHG_USB_DCP;
|
||||
if (data->fastcharge_over_miclr)
|
||||
data->switch_control = SWITCH_STEREO_MIC;
|
||||
else
|
||||
data->switch_control = SWITCH_DISCONNECTED;
|
||||
break;
|
||||
case CHARGER_TYPE_QC:
|
||||
data->usb_type = POWER_SUPPLY_USB_TYPE_DCP;
|
||||
data->cable = EXTCON_CHG_USB_DCP;
|
||||
data->switch_control = SWITCH_DISCONNECTED;
|
||||
break;
|
||||
default:
|
||||
dev_warn(&data->client->dev, "Unknown charger type: 0x%02x\n", charger_type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void lc824206xa_work(struct work_struct *work)
|
||||
{
|
||||
struct lc824206xa_data *data = container_of(work, struct lc824206xa_data, work);
|
||||
bool vbus_boost_enable = false;
|
||||
int status, id;
|
||||
|
||||
status = lc824206xa_read_reg(data, REG_STATUS);
|
||||
if (status < 0)
|
||||
return;
|
||||
|
||||
dev_dbg(&data->client->dev, "status 0x%02x\n", status);
|
||||
|
||||
data->vbus_ok = (status & (STATUS_VBUS_PRESENT | STATUS_OVP)) == STATUS_VBUS_PRESENT;
|
||||
|
||||
/* Read id pin ADC if necessary */
|
||||
switch (status & STATUS_USB_ID) {
|
||||
case STATUS_USB_ID_GND:
|
||||
case STATUS_USB_ID_FLOAT:
|
||||
break;
|
||||
default:
|
||||
/* Happens when the connector is inserted slowly, log at dbg level */
|
||||
dev_dbg(&data->client->dev, "Unknown status 0x%02x\n", status);
|
||||
fallthrough;
|
||||
case STATUS_USB_ID_ACA:
|
||||
id = lc824206xa_get_id(data);
|
||||
dev_dbg(&data->client->dev, "RID 0x%02x\n", id);
|
||||
switch (id) {
|
||||
case 0x10:
|
||||
status = STATUS_USB_ID_GND;
|
||||
break;
|
||||
case 0x18 ... 0x1e:
|
||||
status = STATUS_USB_ID_ACA;
|
||||
break;
|
||||
case 0x1f:
|
||||
status = STATUS_USB_ID_FLOAT;
|
||||
break;
|
||||
default:
|
||||
dev_warn(&data->client->dev, "Unknown RID 0x%02x\n", id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check for out of spec OTG charging hubs, treat as ACA */
|
||||
if ((status & STATUS_USB_ID) == STATUS_USB_ID_GND &&
|
||||
data->vbus_ok && !data->vbus_boost_enabled) {
|
||||
dev_info(&data->client->dev, "Out of spec USB host adapter with Vbus present, not enabling 5V output\n");
|
||||
status = STATUS_USB_ID_ACA;
|
||||
}
|
||||
|
||||
switch (status & STATUS_USB_ID) {
|
||||
case STATUS_USB_ID_ACA:
|
||||
data->usb_type = POWER_SUPPLY_USB_TYPE_ACA;
|
||||
data->cable = EXTCON_CHG_USB_ACA;
|
||||
data->switch_control = SWITCH_USB_HOST;
|
||||
break;
|
||||
case STATUS_USB_ID_GND:
|
||||
data->usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN;
|
||||
data->cable = EXTCON_USB_HOST;
|
||||
data->switch_control = SWITCH_USB_HOST;
|
||||
vbus_boost_enable = true;
|
||||
break;
|
||||
case STATUS_USB_ID_FLOAT:
|
||||
/* When fast charging with Vbus > 5V, OVP will be set */
|
||||
if (data->fastcharge_over_miclr &&
|
||||
data->switch_control == SWITCH_STEREO_MIC &&
|
||||
(status & STATUS_OVP)) {
|
||||
data->cable = EXTCON_CHG_USB_FAST;
|
||||
break;
|
||||
}
|
||||
|
||||
if (data->vbus_ok) {
|
||||
lc824206xa_charger_detect(data);
|
||||
} else {
|
||||
data->usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN;
|
||||
data->cable = EXTCON_NONE;
|
||||
data->switch_control = SWITCH_DISCONNECTED;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
lc824206xa_set_vbus_boost(data, vbus_boost_enable);
|
||||
|
||||
if (data->switch_control != data->previous_switch_control) {
|
||||
lc824206xa_write_reg(data, REG_SWITCH_CONTROL, data->switch_control);
|
||||
data->previous_switch_control = data->switch_control;
|
||||
}
|
||||
|
||||
if (data->cable != data->previous_cable) {
|
||||
extcon_set_state_sync(data->edev, data->previous_cable, false);
|
||||
extcon_set_state_sync(data->edev, data->cable, true);
|
||||
data->previous_cable = data->cable;
|
||||
}
|
||||
|
||||
power_supply_changed(data->psy);
|
||||
}
|
||||
|
||||
static irqreturn_t lc824206xa_irq(int irq, void *_data)
|
||||
{
|
||||
struct lc824206xa_data *data = _data;
|
||||
int intr_status;
|
||||
|
||||
intr_status = lc824206xa_read_reg(data, REG_INTR_STATUS);
|
||||
if (intr_status < 0)
|
||||
intr_status = INTR_ALL; /* Should never happen, clear all */
|
||||
|
||||
dev_dbg(&data->client->dev, "interrupt 0x%02x\n", intr_status);
|
||||
|
||||
lc824206xa_write_reg(data, REG_INTR_CLEAR, intr_status);
|
||||
lc824206xa_write_reg(data, REG_INTR_CLEAR, 0);
|
||||
|
||||
schedule_work(&data->work);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/*
|
||||
* Newer charger (power_supply) drivers expect the max input current to be
|
||||
* provided by a parent power_supply device for the charger chip.
|
||||
*/
|
||||
static int lc824206xa_psy_get_prop(struct power_supply *psy,
|
||||
enum power_supply_property psp,
|
||||
union power_supply_propval *val)
|
||||
{
|
||||
struct lc824206xa_data *data = power_supply_get_drvdata(psy);
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_ONLINE:
|
||||
val->intval = data->vbus_ok && !data->vbus_boost_enabled;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_USB_TYPE:
|
||||
val->intval = data->usb_type;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_CURRENT_MAX:
|
||||
switch (data->usb_type) {
|
||||
case POWER_SUPPLY_USB_TYPE_DCP:
|
||||
case POWER_SUPPLY_USB_TYPE_ACA:
|
||||
val->intval = 2000000;
|
||||
break;
|
||||
case POWER_SUPPLY_USB_TYPE_CDP:
|
||||
val->intval = 1500000;
|
||||
break;
|
||||
default:
|
||||
val->intval = 500000;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const enum power_supply_usb_type lc824206xa_psy_usb_types[] = {
|
||||
POWER_SUPPLY_USB_TYPE_SDP,
|
||||
POWER_SUPPLY_USB_TYPE_CDP,
|
||||
POWER_SUPPLY_USB_TYPE_DCP,
|
||||
POWER_SUPPLY_USB_TYPE_ACA,
|
||||
POWER_SUPPLY_USB_TYPE_UNKNOWN,
|
||||
};
|
||||
|
||||
static const enum power_supply_property lc824206xa_psy_props[] = {
|
||||
POWER_SUPPLY_PROP_ONLINE,
|
||||
POWER_SUPPLY_PROP_USB_TYPE,
|
||||
POWER_SUPPLY_PROP_CURRENT_MAX,
|
||||
};
|
||||
|
||||
static const struct power_supply_desc lc824206xa_psy_desc = {
|
||||
.name = "lc824206xa-charger-detect",
|
||||
.type = POWER_SUPPLY_TYPE_USB,
|
||||
.usb_types = lc824206xa_psy_usb_types,
|
||||
.num_usb_types = ARRAY_SIZE(lc824206xa_psy_usb_types),
|
||||
.properties = lc824206xa_psy_props,
|
||||
.num_properties = ARRAY_SIZE(lc824206xa_psy_props),
|
||||
.get_property = lc824206xa_psy_get_prop,
|
||||
};
|
||||
|
||||
static int lc824206xa_probe(struct i2c_client *client)
|
||||
{
|
||||
struct power_supply_config psy_cfg = { };
|
||||
struct device *dev = &client->dev;
|
||||
struct lc824206xa_data *data;
|
||||
int ret;
|
||||
|
||||
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
data->client = client;
|
||||
INIT_WORK(&data->work, lc824206xa_work);
|
||||
data->cable = EXTCON_NONE;
|
||||
data->previous_cable = EXTCON_NONE;
|
||||
data->usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN;
|
||||
/* Some designs use a custom fast-charge protocol over the mic L/R inputs */
|
||||
data->fastcharge_over_miclr =
|
||||
device_property_read_bool(dev, "onnn,enable-miclr-for-dcp");
|
||||
|
||||
data->vbus_boost = devm_regulator_get(dev, "vbus");
|
||||
if (IS_ERR(data->vbus_boost))
|
||||
return dev_err_probe(dev, PTR_ERR(data->vbus_boost),
|
||||
"getting regulator\n");
|
||||
|
||||
/* Init */
|
||||
ret = lc824206xa_write_reg(data, REG00, REG00_INIT_VALUE);
|
||||
ret |= lc824206xa_write_reg(data, REG10, REG10_INIT_VALUE);
|
||||
msleep(100);
|
||||
ret |= lc824206xa_write_reg(data, REG_INTR_CLEAR, INTR_ALL);
|
||||
ret |= lc824206xa_write_reg(data, REG_INTR_CLEAR, 0);
|
||||
ret |= lc824206xa_write_reg(data, REG_INTR_MASK, INTR_MASK);
|
||||
ret |= lc824206xa_write_reg(data, REG_ID_PIN_ADC_CTRL, ID_PIN_ADC_AUTO);
|
||||
ret |= lc824206xa_write_reg(data, REG_CHARGER_DET, CHARGER_DET_ON);
|
||||
if (ret)
|
||||
return -EIO;
|
||||
|
||||
/* Initialize extcon device */
|
||||
data->edev = devm_extcon_dev_allocate(dev, lc824206xa_cables);
|
||||
if (IS_ERR(data->edev))
|
||||
return PTR_ERR(data->edev);
|
||||
|
||||
ret = devm_extcon_dev_register(dev, data->edev);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "registering extcon device\n");
|
||||
|
||||
psy_cfg.drv_data = data;
|
||||
data->psy = devm_power_supply_register(dev, &lc824206xa_psy_desc, &psy_cfg);
|
||||
if (IS_ERR(data->psy))
|
||||
return dev_err_probe(dev, PTR_ERR(data->psy), "registering power supply\n");
|
||||
|
||||
ret = devm_request_threaded_irq(dev, client->irq, NULL, lc824206xa_irq,
|
||||
IRQF_TRIGGER_LOW | IRQF_ONESHOT,
|
||||
KBUILD_MODNAME, data);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "requesting IRQ\n");
|
||||
|
||||
/* Sync initial state */
|
||||
schedule_work(&data->work);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id lc824206xa_i2c_ids[] = {
|
||||
{ "lc824206xa" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, lc824206xa_i2c_ids);
|
||||
|
||||
static struct i2c_driver lc824206xa_driver = {
|
||||
.driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
},
|
||||
.probe = lc824206xa_probe,
|
||||
.id_table = lc824206xa_i2c_ids,
|
||||
};
|
||||
|
||||
module_i2c_driver(lc824206xa_driver);
|
||||
|
||||
MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>");
|
||||
MODULE_DESCRIPTION("LC824206XA Micro USB Switch driver");
|
||||
MODULE_LICENSE("GPL");
|
Loading…
Reference in New Issue
Block a user