brintos

brintos / linux-shallow public Read only

0
0
Text · 9.4 KiB · 962a6fd Raw
390 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Power supply driver for ChromeOS EC based Peripheral Device Charger.4 *5 * Copyright 2020 Google LLC.6 */7 8#include <linux/mod_devicetable.h>9#include <linux/module.h>10#include <linux/notifier.h>11#include <linux/platform_data/cros_ec_commands.h>12#include <linux/platform_data/cros_ec_proto.h>13#include <linux/platform_device.h>14#include <linux/power_supply.h>15#include <linux/slab.h>16#include <linux/stringify.h>17#include <linux/types.h>18#include <linux/unaligned.h>19 20#define DRV_NAME		"cros-ec-pchg"21#define PCHG_DIR_PREFIX		"peripheral"22#define PCHG_DIR_NAME		PCHG_DIR_PREFIX "%d"23#define PCHG_DIR_NAME_LENGTH \24		sizeof(PCHG_DIR_PREFIX __stringify(EC_PCHG_MAX_PORTS))25#define PCHG_CACHE_UPDATE_DELAY	msecs_to_jiffies(500)26 27struct port_data {28	int port_number;29	char name[PCHG_DIR_NAME_LENGTH];30	struct power_supply *psy;31	struct power_supply_desc psy_desc;32	int psy_status;33	int battery_percentage;34	int charge_type;35	struct charger_data *charger;36	unsigned long last_update;37};38 39struct charger_data {40	struct device *dev;41	struct cros_ec_dev *ec_dev;42	struct cros_ec_device *ec_device;43	int num_registered_psy;44	struct port_data *ports[EC_PCHG_MAX_PORTS];45	struct notifier_block notifier;46};47 48static enum power_supply_property cros_pchg_props[] = {49	POWER_SUPPLY_PROP_STATUS,50	POWER_SUPPLY_PROP_CHARGE_TYPE,51	POWER_SUPPLY_PROP_CAPACITY,52	POWER_SUPPLY_PROP_SCOPE,53};54 55static int cros_pchg_ec_command(const struct charger_data *charger,56				unsigned int version,57				unsigned int command,58				const void *outdata,59				unsigned int outsize,60				void *indata,61				unsigned int insize)62{63	struct cros_ec_dev *ec_dev = charger->ec_dev;64	struct cros_ec_command *msg;65	int ret;66 67	msg = kzalloc(struct_size(msg, data, max(outsize, insize)), GFP_KERNEL);68	if (!msg)69		return -ENOMEM;70 71	msg->version = version;72	msg->command = ec_dev->cmd_offset + command;73	msg->outsize = outsize;74	msg->insize = insize;75 76	if (outsize)77		memcpy(msg->data, outdata, outsize);78 79	ret = cros_ec_cmd_xfer_status(charger->ec_device, msg);80	if (ret >= 0 && insize)81		memcpy(indata, msg->data, insize);82 83	kfree(msg);84	return ret;85}86 87static const unsigned int pchg_cmd_version = 1;88 89static bool cros_pchg_cmd_ver_check(const struct charger_data *charger)90{91	struct ec_params_get_cmd_versions_v1 req;92	struct ec_response_get_cmd_versions rsp;93	int ret;94 95	req.cmd = EC_CMD_PCHG;96	ret = cros_pchg_ec_command(charger, 1, EC_CMD_GET_CMD_VERSIONS,97				   &req, sizeof(req), &rsp, sizeof(rsp));98	if (ret < 0) {99		dev_warn(charger->dev,100			 "Unable to get versions of EC_CMD_PCHG (err:%d)\n",101			 ret);102		return false;103	}104 105	return !!(rsp.version_mask & BIT(pchg_cmd_version));106}107 108static int cros_pchg_port_count(const struct charger_data *charger)109{110	struct ec_response_pchg_count rsp;111	int ret;112 113	ret = cros_pchg_ec_command(charger, 0, EC_CMD_PCHG_COUNT,114				   NULL, 0, &rsp, sizeof(rsp));115	if (ret < 0) {116		dev_warn(charger->dev,117			 "Unable to get number or ports (err:%d)\n", ret);118		return ret;119	}120 121	return rsp.port_count;122}123 124static int cros_pchg_get_status(struct port_data *port)125{126	struct charger_data *charger = port->charger;127	struct ec_params_pchg req;128	struct ec_response_pchg rsp;129	struct device *dev = charger->dev;130	int old_status = port->psy_status;131	int old_percentage = port->battery_percentage;132	int ret;133 134	req.port = port->port_number;135	ret = cros_pchg_ec_command(charger, pchg_cmd_version, EC_CMD_PCHG,136				   &req, sizeof(req), &rsp, sizeof(rsp));137	if (ret < 0) {138		dev_err(dev, "Unable to get port.%d status (err:%d)\n",139			port->port_number, ret);140		return ret;141	}142 143	switch (rsp.state) {144	case PCHG_STATE_RESET:145	case PCHG_STATE_INITIALIZED:146	case PCHG_STATE_ENABLED:147	default:148		port->psy_status = POWER_SUPPLY_STATUS_UNKNOWN;149		port->charge_type = POWER_SUPPLY_CHARGE_TYPE_NONE;150		break;151	case PCHG_STATE_DETECTED:152		port->psy_status = POWER_SUPPLY_STATUS_CHARGING;153		port->charge_type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;154		break;155	case PCHG_STATE_CHARGING:156		port->psy_status = POWER_SUPPLY_STATUS_CHARGING;157		port->charge_type = POWER_SUPPLY_CHARGE_TYPE_STANDARD;158		break;159	case PCHG_STATE_FULL:160		port->psy_status = POWER_SUPPLY_STATUS_FULL;161		port->charge_type = POWER_SUPPLY_CHARGE_TYPE_NONE;162		break;163	}164 165	port->battery_percentage = rsp.battery_percentage;166 167	if (port->psy_status != old_status ||168			port->battery_percentage != old_percentage)169		power_supply_changed(port->psy);170 171	dev_dbg(dev,172		"Port %d: state=%d battery=%d%%\n",173		port->port_number, rsp.state, rsp.battery_percentage);174 175	return 0;176}177 178static int cros_pchg_get_port_status(struct port_data *port, bool ratelimit)179{180	int ret;181 182	if (ratelimit &&183	    time_is_after_jiffies(port->last_update + PCHG_CACHE_UPDATE_DELAY))184		return 0;185 186	ret = cros_pchg_get_status(port);187	if (ret < 0)188		return ret;189 190	port->last_update = jiffies;191 192	return ret;193}194 195static int cros_pchg_get_prop(struct power_supply *psy,196			      enum power_supply_property psp,197			      union power_supply_propval *val)198{199	struct port_data *port = power_supply_get_drvdata(psy);200 201	switch (psp) {202	case POWER_SUPPLY_PROP_STATUS:203	case POWER_SUPPLY_PROP_CAPACITY:204	case POWER_SUPPLY_PROP_CHARGE_TYPE:205		cros_pchg_get_port_status(port, true);206		break;207	default:208		break;209	}210 211	switch (psp) {212	case POWER_SUPPLY_PROP_STATUS:213		val->intval = port->psy_status;214		break;215	case POWER_SUPPLY_PROP_CAPACITY:216		val->intval = port->battery_percentage;217		break;218	case POWER_SUPPLY_PROP_CHARGE_TYPE:219		val->intval = port->charge_type;220		break;221	case POWER_SUPPLY_PROP_SCOPE:222		val->intval = POWER_SUPPLY_SCOPE_DEVICE;223		break;224	default:225		return -EINVAL;226	}227 228	return 0;229}230 231static int cros_pchg_event(const struct charger_data *charger)232{233	int i;234 235	for (i = 0; i < charger->num_registered_psy; i++)236		cros_pchg_get_port_status(charger->ports[i], false);237 238	return NOTIFY_OK;239}240 241static int cros_ec_notify(struct notifier_block *nb,242			  unsigned long queued_during_suspend,243			  void *data)244{245	struct cros_ec_device *ec_dev = data;246	struct charger_data *charger =247			container_of(nb, struct charger_data, notifier);248	u32 host_event;249 250	if (ec_dev->event_data.event_type != EC_MKBP_EVENT_PCHG ||251			ec_dev->event_size != sizeof(host_event))252		return NOTIFY_DONE;253 254	host_event = get_unaligned_le32(&ec_dev->event_data.data.host_event);255 256	if (!(host_event & EC_MKBP_PCHG_DEVICE_EVENT))257		return NOTIFY_DONE;258 259	return cros_pchg_event(charger);260}261 262static int cros_pchg_probe(struct platform_device *pdev)263{264	struct device *dev = &pdev->dev;265	struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent);266	struct cros_ec_device *ec_device = ec_dev->ec_dev;267	struct power_supply_desc *psy_desc;268	struct charger_data *charger;269	struct power_supply *psy;270	struct port_data *port;271	struct notifier_block *nb;272	int num_ports;273	int ret;274	int i;275 276	charger = devm_kzalloc(dev, sizeof(*charger), GFP_KERNEL);277	if (!charger)278		return -ENOMEM;279 280	charger->dev = dev;281	charger->ec_dev = ec_dev;282	charger->ec_device = ec_device;283 284	platform_set_drvdata(pdev, charger);285 286	ret = cros_pchg_port_count(charger);287	if (ret <= 0) {288		/*289		 * This feature is enabled by the EC and the kernel driver is290		 * included by default for CrOS devices. Don't need to be loud291		 * since this error can be normal.292		 */293		dev_info(dev, "No peripheral charge ports (err:%d)\n", ret);294		return -ENODEV;295	}296 297	if (!cros_pchg_cmd_ver_check(charger)) {298		dev_err(dev, "EC_CMD_PCHG version %d isn't available.\n",299			pchg_cmd_version);300		return -EOPNOTSUPP;301	}302 303	num_ports = ret;304	if (num_ports > EC_PCHG_MAX_PORTS) {305		dev_err(dev, "Too many peripheral charge ports (%d)\n",306			num_ports);307		return -ENOBUFS;308	}309 310	dev_info(dev, "%d peripheral charge ports found\n", num_ports);311 312	for (i = 0; i < num_ports; i++) {313		struct power_supply_config psy_cfg = {};314 315		port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL);316		if (!port)317			return -ENOMEM;318 319		port->charger = charger;320		port->port_number = i;321		snprintf(port->name, sizeof(port->name), PCHG_DIR_NAME, i);322 323		psy_desc = &port->psy_desc;324		psy_desc->name = port->name;325		psy_desc->type = POWER_SUPPLY_TYPE_BATTERY;326		psy_desc->get_property = cros_pchg_get_prop;327		psy_desc->external_power_changed = NULL;328		psy_desc->properties = cros_pchg_props;329		psy_desc->num_properties = ARRAY_SIZE(cros_pchg_props);330		psy_cfg.drv_data = port;331 332		psy = devm_power_supply_register(dev, psy_desc, &psy_cfg);333		if (IS_ERR(psy))334			return dev_err_probe(dev, PTR_ERR(psy),335					"Failed to register power supply\n");336		port->psy = psy;337 338		charger->ports[charger->num_registered_psy++] = port;339	}340 341	if (!charger->num_registered_psy)342		return -ENODEV;343 344	nb = &charger->notifier;345	nb->notifier_call = cros_ec_notify;346	ret = blocking_notifier_chain_register(&ec_dev->ec_dev->event_notifier,347					       nb);348	if (ret < 0)349		dev_err(dev, "Failed to register notifier (err:%d)\n", ret);350 351	return 0;352}353 354#ifdef CONFIG_PM_SLEEP355static int __maybe_unused cros_pchg_resume(struct device *dev)356{357	struct charger_data *charger = dev_get_drvdata(dev);358 359	/*360	 * Sync all ports on resume in case reports from EC are lost during361	 * the last suspend.362	 */363	cros_pchg_event(charger);364 365	return 0;366}367#endif368 369static SIMPLE_DEV_PM_OPS(cros_pchg_pm_ops, NULL, cros_pchg_resume);370 371static const struct platform_device_id cros_pchg_id[] = {372	{ DRV_NAME, 0 },373	{}374};375MODULE_DEVICE_TABLE(platform, cros_pchg_id);376 377static struct platform_driver cros_pchg_driver = {378	.driver = {379		.name = DRV_NAME,380		.pm = &cros_pchg_pm_ops,381	},382	.probe = cros_pchg_probe,383	.id_table = cros_pchg_id,384};385 386module_platform_driver(cros_pchg_driver);387 388MODULE_LICENSE("GPL");389MODULE_DESCRIPTION("ChromeOS EC peripheral device charger");390