276 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Copyright (C) 2023 Thomas Weißschuh <linux@weissschuh.net>4 */5#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt6 7#include <linux/completion.h>8#include <linux/device.h>9#include <linux/hwmon.h>10#include <linux/module.h>11#include <linux/mutex.h>12#include <linux/types.h>13#include <linux/usb.h>14 15#define DRIVER_NAME "powerz"16#define POWERZ_EP_CMD_OUT 0x0117#define POWERZ_EP_DATA_IN 0x8118 19struct powerz_sensor_data {20 u8 _unknown_1[8];21 __le32 V_bus;22 __le32 I_bus;23 __le32 V_bus_avg;24 __le32 I_bus_avg;25 u8 _unknown_2[8];26 u8 temp[2];27 __le16 V_cc1;28 __le16 V_cc2;29 __le16 V_dp;30 __le16 V_dm;31 __le16 V_dd;32 u8 _unknown_3[4];33} __packed;34 35struct powerz_priv {36 char transfer_buffer[64]; /* first member to satisfy DMA alignment */37 struct mutex mutex;38 struct completion completion;39 struct urb *urb;40 int status;41};42 43static const struct hwmon_channel_info *const powerz_info[] = {44 HWMON_CHANNEL_INFO(in,45 HWMON_I_INPUT | HWMON_I_LABEL | HWMON_I_AVERAGE,46 HWMON_I_INPUT | HWMON_I_LABEL,47 HWMON_I_INPUT | HWMON_I_LABEL,48 HWMON_I_INPUT | HWMON_I_LABEL,49 HWMON_I_INPUT | HWMON_I_LABEL,50 HWMON_I_INPUT | HWMON_I_LABEL),51 HWMON_CHANNEL_INFO(curr,52 HWMON_C_INPUT | HWMON_C_LABEL | HWMON_C_AVERAGE),53 HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_LABEL),54 NULL55};56 57static umode_t powerz_is_visible(const void *data, enum hwmon_sensor_types type,58 u32 attr, int channel)59{60 return 0444;61}62 63static int powerz_read_string(struct device *dev, enum hwmon_sensor_types type,64 u32 attr, int channel, const char **str)65{66 if (type == hwmon_curr && attr == hwmon_curr_label) {67 *str = "IBUS";68 } else if (type == hwmon_in && attr == hwmon_in_label) {69 if (channel == 0)70 *str = "VBUS";71 else if (channel == 1)72 *str = "VCC1";73 else if (channel == 2)74 *str = "VCC2";75 else if (channel == 3)76 *str = "VDP";77 else if (channel == 4)78 *str = "VDM";79 else if (channel == 5)80 *str = "VDD";81 else82 return -EOPNOTSUPP;83 } else if (type == hwmon_temp && attr == hwmon_temp_label) {84 *str = "TEMP";85 } else {86 return -EOPNOTSUPP;87 }88 89 return 0;90}91 92static void powerz_usb_data_complete(struct urb *urb)93{94 struct powerz_priv *priv = urb->context;95 96 complete(&priv->completion);97}98 99static void powerz_usb_cmd_complete(struct urb *urb)100{101 struct powerz_priv *priv = urb->context;102 103 usb_fill_bulk_urb(urb, urb->dev,104 usb_rcvbulkpipe(urb->dev, POWERZ_EP_DATA_IN),105 priv->transfer_buffer, sizeof(priv->transfer_buffer),106 powerz_usb_data_complete, priv);107 108 priv->status = usb_submit_urb(urb, GFP_ATOMIC);109 if (priv->status)110 complete(&priv->completion);111}112 113static int powerz_read_data(struct usb_device *udev, struct powerz_priv *priv)114{115 int ret;116 117 priv->status = -ETIMEDOUT;118 reinit_completion(&priv->completion);119 120 priv->transfer_buffer[0] = 0x0c;121 priv->transfer_buffer[1] = 0x00;122 priv->transfer_buffer[2] = 0x02;123 priv->transfer_buffer[3] = 0x00;124 125 usb_fill_bulk_urb(priv->urb, udev,126 usb_sndbulkpipe(udev, POWERZ_EP_CMD_OUT),127 priv->transfer_buffer, 4, powerz_usb_cmd_complete,128 priv);129 ret = usb_submit_urb(priv->urb, GFP_KERNEL);130 if (ret)131 return ret;132 133 if (!wait_for_completion_interruptible_timeout134 (&priv->completion, msecs_to_jiffies(5))) {135 usb_kill_urb(priv->urb);136 return -EIO;137 }138 139 if (priv->urb->actual_length < sizeof(struct powerz_sensor_data))140 return -EIO;141 142 return priv->status;143}144 145static int powerz_read(struct device *dev, enum hwmon_sensor_types type,146 u32 attr, int channel, long *val)147{148 struct usb_interface *intf = to_usb_interface(dev->parent);149 struct usb_device *udev = interface_to_usbdev(intf);150 struct powerz_priv *priv = usb_get_intfdata(intf);151 struct powerz_sensor_data *data;152 int ret;153 154 if (!priv)155 return -EIO; /* disconnected */156 157 mutex_lock(&priv->mutex);158 ret = powerz_read_data(udev, priv);159 if (ret)160 goto out;161 162 data = (struct powerz_sensor_data *)priv->transfer_buffer;163 164 if (type == hwmon_curr) {165 if (attr == hwmon_curr_input)166 *val = ((s32)le32_to_cpu(data->I_bus)) / 1000;167 else if (attr == hwmon_curr_average)168 *val = ((s32)le32_to_cpu(data->I_bus_avg)) / 1000;169 else170 ret = -EOPNOTSUPP;171 } else if (type == hwmon_in) {172 if (attr == hwmon_in_input) {173 if (channel == 0)174 *val = le32_to_cpu(data->V_bus) / 1000;175 else if (channel == 1)176 *val = le16_to_cpu(data->V_cc1) / 10;177 else if (channel == 2)178 *val = le16_to_cpu(data->V_cc2) / 10;179 else if (channel == 3)180 *val = le16_to_cpu(data->V_dp) / 10;181 else if (channel == 4)182 *val = le16_to_cpu(data->V_dm) / 10;183 else if (channel == 5)184 *val = le16_to_cpu(data->V_dd) / 10;185 else186 ret = -EOPNOTSUPP;187 } else if (attr == hwmon_in_average && channel == 0) {188 *val = le32_to_cpu(data->V_bus_avg) / 1000;189 } else {190 ret = -EOPNOTSUPP;191 }192 } else if (type == hwmon_temp && attr == hwmon_temp_input) {193 *val = data->temp[1] * 2000 + data->temp[0] * 1000 / 128;194 } else {195 ret = -EOPNOTSUPP;196 }197 198out:199 mutex_unlock(&priv->mutex);200 return ret;201}202 203static const struct hwmon_ops powerz_hwmon_ops = {204 .is_visible = powerz_is_visible,205 .read = powerz_read,206 .read_string = powerz_read_string,207};208 209static const struct hwmon_chip_info powerz_chip_info = {210 .ops = &powerz_hwmon_ops,211 .info = powerz_info,212};213 214static int powerz_probe(struct usb_interface *intf,215 const struct usb_device_id *id)216{217 struct powerz_priv *priv;218 struct device *hwmon_dev;219 struct device *parent;220 221 parent = &intf->dev;222 223 priv = devm_kzalloc(parent, sizeof(*priv), GFP_KERNEL);224 if (!priv)225 return -ENOMEM;226 227 priv->urb = usb_alloc_urb(0, GFP_KERNEL);228 if (!priv->urb)229 return -ENOMEM;230 mutex_init(&priv->mutex);231 init_completion(&priv->completion);232 233 hwmon_dev =234 devm_hwmon_device_register_with_info(parent, DRIVER_NAME, priv,235 &powerz_chip_info, NULL);236 if (IS_ERR(hwmon_dev)) {237 usb_free_urb(priv->urb);238 return PTR_ERR(hwmon_dev);239 }240 241 usb_set_intfdata(intf, priv);242 243 return 0;244}245 246static void powerz_disconnect(struct usb_interface *intf)247{248 struct powerz_priv *priv = usb_get_intfdata(intf);249 250 mutex_lock(&priv->mutex);251 usb_kill_urb(priv->urb);252 usb_free_urb(priv->urb);253 mutex_unlock(&priv->mutex);254}255 256static const struct usb_device_id powerz_id_table[] = {257 { USB_DEVICE_INTERFACE_NUMBER(0x5FC9, 0x0061, 0x00) }, /* ChargerLAB POWER-Z KM002C */258 { USB_DEVICE_INTERFACE_NUMBER(0x5FC9, 0x0063, 0x00) }, /* ChargerLAB POWER-Z KM003C */259 { }260};261 262MODULE_DEVICE_TABLE(usb, powerz_id_table);263 264static struct usb_driver powerz_driver = {265 .name = DRIVER_NAME,266 .id_table = powerz_id_table,267 .probe = powerz_probe,268 .disconnect = powerz_disconnect,269};270 271module_usb_driver(powerz_driver);272 273MODULE_LICENSE("GPL");274MODULE_AUTHOR("Thomas Weißschuh <linux@weissschuh.net>");275MODULE_DESCRIPTION("ChargerLAB POWER-Z USB-C tester");276