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