| // SPDX-License-Identifier: GPL-2.0-or-later |
| |
| #include <linux/device.h> |
| #include <linux/devm-helpers.h> |
| #include <linux/delay.h> |
| #include <linux/err.h> |
| #include <linux/extcon-provider.h> |
| #include <linux/i2c.h> |
| #include <linux/mod_devicetable.h> |
| #include <linux/interrupt.h> |
| #include <linux/module.h> |
| #include <linux/pm.h> |
| #include <linux/regmap.h> |
| |
| /* I2C addresses of MUIC internal registers */ |
| #define MAX14526_DEVICE_ID 0x00 |
| #define MAX14526_ID 0x02 |
| |
| /* CONTROL_1 register masks */ |
| #define MAX14526_CONTROL_1 0x01 |
| #define ID_2P2 BIT(6) |
| #define ID_620 BIT(5) |
| #define ID_200 BIT(4) |
| #define VLDO BIT(3) |
| #define SEMREN BIT(2) |
| #define ADC_EN BIT(1) |
| #define CP_EN BIT(0) |
| |
| /* CONTROL_2 register masks */ |
| #define MAX14526_CONTROL_2 0x02 |
| #define INTPOL BIT(7) |
| #define INT_EN BIT(6) |
| #define MIC_LP BIT(5) |
| #define CP_AUD BIT(4) |
| #define CHG_TYPE BIT(1) |
| #define USB_DET_DIS BIT(0) |
| |
| /* SW_CONTROL register masks */ |
| #define MAX14526_SW_CONTROL 0x03 |
| #define SW_DATA 0x00 |
| #define SW_UART 0x01 |
| #define SW_AUDIO 0x02 |
| #define SW_OPEN 0x07 |
| |
| /* INT_STATUS register masks */ |
| #define MAX14526_INT_STAT 0x04 |
| #define CHGDET BIT(7) |
| #define MR_COMP BIT(6) |
| #define SENDEND BIT(5) |
| #define V_VBUS BIT(4) |
| |
| /* STATUS register masks */ |
| #define MAX14526_STATUS 0x05 |
| #define CPORT BIT(7) |
| #define CHPORT BIT(6) |
| #define C1COMP BIT(0) |
| |
| enum max14526_idno_resistance { |
| MAX14526_GND, |
| MAX14526_24KOHM, |
| MAX14526_56KOHM, |
| MAX14526_100KOHM, |
| MAX14526_130KOHM, |
| MAX14526_180KOHM, |
| MAX14526_240KOHM, |
| MAX14526_330KOHM, |
| MAX14526_430KOHM, |
| MAX14526_620KOHM, |
| MAX14526_910KOHM, |
| MAX14526_OPEN |
| }; |
| |
| enum max14526_field_idx { |
| VENDOR_ID, CHIP_REV, /* DEVID */ |
| DM, DP, /* SW_CONTROL */ |
| MAX14526_N_REGMAP_FIELDS |
| }; |
| |
| static const struct reg_field max14526_reg_field[MAX14526_N_REGMAP_FIELDS] = { |
| [VENDOR_ID] = REG_FIELD(MAX14526_DEVICE_ID, 4, 7), |
| [CHIP_REV] = REG_FIELD(MAX14526_DEVICE_ID, 0, 3), |
| [DM] = REG_FIELD(MAX14526_SW_CONTROL, 0, 2), |
| [DP] = REG_FIELD(MAX14526_SW_CONTROL, 3, 5), |
| }; |
| |
| struct max14526_data { |
| struct i2c_client *client; |
| struct extcon_dev *edev; |
| |
| struct regmap *regmap; |
| struct regmap_field *rfield[MAX14526_N_REGMAP_FIELDS]; |
| |
| int last_state; |
| int cable; |
| }; |
| |
| enum max14526_muic_modes { |
| MAX14526_OTG = MAX14526_GND, /* no power */ |
| MAX14526_MHL = MAX14526_56KOHM, /* no power */ |
| MAX14526_OTG_Y = MAX14526_GND | V_VBUS, |
| MAX14526_MHL_CHG = MAX14526_GND | V_VBUS | CHGDET, |
| MAX14526_NONE = MAX14526_OPEN, |
| MAX14526_USB = MAX14526_OPEN | V_VBUS, |
| MAX14526_CHG = MAX14526_OPEN | V_VBUS | CHGDET, |
| }; |
| |
| static const unsigned int max14526_extcon_cable[] = { |
| EXTCON_USB, |
| EXTCON_USB_HOST, |
| EXTCON_CHG_USB_FAST, |
| EXTCON_DISP_MHL, |
| EXTCON_NONE, |
| }; |
| |
| static int max14526_ap_usb_mode(struct max14526_data *priv) |
| { |
| struct device *dev = &priv->client->dev; |
| int ret; |
| |
| /* Enable USB Path */ |
| ret = regmap_field_write(priv->rfield[DM], SW_DATA); |
| if (ret) |
| return ret; |
| |
| ret = regmap_field_write(priv->rfield[DP], SW_DATA); |
| if (ret) |
| return ret; |
| |
| /* Enable 200K, Charger Pump and ADC */ |
| ret = regmap_write(priv->regmap, MAX14526_CONTROL_1, |
| ID_200 | ADC_EN | CP_EN); |
| if (ret) |
| return ret; |
| |
| dev_dbg(dev, "AP USB mode set\n"); |
| |
| return 0; |
| } |
| |
| static irqreturn_t max14526_interrupt(int irq, void *dev_id) |
| { |
| struct max14526_data *priv = dev_id; |
| struct device *dev = &priv->client->dev; |
| int state, ret; |
| |
| /* |
| * Upon an MUIC IRQ (MUIC_INT_N falls), wait at least 70ms |
| * before reading INT_STAT and STATUS. After the reads, |
| * MUIC_INT_N returns to high (but the INT_STAT and STATUS |
| * contents will be held). |
| */ |
| msleep(100); |
| |
| ret = regmap_read(priv->regmap, MAX14526_INT_STAT, &state); |
| if (ret) |
| dev_err(dev, "failed to read MUIC state %d\n", ret); |
| |
| if (state == priv->last_state) |
| return IRQ_HANDLED; |
| |
| /* Detach previous device */ |
| extcon_set_state_sync(priv->edev, priv->cable, false); |
| |
| switch (state) { |
| case MAX14526_USB: |
| priv->cable = EXTCON_USB; |
| break; |
| |
| case MAX14526_CHG: |
| priv->cable = EXTCON_CHG_USB_FAST; |
| break; |
| |
| case MAX14526_OTG: |
| case MAX14526_OTG_Y: |
| priv->cable = EXTCON_USB_HOST; |
| break; |
| |
| case MAX14526_MHL: |
| case MAX14526_MHL_CHG: |
| priv->cable = EXTCON_DISP_MHL; |
| break; |
| |
| case MAX14526_NONE: |
| default: |
| priv->cable = EXTCON_NONE; |
| break; |
| } |
| |
| extcon_set_state_sync(priv->edev, priv->cable, true); |
| |
| priv->last_state = state; |
| |
| return IRQ_HANDLED; |
| } |
| |
| static const struct regmap_config max14526_regmap_config = { |
| .reg_bits = 8, |
| .val_bits = 8, |
| .max_register = MAX14526_STATUS, |
| }; |
| |
| static int max14526_probe(struct i2c_client *client) |
| { |
| struct device *dev = &client->dev; |
| struct max14526_data *priv; |
| int ret, dev_id, rev, i; |
| |
| priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); |
| if (!priv) |
| return -ENOMEM; |
| |
| priv->client = client; |
| i2c_set_clientdata(client, priv); |
| |
| priv->regmap = devm_regmap_init_i2c(client, &max14526_regmap_config); |
| if (IS_ERR(priv->regmap)) |
| return dev_err_probe(dev, PTR_ERR(priv->regmap), "cannot allocate regmap\n"); |
| |
| for (i = 0; i < MAX14526_N_REGMAP_FIELDS; i++) { |
| priv->rfield[i] = devm_regmap_field_alloc(dev, priv->regmap, |
| max14526_reg_field[i]); |
| if (IS_ERR(priv->rfield[i])) |
| return dev_err_probe(dev, PTR_ERR(priv->rfield[i]), |
| "cannot allocate regmap field\n"); |
| } |
| |
| /* Detect if MUIC version is supported */ |
| ret = regmap_field_read(priv->rfield[VENDOR_ID], &dev_id); |
| if (ret) |
| return dev_err_probe(dev, ret, "failed to read MUIC ID\n"); |
| |
| regmap_field_read(priv->rfield[CHIP_REV], &rev); |
| |
| if (dev_id == MAX14526_ID) |
| dev_info(dev, "detected MAX14526 MUIC with id 0x%x, rev 0x%x\n", dev_id, rev); |
| else |
| dev_err_probe(dev, -EINVAL, "MUIC vendor id 0x%X is not recognized\n", dev_id); |
| |
| priv->edev = devm_extcon_dev_allocate(dev, max14526_extcon_cable); |
| if (IS_ERR(priv->edev)) |
| return dev_err_probe(dev, (IS_ERR(priv->edev)), |
| "failed to allocate extcon device\n"); |
| |
| ret = devm_extcon_dev_register(dev, priv->edev); |
| if (ret < 0) |
| return dev_err_probe(dev, ret, "failed to register extcon device\n"); |
| |
| ret = max14526_ap_usb_mode(priv); |
| if (ret < 0) |
| return dev_err_probe(dev, ret, "failed to set AP USB mode\n"); |
| |
| regmap_write_bits(priv->regmap, MAX14526_CONTROL_2, INT_EN, INT_EN); |
| regmap_write_bits(priv->regmap, MAX14526_CONTROL_2, USB_DET_DIS, (u32)~USB_DET_DIS); |
| |
| ret = devm_request_threaded_irq(dev, client->irq, NULL, &max14526_interrupt, |
| IRQF_ONESHOT | IRQF_SHARED, client->name, priv); |
| if (ret) |
| return dev_err_probe(dev, ret, "failed to register IRQ\n"); |
| |
| irq_wake_thread(client->irq, priv); |
| |
| return 0; |
| } |
| |
| static int max14526_resume(struct device *dev) |
| { |
| struct i2c_client *client = to_i2c_client(dev); |
| struct max14526_data *priv = i2c_get_clientdata(client); |
| |
| irq_wake_thread(client->irq, priv); |
| |
| return 0; |
| } |
| |
| static DEFINE_SIMPLE_DEV_PM_OPS(max14526_pm_ops, NULL, max14526_resume); |
| |
| static const struct of_device_id max14526_match[] = { |
| { .compatible = "maxim,max14526" }, |
| { /* sentinel */ } |
| }; |
| MODULE_DEVICE_TABLE(of, max14526_match); |
| |
| static const struct i2c_device_id max14526_id[] = { |
| { "max14526" }, |
| { } |
| }; |
| MODULE_DEVICE_TABLE(i2c, max14526_id); |
| |
| static struct i2c_driver max14526_driver = { |
| .driver = { |
| .name = "max14526", |
| .of_match_table = max14526_match, |
| .pm = &max14526_pm_ops, |
| }, |
| .probe = max14526_probe, |
| .id_table = max14526_id, |
| }; |
| module_i2c_driver(max14526_driver); |
| |
| MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@gmail.com>"); |
| MODULE_DESCRIPTION("MAX14526 extcon driver to support MUIC"); |
| MODULE_LICENSE("GPL"); |