brintos

brintos / linux-shallow public Read only

0
0
Text · 6.6 KiB · cfb635f Raw
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