--- /dev/null
+// Copyright 2020 Google Inc. All Rights Reserved.
+
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/power_supply.h>
+#include <linux/thermal.h>
+
+struct usb_power_monitor_data {
+ struct device *dev;
+ struct gpio_desc *bc_chg_det_desc;
+ bool bc_chg_det_online;
+ struct power_supply *typec_psy;
+ bool typec_psy_online;
+ struct notifier_block usb_power_nb;
+ bool usb_power_nb_registered;
+ int power_max;
+};
+
+static const struct platform_device_id usb_power_monitor_id[] = {
+ { "usb_power_monitor", 0 },
+ { },
+};
+
+static const struct of_device_id usb_power_monitor_match[] = {
+ { .compatible = "google,usb-power-monitor",
+ .data = &usb_power_monitor_id[0] },
+ { },
+};
+
+static int usb_power_monitor_update_power_max(
+ struct usb_power_monitor_data *data, int power_max)
+{
+ if (data->power_max < power_max) {
+ data->power_max = power_max;
+ dev_info(data->dev, "power_max: %d", power_max);
+ }
+ return 0;
+}
+
+// return max power in micro-watt
+static int usb_power_monitor_get_power_max(struct usb_power_monitor_data *data)
+{
+ if ((data->bc_chg_det_desc && !data->bc_chg_det_online) ||
+ (data->typec_psy && !data->typec_psy_online)) {
+ dev_info(data->dev,
+ "Waiting for power supplies online, report 7.5W.");
+ return 7500000;
+ }
+
+ return data->power_max;
+}
+
+static int usb_power_monitor_notifier(struct notifier_block *nb,
+ unsigned long action, void *p)
+{
+ struct power_supply *psy = p;
+ struct usb_power_monitor_data *data =
+ container_of(nb, struct usb_power_monitor_data, usb_power_nb);
+ union power_supply_propval val;
+ int ret;
+
+ if (action != PSY_EVENT_PROP_CHANGED)
+ return NOTIFY_DONE;
+
+ if (data->typec_psy && psy == data->typec_psy) {
+ dev_info(data->dev, "notifier called, psy: %s\n",
+ psy->desc->name);
+
+ ret = power_supply_get_property(
+ psy, POWER_SUPPLY_PROP_ONLINE, &val);
+ if (ret) {
+ dev_err(data->dev, "%s: Failed to get online status.\n",
+ psy->desc->name);
+ data->typec_psy_online = false;
+ } else {
+ data->typec_psy_online = val.intval;
+ }
+
+ if (!data->typec_psy_online)
+ return NOTIFY_OK;
+
+ ret = power_supply_get_property(
+ psy, POWER_SUPPLY_PROP_CURRENT_MAX, &val);
+ if (ret)
+ dev_err(data->dev, "%s: Failed to get current max.\n",
+ psy->desc->name);
+ else
+ usb_power_monitor_update_power_max(
+ data, val.intval * 5);
+ } else {
+ return NOTIFY_DONE;
+ }
+
+ return NOTIFY_OK;
+}
+
+static ssize_t usb_power_monitor_show_power_max(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct usb_power_monitor_data *data = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%d\n", usb_power_monitor_get_power_max(data));
+}
+
+static SENSOR_DEVICE_ATTR(power1_max, 0444,
+ usb_power_monitor_show_power_max, NULL, 0);
+
+static struct attribute *usb_power_monitor_attrs[] = {
+ &sensor_dev_attr_power1_max.dev_attr.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(usb_power_monitor);
+
+
+static int usb_power_monitor_read_temp(void *data, int *temp)
+{
+ *temp = -usb_power_monitor_get_power_max(data);
+ return 0;
+}
+
+static const struct thermal_zone_of_device_ops
+usb_power_monitor_of_thermal_ops = {
+ .get_temp = usb_power_monitor_read_temp,
+};
+
+static int usb_power_monitor_probe(struct platform_device *pdev)
+{
+ struct thermal_zone_device *tz;
+ struct device *hwmon_dev;
+ struct device *dev = &pdev->dev;
+ const struct of_device_id *of_id =
+ of_match_device(of_match_ptr(usb_power_monitor_match), dev);
+ const struct platform_device_id *pdev_id;
+ struct usb_power_monitor_data *data;
+ struct power_supply *typec_psy;
+ struct gpio_desc *bc_chg_det_desc;
+ int ret;
+
+ data = devm_kzalloc(dev, sizeof(struct usb_power_monitor_data),
+ GFP_KERNEL);
+
+ data->dev = dev;
+
+ bc_chg_det_desc = devm_gpiod_get(dev, "bc_chg_det", GPIOD_IN);
+ if (IS_ERR_OR_NULL(bc_chg_det_desc))
+ dev_err(dev, "Failed to get bc_chg_det.\n");
+ else
+ data->bc_chg_det_desc = bc_chg_det_desc;
+
+ typec_psy = devm_power_supply_get_by_phandle(dev, "typec_power_supply");
+ if (IS_ERR_OR_NULL(typec_psy))
+ dev_err(dev, "Failed to get typec_power_supply.\n");
+ else
+ data->typec_psy = typec_psy;
+
+ if (of_id)
+ pdev->id_entry = of_id->data;
+
+ pdev_id = platform_get_device_id(pdev);
+
+ /* Default to 2.5W (USB2.0) */
+ usb_power_monitor_update_power_max(data, 2500000);
+
+ hwmon_dev = devm_hwmon_device_register_with_groups(
+ dev, pdev_id->name, data, usb_power_monitor_groups);
+ if (IS_ERR(hwmon_dev)) {
+ dev_err(dev, "unable to register as hwmon device.\n");
+ return PTR_ERR(hwmon_dev);
+ }
+
+ tz = devm_thermal_zone_of_sensor_register(
+ dev, 0, data, &usb_power_monitor_of_thermal_ops);
+ if (IS_ERR(tz))
+ dev_dbg(dev, "Failed to register to thermal fw.\n");
+
+ data->usb_power_nb.priority = 0;
+ data->usb_power_nb.notifier_call = usb_power_monitor_notifier;
+ ret = power_supply_reg_notifier(&data->usb_power_nb);
+ if (ret)
+ dev_err(dev, "Register power supply notifier failed.\n");
+ else
+ data->usb_power_nb_registered = true;
+
+ if (data->bc_chg_det_desc) {
+ if (gpiod_get_value(data->bc_chg_det_desc))
+ usb_power_monitor_update_power_max(data, 7500000);
+ data->bc_chg_det_online = true;
+ }
+
+ dev_info(dev, "USB power monitor type: %s successfully probed.\n",
+ pdev_id->name);
+
+ return 0;
+}
+
+static int usb_power_monitor_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct usb_power_monitor_data *data = dev_get_drvdata(dev);
+
+ if (data->usb_power_nb_registered)
+ power_supply_unreg_notifier(&data->usb_power_nb);
+
+ return 0;
+}
+
+static struct platform_driver usb_power_monitor_driver = {
+ .driver = {
+ .name = "usb-power-monitor",
+ .of_match_table = of_match_ptr(usb_power_monitor_match),
+ },
+ .probe = usb_power_monitor_probe,
+ .remove = usb_power_monitor_remove,
+ .id_table = usb_power_monitor_id,
+};
+
+module_platform_driver(usb_power_monitor_driver);
+
+MODULE_DESCRIPTION("Google USB Power Monitor Driver");
+MODULE_AUTHOR("Jacky Liu <qsliu@google.com>");
+MODULE_LICENSE("Proprietary");
+MODULE_ALIAS("platform:usb-power-monitor");