Re: [PATCH] ims-pcu: Add commands supported by the new version of the FW

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Andrey,

On Sat, Dec 21, 2013 at 11:16:48AM -0800, Andrey Smirnov wrote:
> New version of the PCU firmware supports two new commands:
>   - IMS_PCU_CMD_OFN_SET_CONFIG which allows to write data to the
>   registers of one finger navigation(OFN) chip present on the device
>   - IMS_PCU_CMD_OFN_GET_CONFIG which allows to read data form the
>   registers of said chip.
> 
> This commit adds two helper functions to use those commands and sysfs
> attributes to use them. It also exposes some OFN configuration
> parameters via sysfs.
> 
> Signed-off-by: Andrey Smirnov <andrew.smirnov@xxxxxxxxx>
> ---
>  drivers/input/misc/ims-pcu.c | 274 +++++++++++++++++++++++++++++++++++++++++--
>  1 file changed, 263 insertions(+), 11 deletions(-)
> 
> diff --git a/drivers/input/misc/ims-pcu.c b/drivers/input/misc/ims-pcu.c
> index e204f26..050c960 100644
> --- a/drivers/input/misc/ims-pcu.c
> +++ b/drivers/input/misc/ims-pcu.c
> @@ -68,6 +68,9 @@ struct ims_pcu {
>  	char bl_version[IMS_PCU_BL_VERSION_LEN];
>  	char reset_reason[IMS_PCU_BL_RESET_REASON_LEN];
>  	int update_firmware_status;
> +	u8 device_id;
> +
> +	u8 ofn_reg_addr;
>  
>  	struct usb_interface *ctrl_intf;
>  
> @@ -371,6 +374,8 @@ static void ims_pcu_destroy_gamepad(struct ims_pcu *pcu)
>  #define IMS_PCU_CMD_GET_DEVICE_ID	0xae
>  #define IMS_PCU_CMD_SPECIAL_INFO	0xb0
>  #define IMS_PCU_CMD_BOOTLOADER		0xb1	/* Pass data to bootloader */
> +#define IMS_PCU_CMD_OFN_SET_CONFIG	0xb3
> +#define IMS_PCU_CMD_OFN_GET_CONFIG	0xb4
>  
>  /* PCU responses */
>  #define IMS_PCU_RSP_STATUS		0xc0
> @@ -389,6 +394,9 @@ static void ims_pcu_destroy_gamepad(struct ims_pcu *pcu)
>  #define IMS_PCU_RSP_GET_DEVICE_ID	0xce
>  #define IMS_PCU_RSP_SPECIAL_INFO	0xd0
>  #define IMS_PCU_RSP_BOOTLOADER		0xd1	/* Bootloader response */
> +#define IMS_PCU_RSP_OFN_SET_CONFIG	0xd2
> +#define IMS_PCU_RSP_OFN_GET_CONFIG	0xd3
> +
>  
>  #define IMS_PCU_RSP_EVNT_BUTTONS	0xe0	/* Unsolicited, button state */
>  #define IMS_PCU_GAMEPAD_MASK		0x0001ff80UL	/* Bits 7 through 16 */
> @@ -1216,6 +1224,226 @@ ims_pcu_update_firmware_status_show(struct device *dev,
>  static DEVICE_ATTR(update_firmware_status, S_IRUGO,
>  		   ims_pcu_update_firmware_status_show, NULL);
>  
> +enum pcu_ofn_offsets {
> +	OFN_REG_RESULT_LSB_OFFSET = 2,
> +	OFN_REG_RESULT_MSB_OFFSET = 3,
> +};
> +
> +static ssize_t ims_pcu_ofn_reg_data_show(struct device *dev,
> +					 struct device_attribute *dattr,
> +					 char *buf)
> +{
> +	struct usb_interface *intf = to_usb_interface(dev);
> +	struct ims_pcu *pcu = usb_get_intfdata(intf);
> +	int error;
> +
> +	mutex_lock(&pcu->cmd_mutex);
> +
> +	error = ims_pcu_execute_command(pcu, OFN_GET_CONFIG,
> +					&pcu->ofn_reg_addr,
> +					sizeof(pcu->ofn_reg_addr));
> +	if (error >= 0) {
> +		const s16 result = pcu->cmd_buf[OFN_REG_RESULT_MSB_OFFSET] << 8 |
> +			           pcu->cmd_buf[OFN_REG_RESULT_LSB_OFFSET];

const here seems overkill.

> +		if (result < 0)
> +			error = result;
> +		else
> +			error = scnprintf(buf, PAGE_SIZE, "%x\n", (u8)result);

Why cast to u8?

> +	}
> +
> +	mutex_unlock(&pcu->cmd_mutex);
> +
> +	return error;
> +}
> +
> +static ssize_t ims_pcu_ofn_reg_data_store(struct device *dev,
> +					  struct device_attribute *dattr,
> +					  const char *buf, size_t count)
> +{
> +	struct usb_interface *intf = to_usb_interface(dev);
> +	struct ims_pcu *pcu = usb_get_intfdata(intf);
> +	int error;
> +	int value;
> +	u8 buffer[2];
> +
> +	error = kstrtoint(buf, 0, &value);
> +	if (error)
> +		return error;
> +
> +	buffer[0] = pcu->ofn_reg_addr;
> +	buffer[1] = (u8) value;

If you want u8 we have kstrtou8().

> +
> +	mutex_lock(&pcu->cmd_mutex);
> +
> +	error = ims_pcu_execute_command(pcu, OFN_SET_CONFIG,
> +					&buffer, sizeof(buffer));
> +
> +	mutex_unlock(&pcu->cmd_mutex);
> +
> +	if (!error) {
> +		const s16 result = pcu->cmd_buf[OFN_REG_RESULT_MSB_OFFSET] << 8 |
> +			           pcu->cmd_buf[OFN_REG_RESULT_LSB_OFFSET];

You should not be checking contents of pcu->cmd_buf after releasing
mutex as someone else could be accessing the same sysfs attribute and
stomping on your data.

> +		error = result;

Does firmware guarantee that it would return errors that make sense to
Linux?

> +	}
> +
> +	return error ?: count;
> +}
> +
> +static DEVICE_ATTR(ofn_reg_data, S_IRUGO | S_IWUSR,
> +		   ims_pcu_ofn_reg_data_show, ims_pcu_ofn_reg_data_store);
> +
> +static ssize_t ims_pcu_ofn_reg_addr_show(struct device *dev,
> +					 struct device_attribute *dattr,
> +					 char *buf)
> +{
> +	struct usb_interface *intf = to_usb_interface(dev);
> +	struct ims_pcu *pcu = usb_get_intfdata(intf);
> +	int error;
> +
> +	mutex_lock(&pcu->cmd_mutex);
> +	error = scnprintf(buf, PAGE_SIZE, "%x\n", pcu->ofn_reg_addr);
> +	mutex_unlock(&pcu->cmd_mutex);
> +
> +	return error;
> +}
> +
> +static ssize_t ims_pcu_ofn_reg_addr_store(struct device *dev,
> +					  struct device_attribute *dattr,
> +					  const char *buf, size_t count)
> +{
> +	struct usb_interface *intf = to_usb_interface(dev);
> +	struct ims_pcu *pcu = usb_get_intfdata(intf);
> +	int error;
> +	int value;
> +
> +	error = kstrtoint(buf, 0, &value);
> +	if (error)
> +		return error;

kstrtou8().

> +
> +	mutex_lock(&pcu->cmd_mutex);
> +	pcu->ofn_reg_addr = (u8) value;
> +	mutex_unlock(&pcu->cmd_mutex);
> +
> +	return error ?: count;
> +}
> +
> +static DEVICE_ATTR(ofn_reg_addr, S_IRUGO | S_IWUSR,
> +		   ims_pcu_ofn_reg_addr_show, ims_pcu_ofn_reg_addr_store);
> +
> +static ssize_t ims_pcu_ofn_bit_show(u8 addr, u8 nr,
> +				    struct device *dev,
> +				    struct device_attribute *dattr,
> +				    char *buf)
> +{
> +	struct usb_interface *intf = to_usb_interface(dev);
> +	struct ims_pcu *pcu = usb_get_intfdata(intf);
> +	int error;
> +
> +	mutex_lock(&pcu->cmd_mutex);
> +
> +	error = ims_pcu_execute_command(pcu, OFN_GET_CONFIG,
> +					&addr, sizeof(addr));
> +	if (error >= 0) {
> +		const s16 result = pcu->cmd_buf[OFN_REG_RESULT_MSB_OFFSET] << 8 |
> +			           pcu->cmd_buf[OFN_REG_RESULT_LSB_OFFSET];
> +		if (result < 0)
> +			error = result;
> +		else
> +			error = scnprintf(buf, PAGE_SIZE, "%d\n",
> +					  !!(result & (1 << nr)));
> +	}
> +
> +	mutex_unlock(&pcu->cmd_mutex);
> +	return error;
> +}
> +
> +static ssize_t ims_pcu_ofn_bit_store(u8 addr, u8 nr,
> +				     struct device *dev,
> +				     struct device_attribute *dattr,
> +				     const char *buf, size_t count)
> +{
> +	struct usb_interface *intf = to_usb_interface(dev);
> +	struct ims_pcu *pcu = usb_get_intfdata(intf);
> +	int error;
> +	int value;
> +	u8 contents;
> +
> +
> +	error = kstrtoint(buf, 0, &value);
> +	if (error)
> +		return error;
> +
> +	if (value > 1)
> +		return -EINVAL;
> +
> +	mutex_lock(&pcu->cmd_mutex);
> +
> +	error = ims_pcu_execute_command(pcu, OFN_GET_CONFIG,
> +					&addr, sizeof(addr));
> +	if (error < 0)
> +		goto exit;
> +
> +	{

Generally dislike artificial code blocks. Please declare all needed
variables upfront.

> +		const s16 result = pcu->cmd_buf[OFN_REG_RESULT_MSB_OFFSET] << 8 |
> +			           pcu->cmd_buf[OFN_REG_RESULT_LSB_OFFSET];
> +		if (result < 0) {
> +			error = result;
> +			goto exit;
> +		}
> +		contents = (u8) result;
> +	}
> +
> +	if (value)
> +		contents |= 1 << nr;
> +	else
> +		contents &= ~(1 << nr);
> +
> +	{
> +		const u8 buffer[] = { addr, contents };
> +		error = ims_pcu_execute_command(pcu, OFN_SET_CONFIG,
> +						&buffer, sizeof(buffer));
> +	}
> +
> +exit:
> +	mutex_unlock(&pcu->cmd_mutex);
> +
> +	if (!error) {
> +		const s16 result = pcu->cmd_buf[OFN_REG_RESULT_MSB_OFFSET] << 8 |
> +			           pcu->cmd_buf[OFN_REG_RESULT_LSB_OFFSET];
> +		error = result;
> +	}
> +
> +	return error ?: count;
> +}
> +
> +
> +#define IMS_PCU_BIT_ATTR(name, addr, nr)				\
> +	static ssize_t ims_pcu_##name##_show(struct device *dev,	\
> +					     struct device_attribute *dattr, \
> +					     char *buf)			\
> +	{								\
> +		return ims_pcu_ofn_bit_show(addr, nr, dev, dattr, buf);	\
> +	}								\
> +									\
> +	static ssize_t ims_pcu_##name##_store(struct device *dev,	\
> +					      struct device_attribute *dattr, \
> +					      const char *buf, size_t count) \
> +	{								\
> +		return ims_pcu_ofn_bit_store(addr, nr, dev, dattr, buf, count); \
> +	}								\
> +									\
> +	static DEVICE_ATTR(name, S_IRUGO | S_IWUSR,			\
> +			   ims_pcu_##name##_show, ims_pcu_##name##_store)
> +
> +IMS_PCU_BIT_ATTR(ofn_engine_enable,   0x60, 7);
> +IMS_PCU_BIT_ATTR(ofn_speed_enable,    0x60, 6);
> +IMS_PCU_BIT_ATTR(ofn_assert_enable,   0x60, 5);
> +IMS_PCU_BIT_ATTR(ofn_xyquant_enable,  0x60, 4);
> +IMS_PCU_BIT_ATTR(ofn_xyscale_enable,  0x60, 1);
> +
> +IMS_PCU_BIT_ATTR(ofn_scale_x2,        0x63, 6);
> +IMS_PCU_BIT_ATTR(ofn_scale_y2,        0x63, 7);
> +
>  static struct attribute *ims_pcu_attrs[] = {
>  	&ims_pcu_attr_part_number.dattr.attr,
>  	&ims_pcu_attr_serial_number.dattr.attr,
> @@ -1226,6 +1454,18 @@ static struct attribute *ims_pcu_attrs[] = {
>  	&dev_attr_reset_device.attr,
>  	&dev_attr_update_firmware.attr,
>  	&dev_attr_update_firmware_status.attr,
> +
> +#define IMS_PCU_ATTRS_OFN_START_OFFSET (9)
> +
> +	&dev_attr_ofn_reg_data.attr,
> +	&dev_attr_ofn_reg_addr.attr,
> +	&dev_attr_ofn_engine_enable.attr,
> +	&dev_attr_ofn_speed_enable.attr,
> +	&dev_attr_ofn_assert_enable.attr,
> +	&dev_attr_ofn_xyquant_enable.attr,
> +	&dev_attr_ofn_xyscale_enable.attr,
> +	&dev_attr_ofn_scale_x2.attr,
> +	&dev_attr_ofn_scale_y2.attr,
>  	NULL
>  };
>  
> @@ -1244,8 +1484,21 @@ static umode_t ims_pcu_is_attr_visible(struct kobject *kobj,
>  			mode = 0;
>  		}
>  	} else {
> -		if (attr == &dev_attr_update_firmware_status.attr)
> +		if (attr == &dev_attr_update_firmware_status.attr) {
>  			mode = 0;
> +		} else if (pcu->device_id == 5) {
> +			/*
> +			   PCU-B devices, both GEN_1 and GEN_2(device_id == 5),
> +			   have no OFN sensor so exposing those attributes
> +			   for them does not make any sense
> +			*/
> +			int i;
> +			for (i = IMS_PCU_ATTRS_OFN_START_OFFSET; ims_pcu_attrs[i]; i++)
> +				if (attr == ims_pcu_attrs[i]) {
> +					mode = 0;
> +					break;
> +				}
> +		}
>  	}
>  
>  	return mode;
> @@ -1624,7 +1877,6 @@ static int ims_pcu_init_application_mode(struct ims_pcu *pcu)
>  	static atomic_t device_no = ATOMIC_INIT(0);
>  
>  	const struct ims_pcu_device_info *info;
> -	u8 device_id;
>  	int error;
>  
>  	error = ims_pcu_get_device_info(pcu);
> @@ -1633,7 +1885,7 @@ static int ims_pcu_init_application_mode(struct ims_pcu *pcu)
>  		return error;
>  	}
>  
> -	error = ims_pcu_identify_type(pcu, &device_id);
> +	error = ims_pcu_identify_type(pcu, &pcu->device_id);
>  	if (error) {
>  		dev_err(pcu->dev,
>  			"Failed to identify device, error: %d\n", error);
> @@ -1645,9 +1897,9 @@ static int ims_pcu_init_application_mode(struct ims_pcu *pcu)
>  		return 0;
>  	}
>  
> -	if (device_id >= ARRAY_SIZE(ims_pcu_device_info) ||
> -	    !ims_pcu_device_info[device_id].keymap) {
> -		dev_err(pcu->dev, "Device ID %d is not valid\n", device_id);
> +	if (pcu->device_id >= ARRAY_SIZE(ims_pcu_device_info) ||
> +	    !ims_pcu_device_info[pcu->device_id].keymap) {
> +		dev_err(pcu->dev, "Device ID %d is not valid\n", pcu->device_id);
>  		/* Same as above, punt to userspace */
>  		return 0;
>  	}
> @@ -1659,7 +1911,7 @@ static int ims_pcu_init_application_mode(struct ims_pcu *pcu)
>  	if (error)
>  		return error;
>  
> -	info = &ims_pcu_device_info[device_id];
> +	info = &ims_pcu_device_info[pcu->device_id];
>  	error = ims_pcu_setup_buttons(pcu, info->keymap, info->keymap_len);
>  	if (error)
>  		goto err_destroy_backlight;
> @@ -1783,14 +2035,14 @@ static int ims_pcu_probe(struct usb_interface *intf,
>  	if (error)
>  		goto err_stop_io;
>  
> -	error = sysfs_create_group(&intf->dev.kobj, &ims_pcu_attr_group);
> -	if (error)
> -		goto err_stop_io;
> -
>  	error = pcu->bootloader_mode ?
>  			ims_pcu_init_bootloader_mode(pcu) :
>  			ims_pcu_init_application_mode(pcu);
>  	if (error)
> +		goto err_stop_io;
> +
> +	error = sysfs_create_group(&intf->dev.kobj, &ims_pcu_attr_group);
> +	if (error)
>  		goto err_remove_sysfs;

Why did you move sysfs group creation? Now one can not observe progress
of firmware update...

Thanks.

-- 
Dmitry
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Linux Media Devel]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Linux Wireless Networking]     [Linux Omap]

  Powered by Linux