brintos

brintos / linux-shallow public Read only

0
0
Text · 10.9 KiB · e65d014 Raw
423 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * max8903_charger.c - Maxim 8903 USB/Adapter Charger Driver4 *5 * Copyright (C) 2011 Samsung Electronics6 * MyungJoo Ham <myungjoo.ham@samsung.com>7 */8 9#include <linux/gpio/consumer.h>10#include <linux/interrupt.h>11#include <linux/module.h>12#include <linux/of.h>13#include <linux/slab.h>14#include <linux/power_supply.h>15#include <linux/platform_device.h>16 17struct max8903_data {18	struct device *dev;19	struct power_supply *psy;20	struct power_supply_desc psy_desc;21	/*22	 * GPIOs23	 * chg, flt, dcm and usus are optional.24	 * dok or uok must be present.25	 * If dok is present, cen must be present.26	 */27	struct gpio_desc *cen; /* Charger Enable input */28	struct gpio_desc *dok; /* DC (Adapter) Power OK output */29	struct gpio_desc *uok; /* USB Power OK output */30	struct gpio_desc *chg; /* Charger status output */31	struct gpio_desc *flt; /* Fault output */32	struct gpio_desc *dcm; /* Current-Limit Mode input (1: DC, 2: USB) */33	struct gpio_desc *usus; /* USB Suspend Input (1: suspended) */34	bool fault;35	bool usb_in;36	bool ta_in;37};38 39static enum power_supply_property max8903_charger_props[] = {40	POWER_SUPPLY_PROP_STATUS, /* Charger status output */41	POWER_SUPPLY_PROP_ONLINE, /* External power source */42	POWER_SUPPLY_PROP_HEALTH, /* Fault or OK */43};44 45static int max8903_get_property(struct power_supply *psy,46		enum power_supply_property psp,47		union power_supply_propval *val)48{49	struct max8903_data *data = power_supply_get_drvdata(psy);50 51	switch (psp) {52	case POWER_SUPPLY_PROP_STATUS:53		val->intval = POWER_SUPPLY_STATUS_UNKNOWN;54		if (data->chg) {55			if (gpiod_get_value(data->chg))56				/* CHG asserted */57				val->intval = POWER_SUPPLY_STATUS_CHARGING;58			else if (data->usb_in || data->ta_in)59				val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;60			else61				val->intval = POWER_SUPPLY_STATUS_DISCHARGING;62		}63		break;64	case POWER_SUPPLY_PROP_ONLINE:65		val->intval = 0;66		if (data->usb_in || data->ta_in)67			val->intval = 1;68		break;69	case POWER_SUPPLY_PROP_HEALTH:70		val->intval = POWER_SUPPLY_HEALTH_GOOD;71		if (data->fault)72			val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;73		break;74	default:75		return -EINVAL;76	}77 78	return 0;79}80 81static irqreturn_t max8903_dcin(int irq, void *_data)82{83	struct max8903_data *data = _data;84	bool ta_in;85	enum power_supply_type old_type;86 87	/*88	 * This means the line is asserted.89	 *90	 * The signal is active low, but the inversion is handled in the GPIO91	 * library as the line should be flagged GPIO_ACTIVE_LOW in the device92	 * tree.93	 */94	ta_in = gpiod_get_value(data->dok);95 96	if (ta_in == data->ta_in)97		return IRQ_HANDLED;98 99	data->ta_in = ta_in;100 101	/* Set Current-Limit-Mode 1:DC 0:USB */102	if (data->dcm)103		gpiod_set_value(data->dcm, ta_in);104 105	/* Charger Enable / Disable */106	if (data->cen) {107		int val;108 109		if (ta_in)110			/* Certainly enable if DOK is asserted */111			val = 1;112		else if (data->usb_in)113			/* Enable if the USB charger is enabled */114			val = 1;115		else116			/* Else default-disable */117			val = 0;118 119		gpiod_set_value(data->cen, val);120	}121 122	dev_dbg(data->dev, "TA(DC-IN) Charger %s.\n", ta_in ?123			"Connected" : "Disconnected");124 125	old_type = data->psy_desc.type;126 127	if (data->ta_in)128		data->psy_desc.type = POWER_SUPPLY_TYPE_MAINS;129	else if (data->usb_in)130		data->psy_desc.type = POWER_SUPPLY_TYPE_USB;131	else132		data->psy_desc.type = POWER_SUPPLY_TYPE_BATTERY;133 134	if (old_type != data->psy_desc.type)135		power_supply_changed(data->psy);136 137	return IRQ_HANDLED;138}139 140static irqreturn_t max8903_usbin(int irq, void *_data)141{142	struct max8903_data *data = _data;143	bool usb_in;144	enum power_supply_type old_type;145 146	/*147	 * This means the line is asserted.148	 *149	 * The signal is active low, but the inversion is handled in the GPIO150	 * library as the line should be flagged GPIO_ACTIVE_LOW in the device151	 * tree.152	 */153	usb_in = gpiod_get_value(data->uok);154 155	if (usb_in == data->usb_in)156		return IRQ_HANDLED;157 158	data->usb_in = usb_in;159 160	/* Do not touch Current-Limit-Mode */161 162	/* Charger Enable / Disable */163	if (data->cen) {164		int val;165 166		if (usb_in)167			/* Certainly enable if UOK is asserted */168			val = 1;169		else if (data->ta_in)170			/* Enable if the DC charger is enabled */171			val = 1;172		else173			/* Else default-disable */174			val = 0;175 176		gpiod_set_value(data->cen, val);177	}178 179	dev_dbg(data->dev, "USB Charger %s.\n", usb_in ?180			"Connected" : "Disconnected");181 182	old_type = data->psy_desc.type;183 184	if (data->ta_in)185		data->psy_desc.type = POWER_SUPPLY_TYPE_MAINS;186	else if (data->usb_in)187		data->psy_desc.type = POWER_SUPPLY_TYPE_USB;188	else189		data->psy_desc.type = POWER_SUPPLY_TYPE_BATTERY;190 191	if (old_type != data->psy_desc.type)192		power_supply_changed(data->psy);193 194	return IRQ_HANDLED;195}196 197static irqreturn_t max8903_fault(int irq, void *_data)198{199	struct max8903_data *data = _data;200	bool fault;201 202	/*203	 * This means the line is asserted.204	 *205	 * The signal is active low, but the inversion is handled in the GPIO206	 * library as the line should be flagged GPIO_ACTIVE_LOW in the device207	 * tree.208	 */209	fault = gpiod_get_value(data->flt);210 211	if (fault == data->fault)212		return IRQ_HANDLED;213 214	data->fault = fault;215 216	if (fault)217		dev_err(data->dev, "Charger suffers a fault and stops.\n");218	else219		dev_err(data->dev, "Charger recovered from a fault.\n");220 221	return IRQ_HANDLED;222}223 224static int max8903_setup_gpios(struct platform_device *pdev)225{226	struct max8903_data *data = platform_get_drvdata(pdev);227	struct device *dev = &pdev->dev;228	bool ta_in = false;229	bool usb_in = false;230	enum gpiod_flags flags;231 232	data->dok = devm_gpiod_get_optional(dev, "dok", GPIOD_IN);233	if (IS_ERR(data->dok))234		return dev_err_probe(dev, PTR_ERR(data->dok),235				     "failed to get DOK GPIO");236	if (data->dok) {237		gpiod_set_consumer_name(data->dok, data->psy_desc.name);238		/*239		 * The DC OK is pulled up to 1 and goes low when a charger240		 * is plugged in (active low) but in the device tree the241		 * line is marked as GPIO_ACTIVE_LOW so we get a 1 (asserted)242		 * here if the DC charger is plugged in.243		 */244		ta_in = gpiod_get_value(data->dok);245	}246 247	data->uok = devm_gpiod_get_optional(dev, "uok", GPIOD_IN);248	if (IS_ERR(data->uok))249		return dev_err_probe(dev, PTR_ERR(data->uok),250				     "failed to get UOK GPIO");251	if (data->uok) {252		gpiod_set_consumer_name(data->uok, data->psy_desc.name);253		/*254		 * The USB OK is pulled up to 1 and goes low when a USB charger255		 * is plugged in (active low) but in the device tree the256		 * line is marked as GPIO_ACTIVE_LOW so we get a 1 (asserted)257		 * here if the USB charger is plugged in.258		 */259		usb_in = gpiod_get_value(data->uok);260	}261 262	/* Either DC OK or USB OK must be provided */263	if (!data->dok && !data->uok) {264		dev_err(dev, "no valid power source\n");265		return -EINVAL;266	}267 268	/*269	 * If either charger is already connected at this point,270	 * assert the CEN line and enable charging from the start.271	 *272	 * The line is active low but also marked with GPIO_ACTIVE_LOW273	 * in the device tree, so when we assert the line with274	 * GPIOD_OUT_HIGH the line will be driven low.275	 */276	flags = (ta_in || usb_in) ? GPIOD_OUT_HIGH : GPIOD_OUT_LOW;277	/*278	 * If DC OK is provided, Charger Enable CEN is compulsory279	 * so this is not optional here.280	 */281	data->cen = devm_gpiod_get(dev, "cen", flags);282	if (IS_ERR(data->cen))283		return dev_err_probe(dev, PTR_ERR(data->cen),284				     "failed to get CEN GPIO");285	gpiod_set_consumer_name(data->cen, data->psy_desc.name);286 287	/*288	 * If the DC charger is connected, then select it.289	 *290	 * The DCM line should be marked GPIO_ACTIVE_HIGH in the291	 * device tree. Driving it high will enable the DC charger292	 * input over the USB charger input.293	 */294	flags = ta_in ? GPIOD_OUT_HIGH : GPIOD_OUT_LOW;295	data->dcm = devm_gpiod_get_optional(dev, "dcm", flags);296	if (IS_ERR(data->dcm))297		return dev_err_probe(dev, PTR_ERR(data->dcm),298				     "failed to get DCM GPIO");299	gpiod_set_consumer_name(data->dcm, data->psy_desc.name);300 301	data->chg = devm_gpiod_get_optional(dev, "chg", GPIOD_IN);302	if (IS_ERR(data->chg))303		return dev_err_probe(dev, PTR_ERR(data->chg),304				     "failed to get CHG GPIO");305	gpiod_set_consumer_name(data->chg, data->psy_desc.name);306 307	data->flt = devm_gpiod_get_optional(dev, "flt", GPIOD_IN);308	if (IS_ERR(data->flt))309		return dev_err_probe(dev, PTR_ERR(data->flt),310				     "failed to get FLT GPIO");311	gpiod_set_consumer_name(data->flt, data->psy_desc.name);312 313	data->usus = devm_gpiod_get_optional(dev, "usus", GPIOD_IN);314	if (IS_ERR(data->usus))315		return dev_err_probe(dev, PTR_ERR(data->usus),316				     "failed to get USUS GPIO");317	gpiod_set_consumer_name(data->usus, data->psy_desc.name);318 319	data->fault = false;320	data->ta_in = ta_in;321	data->usb_in = usb_in;322 323	return 0;324}325 326static int max8903_probe(struct platform_device *pdev)327{328	struct max8903_data *data;329	struct device *dev = &pdev->dev;330	struct power_supply_config psy_cfg = {};331	int ret = 0;332 333	data = devm_kzalloc(dev, sizeof(struct max8903_data), GFP_KERNEL);334	if (!data)335		return -ENOMEM;336 337	data->dev = dev;338	platform_set_drvdata(pdev, data);339 340	ret = max8903_setup_gpios(pdev);341	if (ret)342		return ret;343 344	data->psy_desc.name = "max8903_charger";345	data->psy_desc.type = (data->ta_in) ? POWER_SUPPLY_TYPE_MAINS :346			((data->usb_in) ? POWER_SUPPLY_TYPE_USB :347			 POWER_SUPPLY_TYPE_BATTERY);348	data->psy_desc.get_property = max8903_get_property;349	data->psy_desc.properties = max8903_charger_props;350	data->psy_desc.num_properties = ARRAY_SIZE(max8903_charger_props);351 352	psy_cfg.of_node = dev->of_node;353	psy_cfg.drv_data = data;354 355	data->psy = devm_power_supply_register(dev, &data->psy_desc, &psy_cfg);356	if (IS_ERR(data->psy)) {357		dev_err(dev, "failed: power supply register.\n");358		return PTR_ERR(data->psy);359	}360 361	if (data->dok) {362		ret = devm_request_threaded_irq(dev, gpiod_to_irq(data->dok),363					NULL, max8903_dcin,364					IRQF_TRIGGER_FALLING |365					IRQF_TRIGGER_RISING | IRQF_ONESHOT,366					"MAX8903 DC IN", data);367		if (ret) {368			dev_err(dev, "Cannot request irq %d for DC (%d)\n",369					gpiod_to_irq(data->dok), ret);370			return ret;371		}372	}373 374	if (data->uok) {375		ret = devm_request_threaded_irq(dev, gpiod_to_irq(data->uok),376					NULL, max8903_usbin,377					IRQF_TRIGGER_FALLING |378					IRQF_TRIGGER_RISING | IRQF_ONESHOT,379					"MAX8903 USB IN", data);380		if (ret) {381			dev_err(dev, "Cannot request irq %d for USB (%d)\n",382					gpiod_to_irq(data->uok), ret);383			return ret;384		}385	}386 387	if (data->flt) {388		ret = devm_request_threaded_irq(dev, gpiod_to_irq(data->flt),389					NULL, max8903_fault,390					IRQF_TRIGGER_FALLING |391					IRQF_TRIGGER_RISING | IRQF_ONESHOT,392					"MAX8903 Fault", data);393		if (ret) {394			dev_err(dev, "Cannot request irq %d for Fault (%d)\n",395					gpiod_to_irq(data->flt), ret);396			return ret;397		}398	}399 400	return 0;401}402 403static const struct of_device_id max8903_match_ids[] = {404	{ .compatible = "maxim,max8903", },405	{ /* sentinel */ }406};407MODULE_DEVICE_TABLE(of, max8903_match_ids);408 409static struct platform_driver max8903_driver = {410	.probe	= max8903_probe,411	.driver = {412		.name	= "max8903-charger",413		.of_match_table = max8903_match_ids414	},415};416 417module_platform_driver(max8903_driver);418 419MODULE_LICENSE("GPL");420MODULE_DESCRIPTION("MAX8903 Charger Driver");421MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");422MODULE_ALIAS("platform:max8903-charger");423