Skip to content

Commit 8242336

Browse files
Jakko3sre
authored andcommitted
power: supply: rt5033_charger: Add cable detection and USB OTG supply
Implement cable detection by extcon and handle the driver according to the connector type. There are basically three types of action: "set_charging", "set_otg" and "set_disconnect". A forth helper function to "unset_otg" was added because this is used in both "set_charging" and "set_disconnect". In the first case it covers the rather rare event that someone changes from OTG to charging without disconnect. In the second case, when disconnecting, the values are set back to the ones from initialization to return into a defined state. Additionally, there is "set_mivr". When connecting to e.g. a laptop/PC, the minimum input voltage regulation (MIVR) shall prevent a voltage drop if the cable or the supply is weak. The MIVR value is set to 4600MV, same as in the Android driver [1]. When disconnecting, MIVR is set back to DISABLED. In the function rt5033_get_charger_state(): When in OTG mode, the chip reports status "charging". Change this to "discharging" because there is no charging going on in OTG mode [2]. Yang Yingliang detected missing mutex_unlock() in some error path and suggested a fix [3]. The suggestion was squashed into this patch. [1] https://github.com/msm8916-mainline/linux-downstream/blob/GT-I9195I/drivers/battery/rt5033_charger.c#L499 [2] https://github.com/msm8916-mainline/linux-downstream/blob/GT-I9195I/drivers/battery/rt5033_charger.c#L686-L687 [3] https://lore.kernel.org/linux-pm/20230822030207.644738-1-yangyingliang@huawei.com Tested-by: Raymond Hackley <raymondhackley@protonmail.com> Co-developed-by: Yang Yingliang <yangyingliang@huawei.com> Signed-off-by: Jakob Hauser <jahau@rocketmail.com> Link: https://lore.kernel.org/r/cc4e37e510abbb0cdfa7faa8408da48c2cb448a4.1696165240.git.jahau@rocketmail.com Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
1 parent 0ce5145 commit 8242336

1 file changed

Lines changed: 285 additions & 2 deletions

File tree

drivers/power/supply/rt5033_charger.c

Lines changed: 285 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,12 @@
66
* Author: Beomho Seo <beomho.seo@samsung.com>
77
*/
88

9+
#include <linux/devm-helpers.h>
10+
#include <linux/extcon.h>
911
#include <linux/mod_devicetable.h>
1012
#include <linux/module.h>
13+
#include <linux/mutex.h>
14+
#include <linux/of.h>
1115
#include <linux/platform_device.h>
1216
#include <linux/power_supply.h>
1317
#include <linux/regmap.h>
@@ -26,6 +30,14 @@ struct rt5033_charger {
2630
struct regmap *regmap;
2731
struct power_supply *psy;
2832
struct rt5033_charger_data *chg;
33+
struct extcon_dev *edev;
34+
struct notifier_block extcon_nb;
35+
struct work_struct extcon_work;
36+
struct mutex lock;
37+
bool online;
38+
bool otg;
39+
bool mivr_enabled;
40+
u8 cv_regval;
2941
};
3042

3143
static int rt5033_get_charger_state(struct rt5033_charger *charger)
@@ -56,6 +68,10 @@ static int rt5033_get_charger_state(struct rt5033_charger *charger)
5668
state = POWER_SUPPLY_STATUS_UNKNOWN;
5769
}
5870

71+
/* For OTG mode, RT5033 would still report "charging" */
72+
if (charger->otg)
73+
state = POWER_SUPPLY_STATUS_DISCHARGING;
74+
5975
return state;
6076
}
6177

@@ -147,6 +163,9 @@ static inline int rt5033_init_const_charge(struct rt5033_charger *charger)
147163
return -EINVAL;
148164
}
149165

