/* * If firmware does not support the new interrupt API, we are informed * of every change of the status word by an interrupt from MCU and save * its value in the interrupt service routine. Simply return the saved * value.
*/ if (gpio->cmd == OMNIA_CMD_GET_STATUS_WORD &&
!(mcu->features & OMNIA_FEAT_NEW_INT_API)) return test_bit(gpio->bit, &mcu->last_status);
guard(mutex)(&mcu->lock);
/* * If firmware does support the new interrupt API, we may have cached * the value of a GPIO in the interrupt service routine. If not, read * the relevant bit now.
*/ if (is_int_bit_valid(gpio) && test_bit(gpio->int_bit, &mcu->is_cached)) return test_bit(gpio->int_bit, &mcu->cached);
/* determine which bits to read from the 3 possible commands */
for_each_set_bit(i, mask, ARRAY_SIZE(omnia_gpios)) {
field = _relevant_field_for_sts_cmd(omnia_gpios[i].cmd,
&sts, &ext_sts, &ext_ctl); if (!field) continue;
__set_bit(omnia_gpios[i].bit, field);
}
guard(mutex)(&mcu->lock);
if (mcu->features & OMNIA_FEAT_NEW_INT_API) { /* read relevant bits from status */
err = omnia_cmd_read_bits(client, OMNIA_CMD_GET_STATUS_WORD,
sts, &sts); if (err) return err;
} else { /* * Use status word value cached in the interrupt service routine * if firmware does not support the new interrupt API.
*/
sts = mcu->last_status;
}
/* read relevant bits from extended status */
err = omnia_cmd_read_bits(client, OMNIA_CMD_GET_EXT_STATUS_DWORD,
ext_sts, &ext_sts); if (err) return err;
/* read relevant bits from extended control */
err = omnia_cmd_read_bits(client, OMNIA_CMD_GET_EXT_CONTROL_STATUS,
ext_ctl, &ext_ctl); if (err) return err;
/* assign relevant bits in result */
for_each_set_bit(i, mask, ARRAY_SIZE(omnia_gpios)) {
field = _relevant_field_for_sts_cmd(omnia_gpios[i].cmd,
&sts, &ext_sts, &ext_ctl); if (!field) continue;
/* nothing to do if MCU firmware does not support new interrupt API */ if (!(mcu->features & OMNIA_FEAT_NEW_INT_API)) return;
mutex_lock(&mcu->lock);
}
/** * omnia_mask_interleave - Interleaves the bytes from @rising and @falling * @dst: the destination u8 array of interleaved bytes * @rising: rising mask * @falling: falling mask * * Interleaves the little-endian bytes from @rising and @falling words. * * If @rising = (r0, r1, r2, r3) and @falling = (f0, f1, f2, f3), the result is * @dst = (r0, f0, r1, f1, r2, f2, r3, f3). * * The MCU receives an interrupt mask and reports a pending interrupt bitmap in * this interleaved format. The rationale behind this is that the low-indexed * bits are more important - in many cases, the user will be interested only in * interrupts with indexes 0 to 7, and so the system can stop reading after * first 2 bytes (r0, f0), to save time on the slow I2C bus. * * Feel free to remove this function and its inverse, omnia_mask_deinterleave, * and use an appropriate bitmap_*() function once such a function exists.
*/ staticvoid
omnia_mask_interleave(u8 *dst, unsignedlong rising, unsignedlong falling)
{ for (unsignedint i = 0; i < sizeof(u32); i++) {
dst[2 * i] = rising >> (8 * i);
dst[2 * i + 1] = falling >> (8 * i);
}
}
/** * omnia_mask_deinterleave - Deinterleaves the bytes into @rising and @falling * @src: the source u8 array containing the interleaved bytes * @rising: pointer where to store the rising mask gathered from @src * @falling: pointer where to store the falling mask gathered from @src * * This is the inverse function to omnia_mask_interleave.
*/ staticvoid omnia_mask_deinterleave(const u8 *src, unsignedlong *rising, unsignedlong *falling)
{
*rising = *falling = 0;
for (unsignedint i = 0; i < sizeof(u32); i++) {
*rising |= src[2 * i] << (8 * i);
*falling |= src[2 * i + 1] << (8 * i);
}
}
/* interleave the rising and falling bytes into the command arguments */
omnia_mask_interleave(&cmd[1], rising, falling);
dev_dbg(dev, "set int mask %8ph\n", &cmd[1]);
err = omnia_cmd_write(mcu->client, cmd, sizeof(cmd)); if (err) {
dev_err(dev, "Cannot set mask: %d\n", err); goto unlock;
}
/* * Remember which GPIOs have both rising and falling interrupts enabled. * For those we will cache their value so that .get() method is faster. * We also need to forget cached values of GPIOs that aren't cached * anymore.
*/
mcu->both = rising & falling;
mcu->is_cached &= mcu->both;
/* * Determine how many bytes we need to read from the reply to the * OMNIA_CMD_GET_INT_AND_CLEAR command in order to retrieve all unmasked * interrupts.
*/ staticunsignedint
omnia_irq_compute_pending_length(unsignedlong rising, unsignedlong falling)
{ return max(omnia_compute_reply_length(rising, true, 0),
omnia_compute_reply_length(falling, true, 1));
}
err = omnia_cmd_read_u16(mcu->client, OMNIA_CMD_GET_STATUS_WORD,
&raw_status); if (err) return err;
/* * Old firmware has a bug wherein it never resets the USB port * overcurrent bits back to zero. Ignore them.
*/
*status = raw_status & ~(OMNIA_STS_USB30_OVC | OMNIA_STS_USB31_OVC);
/* * The old firmware triggers an interrupt whenever status word changes, * but does not inform about which bits rose or fell. We need to compute * this here by comparing with the last status word value. * * The OMNIA_STS_BUTTON_PRESSED bit needs special handling, because the * old firmware clears the OMNIA_STS_BUTTON_PRESSED bit on successful * completion of the OMNIA_CMD_GET_STATUS_WORD command, resulting in * another interrupt: * - first we get an interrupt, we read the status word where * OMNIA_STS_BUTTON_PRESSED is present, * - MCU clears the OMNIA_STS_BUTTON_PRESSED bit because we read the * status word, * - we get another interrupt because the status word changed again * (the OMNIA_STS_BUTTON_PRESSED bit was cleared). * * The gpiolib-cdev, gpiolib-sysfs and gpio-keys input driver all call * the gpiochip's .get() method after an edge event on a requested GPIO * occurs. * * We ensure that the .get() method reads 1 for the button GPIO for some * time.
*/
if (status & OMNIA_STS_BUTTON_PRESSED) {
mcu->button_pressed_emul = true;
mod_delayed_work(system_wq, &mcu->button_release_emul_work,
msecs_to_jiffies(FRONT_BUTTON_RELEASE_DELAY_MS));
} elseif (mcu->button_pressed_emul) {
status |= OMNIA_STS_BUTTON_PRESSED;
}
/* * Before requesting the interrupt, if firmware does not support the new * interrupt API, we need to cache the value of the status word, so that * when it changes, we may compare the new value with the cached one in * the interrupt handler.
*/ if (!new_api) {
err = omnia_read_status_word_old_fw(mcu, &mcu->last_status); if (err) return dev_err_probe(dev, err, "Cannot read status word\n");
if (!new_api) { /* * The button_release_emul_work has to be initialized before the * thread is requested, and on driver remove it needs to be * canceled before the thread is freed. Therefore we can't use * devm_delayed_work_autocancel() directly, because the order * devm_delayed_work_autocancel(); * devm_request_threaded_irq(); * would cause improper release order: * free_irq(); * cancel_delayed_work_sync(); * Instead we first initialize the work above, and only now * after IRQ is requested we add the work devm action.
*/
err = devm_add_action(dev, devm_delayed_work_drop,
&mcu->button_release_emul_work); if (err) return err;
}
return 0;
}
int omnia_mcu_request_irq(struct omnia_mcu *mcu, u32 spec,
irq_handler_t thread_fn, constchar *devname)
{
u8 irq_idx; int irq;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.