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