166+
/* Store that value for later usage */
167+
charger->cv_regval = reg_data;
168+
150169
/* Set end of charge current */
151170
if (chg->eoc_uamp < RT5033_CHARGER_EOC_MIN ||
152171
chg->eoc_uamp > RT5033_CHARGER_EOC_MAX) {
@@ -330,6 +349,162 @@ static int rt5033_charger_reg_init(struct rt5033_charger *charger)
330349
return 0;
331350
}
332351

352+
static int rt5033_charger_set_otg(struct rt5033_charger *charger)
353+
{
354+
int ret;
355+
356+
mutex_lock(&charger->lock);
357+
358+
/* Set OTG boost v_out to 5 volts */
359+
ret = regmap_update_bits(charger->regmap, RT5033_REG_CHG_CTRL2,
360+
RT5033_CHGCTRL2_CV_MASK,
361+
0x37 << RT5033_CHGCTRL2_CV_SHIFT);
362+
if (ret) {
363+
dev_err(charger->dev, "Failed set OTG boost v_out\n");
364+
ret = -EINVAL;
365+
goto out_unlock;
366+
}
367+
368+
/* Set operation mode to OTG */
369+
ret = regmap_update_bits(charger->regmap, RT5033_REG_CHG_CTRL1,
370+
RT5033_CHGCTRL1_MODE_MASK, RT5033_BOOST_MODE);
371+
if (ret) {
372+
dev_err(charger->dev, "Failed to update OTG mode.\n");
373+
ret = -EINVAL;
374+
goto out_unlock;
375+
}
376+
377+
/* In case someone switched from charging to OTG directly */
378+
if (charger->online)
379+
charger->online = false;
380+
381+
charger->otg = true;
382+
383+
out_unlock:
384+
mutex_unlock(&charger->lock);
385+
386+
return ret;
387+
}
388+
389+
static int rt5033_charger_unset_otg(struct rt5033_charger *charger)
390+
{
391+
int ret;
392+
u8 data;
393+
394+
/* Restore constant voltage for charging */
395+
data = charger->cv_regval;
396+
ret = regmap_update_bits(charger->regmap, RT5033_REG_CHG_CTRL2,
397+
RT5033_CHGCTRL2_CV_MASK,
398+
data << RT5033_CHGCTRL2_CV_SHIFT);
399+
if (ret) {
400+
dev_err(charger->dev, "Failed to restore constant voltage\n");
401+
return -EINVAL;
402+
}
403+
404+
/* Set operation mode to charging */
405+
ret = regmap_update_bits(charger->regmap, RT5033_REG_CHG_CTRL1,
406+
RT5033_CHGCTRL1_MODE_MASK, RT5033_CHARGER_MODE);
407+
if (ret) {
408+
dev_err(charger->dev, "Failed to update charger mode.\n");
409+
return -EINVAL;
410+
}
411+
412+
charger->otg = false;
413+
414+
return 0;
415+
}
416+
417+
static int rt5033_charger_set_charging(struct rt5033_charger *charger)
418+
{
419+
int ret;
420+
421+
mutex_lock(&charger->lock);
422+
423+
/* In case someone switched from OTG to charging directly */
424+
if (charger->otg) {
425+
ret = rt5033_charger_unset_otg(charger);
426+
if (ret) {
427+
mutex_unlock(&charger->lock);
428+
return -EINVAL;
429+
}
430+
}
431+
432+
charger->online = true;
433+
434+
mutex_unlock(&charger->lock);
435+
436+
return 0;
437+
}
438+
439+
static int rt5033_charger_set_mivr(struct rt5033_charger *charger)
440+
{
441+
int ret;
442+
443+
mutex_lock(&charger->lock);
444+
445+
/*
446+
* When connected via USB connector type SDP (Standard Downstream Port),
447+
* the minimum input voltage regulation (MIVR) should be enabled. It
448+
* prevents an input voltage drop due to insufficient current provided
449+
* by the adapter or USB input. As a downside, it may reduces the
450+
* charging current and thus slows the charging.
451+
*/
452+
ret = regmap_update_bits(charger->regmap, RT5033_REG_CHG_CTRL4,
453+
RT5033_CHGCTRL4_MIVR_MASK, RT5033_CHARGER_MIVR_4600MV);
454+
if (ret) {
455+
dev_err(charger->dev, "Failed to set MIVR level.\n");
456+
mutex_unlock(&charger->lock);
457+
return -EINVAL;
458+
}
459+
460+
charger->mivr_enabled = true;
461+
462+
mutex_unlock(&charger->lock);
463+
464+
/* Beyond this, do the same steps like setting charging */
465+
rt5033_charger_set_charging(charger);
466+
467+
return 0;
468+
}
469+
470+
static int rt5033_charger_set_disconnect(struct rt5033_charger *charger)
471+
{
472+
int ret = 0;
473+
474+
mutex_lock(&charger->lock);
475+
476+
/* Disable MIVR if enabled */
477+
if (charger->mivr_enabled) {
478+
ret = regmap_update_bits(charger->regmap,
479+
RT5033_REG_CHG_CTRL4,
480+
RT5033_CHGCTRL4_MIVR_MASK,
481+
RT5033_CHARGER_MIVR_DISABLE);
482+
if (ret) {
483+
dev_err(charger->dev, "Failed to disable MIVR.\n");
484+
ret = -EINVAL;
485+
goto out_unlock;
486+
}
487+
488+
charger->mivr_enabled = false;
489+
}
490+
491+
if (charger->otg) {
492+
ret = rt5033_charger_unset_otg(charger);
493+
if (ret) {
494+
ret = -EINVAL;
495+
goto out_unlock;
496+
}
497+
}
498+
499+
if (charger->online)
500+
charger->online = false;
501+
502+
out_unlock:
503+
mutex_unlock(&charger->lock);
504+
505+
return ret;
506+
}
507+
333508
static enum power_supply_property rt5033_charger_props[] = {
334509
POWER_SUPPLY_PROP_STATUS,
335510
POWER_SUPPLY_PROP_CHARGE_TYPE,
@@ -366,8 +541,7 @@ static int rt5033_charger_get_property(struct power_supply *psy,
366541
val->strval = RT5033_MANUFACTURER;
367542
break;
368543
case POWER_SUPPLY_PROP_ONLINE:
369-
val->intval = (rt5033_get_charger_state(charger) ==
370-
POWER_SUPPLY_STATUS_CHARGING);
544+
val->intval = charger->online;
371545
break;
372546
default:
373547
return -EINVAL;
@@ -402,6 +576,86 @@ static struct rt5033_charger_data *rt5033_charger_dt_init(
402576
return chg;
403577
}
404578

579+
static void rt5033_charger_extcon_work(struct work_struct *work)
580+
{
581+
struct rt5033_charger *charger =
582+
container_of(work, struct rt5033_charger, extcon_work);
583+
struct extcon_dev *edev = charger->edev;
584+
int connector, state;
585+
int ret;
586+
587+
for (connector = EXTCON_USB_HOST; connector <= EXTCON_CHG_USB_PD;
588+
connector++) {
589+
state = extcon_get_state(edev, connector);
590+
if (state == 1)
591+
break;
592+
}
593+
594+
/*
595+
* Adding a delay between extcon notification and extcon action. This
596+
* makes extcon action execution more reliable. Without the delay the
597+
* execution sometimes fails, possibly because the chip is busy or not
598+
* ready.
599+
*/
600+
msleep(100);
601+
602+
switch (connector) {
603+
case EXTCON_CHG_USB_SDP:
604+
ret = rt5033_charger_set_mivr(charger);
605+
if (ret) {
606+
dev_err(charger->dev, "failed to set USB mode\n");
607+
break;
608+
}
609+
dev_info(charger->dev, "USB mode. connector type: %d\n",
610+
connector);
611+
break;
612+
case EXTCON_CHG_USB_DCP:
613+
case EXTCON_CHG_USB_CDP:
614+
case EXTCON_CHG_USB_ACA:
615+
case EXTCON_CHG_USB_FAST:
616+
case EXTCON_CHG_USB_SLOW:
617+
case EXTCON_CHG_WPT:
618+
case EXTCON_CHG_USB_PD:
619+
ret = rt5033_charger_set_charging(charger);
620+
if (ret) {
621+
dev_err(charger->dev, "failed to set charging\n");
622+
break;
623+
}
624+
dev_info(charger->dev, "charging. connector type: %d\n",
625+
connector);
626+
break;
627+
case EXTCON_USB_HOST:
628+
ret = rt5033_charger_set_otg(charger);
629+
if (ret) {
630+
dev_err(charger->dev, "failed to set OTG\n");
631+
break;
632+
}
633+
dev_info(charger->dev, "OTG enabled\n");
634+
break;
635+
default:
636+
ret = rt5033_charger_set_disconnect(charger);
637+
if (ret) {
638+
dev_err(charger->dev, "failed to set disconnect\n");
639+
break;
640+
}
641+
dev_info(charger->dev, "disconnected\n");
642+
break;
643+
}
644+
645+
power_supply_changed(charger->psy);
646+
}
647+
648+
static int rt5033_charger_extcon_notifier(struct notifier_block *nb,
649+
unsigned long event, void *param)
650+
{
651+
struct rt5033_charger *charger =
652+
container_of(nb, struct rt5033_charger, extcon_nb);
653+
654+
schedule_work(&charger->extcon_work);
655+
656+
return NOTIFY_OK;
657+
}
658+
405659
static const struct power_supply_desc rt5033_charger_desc = {
406660
.name = "rt5033-charger",
407661
.type = POWER_SUPPLY_TYPE_USB,
@@ -414,6 +668,7 @@ static int rt5033_charger_probe(struct platform_device *pdev)
414668
{
415669
struct rt5033_charger *charger;
416670
struct power_supply_config psy_cfg = {};
671+
struct device_node *np_conn, *np_edev;
417672
int ret;
418673

419674
charger = devm_kzalloc(&pdev->dev, sizeof(*charger), GFP_KERNEL);
@@ -423,6 +678,7 @@ static int rt5033_charger_probe(struct platform_device *pdev)
423678
platform_set_drvdata(pdev, charger);
424679
charger->dev = &pdev->dev;
425680
charger->regmap = dev_get_regmap(pdev->dev.parent, NULL);
681+
mutex_init(&charger->lock);
426682

427683
psy_cfg.of_node = pdev->dev.of_node;
428684
psy_cfg.drv_data = charger;
@@ -442,6 +698,33 @@ static int rt5033_charger_probe(struct platform_device *pdev)
442698
if (ret)
443699
return ret;
444700

701+
/*
702+
* Extcon support is not vital for the charger to work. If no extcon
703+
* is available, just emit a warning and leave the probe function.
704+
*/
705+
np_conn = of_parse_phandle(pdev->dev.of_node, "richtek,usb-connector", 0);
706+
np_edev = of_get_parent(np_conn);
707+
charger->edev = extcon_find_edev_by_node(np_edev);
708+
if (IS_ERR(charger->edev)) {
709+
dev_warn(&pdev->dev, "no extcon device found in device-tree\n");
710+
goto out;
711+
}
712+
713+
ret = devm_work_autocancel(&pdev->dev, &charger->extcon_work,
714+
rt5033_charger_extcon_work);
715+
if (ret) {
716+
dev_err(&pdev->dev, "failed to initialize extcon work\n");
717+
return ret;
718+
}
719+
720+
charger->extcon_nb.notifier_call = rt5033_charger_extcon_notifier;
721+
ret = devm_extcon_register_notifier_all(&pdev->dev, charger->edev,
722+
&charger->extcon_nb);
723+
if (ret) {
724+
dev_err(&pdev->dev, "failed to register extcon notifier\n");
725+
return ret;
726+
}
727+
out:
445728
return 0;
446729
}
447730

0 commit comments

Comments
 (0)