162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Backup battery driver for Wolfson Microelectronics wm831x PMICs
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright 2009 Wolfson Microelectronics PLC.
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/module.h>
962306a36Sopenharmony_ci#include <linux/err.h>
1062306a36Sopenharmony_ci#include <linux/platform_device.h>
1162306a36Sopenharmony_ci#include <linux/power_supply.h>
1262306a36Sopenharmony_ci#include <linux/slab.h>
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#include <linux/mfd/wm831x/core.h>
1562306a36Sopenharmony_ci#include <linux/mfd/wm831x/auxadc.h>
1662306a36Sopenharmony_ci#include <linux/mfd/wm831x/pmu.h>
1762306a36Sopenharmony_ci#include <linux/mfd/wm831x/pdata.h>
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_cistruct wm831x_backup {
2062306a36Sopenharmony_ci	struct wm831x *wm831x;
2162306a36Sopenharmony_ci	struct power_supply *backup;
2262306a36Sopenharmony_ci	struct power_supply_desc backup_desc;
2362306a36Sopenharmony_ci	char name[20];
2462306a36Sopenharmony_ci};
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_cistatic int wm831x_backup_read_voltage(struct wm831x *wm831x,
2762306a36Sopenharmony_ci				     enum wm831x_auxadc src,
2862306a36Sopenharmony_ci				     union power_supply_propval *val)
2962306a36Sopenharmony_ci{
3062306a36Sopenharmony_ci	int ret;
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci	ret = wm831x_auxadc_read_uv(wm831x, src);
3362306a36Sopenharmony_ci	if (ret >= 0)
3462306a36Sopenharmony_ci		val->intval = ret;
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci	return ret;
3762306a36Sopenharmony_ci}
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci/*********************************************************************
4062306a36Sopenharmony_ci *		Backup supply properties
4162306a36Sopenharmony_ci *********************************************************************/
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_cistatic void wm831x_config_backup(struct wm831x *wm831x)
4462306a36Sopenharmony_ci{
4562306a36Sopenharmony_ci	struct wm831x_pdata *wm831x_pdata = wm831x->dev->platform_data;
4662306a36Sopenharmony_ci	struct wm831x_backup_pdata *pdata;
4762306a36Sopenharmony_ci	int ret, reg;
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci	if (!wm831x_pdata || !wm831x_pdata->backup) {
5062306a36Sopenharmony_ci		dev_warn(wm831x->dev,
5162306a36Sopenharmony_ci			 "No backup battery charger configuration\n");
5262306a36Sopenharmony_ci		return;
5362306a36Sopenharmony_ci	}
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	pdata = wm831x_pdata->backup;
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci	reg = 0;
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	if (pdata->charger_enable)
6062306a36Sopenharmony_ci		reg |= WM831X_BKUP_CHG_ENA | WM831X_BKUP_BATT_DET_ENA;
6162306a36Sopenharmony_ci	if (pdata->no_constant_voltage)
6262306a36Sopenharmony_ci		reg |= WM831X_BKUP_CHG_MODE;
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	switch (pdata->vlim) {
6562306a36Sopenharmony_ci	case 2500:
6662306a36Sopenharmony_ci		break;
6762306a36Sopenharmony_ci	case 3100:
6862306a36Sopenharmony_ci		reg |= WM831X_BKUP_CHG_VLIM;
6962306a36Sopenharmony_ci		break;
7062306a36Sopenharmony_ci	default:
7162306a36Sopenharmony_ci		dev_err(wm831x->dev, "Invalid backup voltage limit %dmV\n",
7262306a36Sopenharmony_ci			pdata->vlim);
7362306a36Sopenharmony_ci	}
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	switch (pdata->ilim) {
7662306a36Sopenharmony_ci	case 100:
7762306a36Sopenharmony_ci		break;
7862306a36Sopenharmony_ci	case 200:
7962306a36Sopenharmony_ci		reg |= 1;
8062306a36Sopenharmony_ci		break;
8162306a36Sopenharmony_ci	case 300:
8262306a36Sopenharmony_ci		reg |= 2;
8362306a36Sopenharmony_ci		break;
8462306a36Sopenharmony_ci	case 400:
8562306a36Sopenharmony_ci		reg |= 3;
8662306a36Sopenharmony_ci		break;
8762306a36Sopenharmony_ci	default:
8862306a36Sopenharmony_ci		dev_err(wm831x->dev, "Invalid backup current limit %duA\n",
8962306a36Sopenharmony_ci			pdata->ilim);
9062306a36Sopenharmony_ci	}
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	ret = wm831x_reg_unlock(wm831x);
9362306a36Sopenharmony_ci	if (ret != 0) {
9462306a36Sopenharmony_ci		dev_err(wm831x->dev, "Failed to unlock registers: %d\n", ret);
9562306a36Sopenharmony_ci		return;
9662306a36Sopenharmony_ci	}
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	ret = wm831x_set_bits(wm831x, WM831X_BACKUP_CHARGER_CONTROL,
9962306a36Sopenharmony_ci			      WM831X_BKUP_CHG_ENA_MASK |
10062306a36Sopenharmony_ci			      WM831X_BKUP_CHG_MODE_MASK |
10162306a36Sopenharmony_ci			      WM831X_BKUP_BATT_DET_ENA_MASK |
10262306a36Sopenharmony_ci			      WM831X_BKUP_CHG_VLIM_MASK |
10362306a36Sopenharmony_ci			      WM831X_BKUP_CHG_ILIM_MASK,
10462306a36Sopenharmony_ci			      reg);
10562306a36Sopenharmony_ci	if (ret != 0)
10662306a36Sopenharmony_ci		dev_err(wm831x->dev,
10762306a36Sopenharmony_ci			"Failed to set backup charger config: %d\n", ret);
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	wm831x_reg_lock(wm831x);
11062306a36Sopenharmony_ci}
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_cistatic int wm831x_backup_get_prop(struct power_supply *psy,
11362306a36Sopenharmony_ci				  enum power_supply_property psp,
11462306a36Sopenharmony_ci				  union power_supply_propval *val)
11562306a36Sopenharmony_ci{
11662306a36Sopenharmony_ci	struct wm831x_backup *devdata = dev_get_drvdata(psy->dev.parent);
11762306a36Sopenharmony_ci	struct wm831x *wm831x = devdata->wm831x;
11862306a36Sopenharmony_ci	int ret = 0;
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	ret = wm831x_reg_read(wm831x, WM831X_BACKUP_CHARGER_CONTROL);
12162306a36Sopenharmony_ci	if (ret < 0)
12262306a36Sopenharmony_ci		return ret;
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	switch (psp) {
12562306a36Sopenharmony_ci	case POWER_SUPPLY_PROP_STATUS:
12662306a36Sopenharmony_ci		if (ret & WM831X_BKUP_CHG_STS)
12762306a36Sopenharmony_ci			val->intval = POWER_SUPPLY_STATUS_CHARGING;
12862306a36Sopenharmony_ci		else
12962306a36Sopenharmony_ci			val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
13062306a36Sopenharmony_ci		break;
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
13362306a36Sopenharmony_ci		ret = wm831x_backup_read_voltage(wm831x, WM831X_AUX_BKUP_BATT,
13462306a36Sopenharmony_ci						val);
13562306a36Sopenharmony_ci		break;
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	case POWER_SUPPLY_PROP_PRESENT:
13862306a36Sopenharmony_ci		if (ret & WM831X_BKUP_CHG_STS)
13962306a36Sopenharmony_ci			val->intval = 1;
14062306a36Sopenharmony_ci		else
14162306a36Sopenharmony_ci			val->intval = 0;
14262306a36Sopenharmony_ci		break;
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	default:
14562306a36Sopenharmony_ci		ret = -EINVAL;
14662306a36Sopenharmony_ci		break;
14762306a36Sopenharmony_ci	}
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	return ret;
15062306a36Sopenharmony_ci}
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_cistatic enum power_supply_property wm831x_backup_props[] = {
15362306a36Sopenharmony_ci	POWER_SUPPLY_PROP_STATUS,
15462306a36Sopenharmony_ci	POWER_SUPPLY_PROP_VOLTAGE_NOW,
15562306a36Sopenharmony_ci	POWER_SUPPLY_PROP_PRESENT,
15662306a36Sopenharmony_ci};
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci/*********************************************************************
15962306a36Sopenharmony_ci *		Initialisation
16062306a36Sopenharmony_ci *********************************************************************/
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_cistatic int wm831x_backup_probe(struct platform_device *pdev)
16362306a36Sopenharmony_ci{
16462306a36Sopenharmony_ci	struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent);
16562306a36Sopenharmony_ci	struct wm831x_pdata *wm831x_pdata = wm831x->dev->platform_data;
16662306a36Sopenharmony_ci	struct wm831x_backup *devdata;
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci	devdata = devm_kzalloc(&pdev->dev, sizeof(struct wm831x_backup),
16962306a36Sopenharmony_ci				GFP_KERNEL);
17062306a36Sopenharmony_ci	if (devdata == NULL)
17162306a36Sopenharmony_ci		return -ENOMEM;
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	devdata->wm831x = wm831x;
17462306a36Sopenharmony_ci	platform_set_drvdata(pdev, devdata);
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	/* We ignore configuration failures since we can still read
17762306a36Sopenharmony_ci	 * back the status without enabling the charger (which may
17862306a36Sopenharmony_ci	 * already be enabled anyway).
17962306a36Sopenharmony_ci	 */
18062306a36Sopenharmony_ci	wm831x_config_backup(wm831x);
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	if (wm831x_pdata && wm831x_pdata->wm831x_num)
18362306a36Sopenharmony_ci		snprintf(devdata->name, sizeof(devdata->name),
18462306a36Sopenharmony_ci			 "wm831x-backup.%d", wm831x_pdata->wm831x_num);
18562306a36Sopenharmony_ci	else
18662306a36Sopenharmony_ci		snprintf(devdata->name, sizeof(devdata->name),
18762306a36Sopenharmony_ci			 "wm831x-backup");
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	devdata->backup_desc.name = devdata->name;
19062306a36Sopenharmony_ci	devdata->backup_desc.type = POWER_SUPPLY_TYPE_BATTERY;
19162306a36Sopenharmony_ci	devdata->backup_desc.properties = wm831x_backup_props;
19262306a36Sopenharmony_ci	devdata->backup_desc.num_properties = ARRAY_SIZE(wm831x_backup_props);
19362306a36Sopenharmony_ci	devdata->backup_desc.get_property = wm831x_backup_get_prop;
19462306a36Sopenharmony_ci	devdata->backup = power_supply_register(&pdev->dev,
19562306a36Sopenharmony_ci						&devdata->backup_desc, NULL);
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	return PTR_ERR_OR_ZERO(devdata->backup);
19862306a36Sopenharmony_ci}
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_cistatic int wm831x_backup_remove(struct platform_device *pdev)
20162306a36Sopenharmony_ci{
20262306a36Sopenharmony_ci	struct wm831x_backup *devdata = platform_get_drvdata(pdev);
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci	power_supply_unregister(devdata->backup);
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci	return 0;
20762306a36Sopenharmony_ci}
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_cistatic struct platform_driver wm831x_backup_driver = {
21062306a36Sopenharmony_ci	.probe = wm831x_backup_probe,
21162306a36Sopenharmony_ci	.remove = wm831x_backup_remove,
21262306a36Sopenharmony_ci	.driver = {
21362306a36Sopenharmony_ci		.name = "wm831x-backup",
21462306a36Sopenharmony_ci	},
21562306a36Sopenharmony_ci};
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_cimodule_platform_driver(wm831x_backup_driver);
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ciMODULE_DESCRIPTION("Backup battery charger driver for WM831x PMICs");
22062306a36Sopenharmony_ciMODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
22162306a36Sopenharmony_ciMODULE_LICENSE("GPL");
22262306a36Sopenharmony_ciMODULE_ALIAS("platform:wm831x-backup");
223