Re: [PATCH v4 2/4] Input: cs40l50 - Add cirrus haptics base support

From: Jeff LaBundy
Date: Wed Dec 13 2023 - 21:12:12 EST


Hi James,

Apologies for the delayed response.

On Wed, Nov 29, 2023 at 10:22:16PM +0000, James Ogletree wrote:
>
> >>>> +
> >>>> + words = kcalloc(haptics->dsp.pseq_size, sizeof(u32), GFP_KERNEL);
> >>>> + if (!words)
> >>>> + return -ENOMEM;
> >>>> +
> >>>> + error = regmap_bulk_read(haptics->regmap, haptics->dsp.pseq_reg,
> >>>> + words, haptics->dsp.pseq_size);
> >>>> + if (error)
> >>>> + goto err_free;
> >>>> +
> >>>> + for (i = 0; i < haptics->dsp.pseq_size; i += num_words) {
> >>>> + operation = FIELD_GET(PSEQ_OP_MASK, words[i]);
> >>>> + switch (operation) {
> >>>> + case PSEQ_OP_END:
> >>>> + case PSEQ_OP_WRITE_UNLOCK:
> >>>> + num_words = PSEQ_OP_END_WORDS;
> >>>> + break;
> >>>> + case PSEQ_OP_WRITE_ADDR8:
> >>>> + case PSEQ_OP_WRITE_H16:
> >>>> + case PSEQ_OP_WRITE_L16:
> >>>> + num_words = PSEQ_OP_WRITE_X16_WORDS;
> >>>> + break;
> >>>> + case PSEQ_OP_WRITE_FULL:
> >>>> + num_words = PSEQ_OP_WRITE_FULL_WORDS;
> >>>> + break;
> >>>> + default:
> >>>> + error = -EINVAL;
> >>>> + dev_err(haptics->dev, "Unsupported op: %u\n", operation);
> >>>> + goto err_free;
> >>>> + }
> >>>> +
> >>>> + op = devm_kzalloc(haptics->dev, sizeof(*op), GFP_KERNEL);
> >>>> + if (!op) {
> >>>> + error = -ENOMEM;
> >>>> + goto err_free;
> >>>> + }
> >>>> +
> >>>> + op->size = num_words * sizeof(u32);
> >>>> + memcpy(op->words, &words[i], op->size);
> >>>> + op->offset = i * sizeof(u32);
> >>>> + op->operation = operation;
> >>>> + list_add(&op->list, &haptics->pseq_head);
> >>>> +
> >>>> + if (operation == PSEQ_OP_END)
> >>>> + break;
> >>>> + }
> >>>> +
> >>>> + if (operation != PSEQ_OP_END)
> >>>> + error = -ENOENT;
> >>>> +
> >>>> +err_free:
> >>>> + kfree(words);
> >>>> +
> >>>> + return error;
> >>>> +}
> >>>
> >>> My first thought as I reviewed this patch was that this and the lot
> >>> of pseq-related functions are not necessarily related to haptics, but
> >>> rather CS40L50 register access and housekeeping in general.
> >>>
> >>> I seem to recall on L25 and friends that the the power-on sequencer,
> >>> i.e. PSEQ, is more or less a "tape recorder" of sorts in DSP memory
> >>> that can play back a series of address/data pairs when the device
> >>> comes out of hibernation, and any registers written during runtime
> >>> must also be mirrored to the PSEQ for "playback" later. Is that still
> >>> the case here?
> >>>
> >>> Assuming so, these functions seem like they belong in the MFD, not
> >>> an input-specific library, because they will presumably be used by
> >>> the codec driver as well, since that driver will write registers to
> >>> set BCLK/LRCK ratio, etc.
> >>>
> >>> Therefore, I think it makes more sense for these functions to move to
> >>> the MFD, which can then export them for use by the input/FF and codec
> >>> children.
> >>
> >> I think the problem with moving the write sequencer code to the MFD
> >> driver is that it would go unused in a codec-only environment. We only
> >> need to write to the PSEQ when we want to maintain register settings
> >> throughout hibernation cycles, and it isn’t possible to hibernate when
> >> there is data streaming to the device. So the PSEQ will never be used
> >> in the codec driver.
> >
> > I agree that in practice, the write sequencer would technically go unused
> > in a codec-only implementation. However, that is because the ASoC core
> > happens to write all pertinent registers ahead-of-time each time a stream
> > starts. That is a property of the ASoC core and not L50; my feeling is that
> > the driver should not be structured based on what one of the subsystems
> > associated with it happens to do, but rather the nature of the hardware.
> >
> > Some specific reasons I think the MFD is a better home for the pseq code:
> >
> > 1. The write sequencer is a housekeeping function derived from the way
> > the hardware implements its power management; it doesn't have anything
> > to do with haptics. My opinion is that facilities supporting application-
> > agnostic functions belong in the MFD, for all children to access, even
> > if only one happens to do so today.
> >
> > 2. Over the course of the IC's life, you may be required to add errata
> > writes after the IC is taken out of reset; these presumably would need
> > to be "scheduled" in the write sequencer as well. These wouldn't make
> > logical sense to add in the input driver, and they would be missed in
> > the theoretical codec-only case.
> >
> > 3. This device has a programmable DSP; it wouldn't be unheard of for a
> > customer to ask for a new function down the road that begets a third
> > child device. Perhaps a customer asks for a special .wmfw file that
> > repurposes the SDOUT pin as a PWM output for an LED, and now you must
> > add a pwm child. That's a made-up example of course, but in general we
> > want to structure things in such a way that rip-up is minimal in case
> > requirements change in the future.
>
> Great points. I agree now that the write sequencer code ought not to go in
> cirrus_haptics.c. After talking it over with the internal team, I am considering
> moving the write sequencer interface to cs_dsp.c. It’s an already existing
> library with both Cirrus haptics and audio users. It seems to dodge your
> concerns above and avoids a new common library as you suggested
> below. Do you have any concerns on this approach over putting it in MFD?

I think that's a great idea. Not every DSP-equipped device has a write
sequencer, but most, if not all write-sequencer-equipped devices have
a DSP. Adding the write sequencer code to cs_dsp.c seems like a natural
union of the two facilities.

>
>
> >>> This leaves cirrus_haptics.* with only a few functions related to
> >>> starting and stopping work, which seem specific enough to just live
> >>> in cs40l50-vibra.c. Presumably many of those could be re-used by
> >>> the L30 down the road, but in that case I think we'd be looking to
> >>> actually re-use the L50 driver and simply add a compatible string
> >>> for L30.
> >>>
> >>> I recall L30 has some overhead that L50 does not, which may make
> >>> it hairy for cs40l50-vibra.c to support both. But in that case,
> >>> you could always fork a cs40l30-vibra.c with its own compatible
> >>> string, then have the L50 MFD selectively load the correct child
> >>> based on device ID. To summarize, we should have:
> >>>
> >>> * drivers/mfd/cs40l50-core.c: MFD cell definition, device discovery,
> >>> IRQ handling, exported PSEQ functions, etc.
> >>> * sound/soc/codecs/cs40l50.c: codec driver, uses PSEQ library from
> >>> the MFD.
> >>> * drivers/input/misc/cs40l50-vibra.c: input/FF driver, start/stop
> >>> work, also uses PSEQ library from the MFD.
> >>>
> >>> And down the road, depending on complexity, maybe we also have:
> >>>
> >>> * drivers/input/misc/cs40l30-vibra.c: another input/FF driver that
> >>> includes other functionality that didn't really fit in cs40l50-vibra.c;
> >>> also uses PSEQ library from, and is loaded by, the original L50 MFD.
> >>> If this driver duplicates small bits of cs40l50-vibra.c, it's not the
> >>> end of the world.
> >>>
> >>> All of these files would #include include/linux/mfd/cs40l50.h. And
> >>> finally, cirrus_haptics.* simply go away. Same idea, just slightly
> >>> more scalable, and closer to common design patterns.
> >>
> >>
> >> I understand that it is a common design pattern to selectively load
> >> devices from the MFD driver. If I could summarize my thoughts on why
> >> that would not be fitting here, it’s that the L26 and L50 share a ton of
> >> input FF related work, and not enough “MFD core” related work.
> >> Between errata differences, power supply configurations, regmap
> >> configurations, interrupt register differences, it would seem to make for
> >> a very awkward, scrambled MFD driver. Moreover, I think I will be moving
> >> firmware download to the MFD driver, and that alone constitutes a
> >> significant incompatibility because firmware downloading is compulsory
> >> on L26, not so on L50.
> >>
> >> On the other hand, I want to emphasize the amount that L26 and
> >> L50 share when it comes to the Input FF callbacks. The worker
> >> functions in cirrus_haptics.c are bare-bones for this first
> >> submission, but were designed to be totally generic and scalable to
> >> the needs of L26 and all future Cirrus input drivers. While it might appear
> >> too specific for L26, everything currently in cirrus_haptics is usable by
> >> L26 as-is.
> >>
> >> For the above reasons I favor the current approach.
> >>
> >
> > Likewise, if the input-related functions of L26 and L50 are nearly identical,
> > then it's also perfectly acceptable for both drivers/mfd/cs40l26.c and
> > drivers/mfd/cs40l50.c to load drivers/input/misc/cs40l50-vibra.c, which
> > supports both L26 and L50 haptics-related functions. You're already doing
> > something similar, but I disagree on the following:
> >
> > 1. Rather than have a library referenced by two drivers that support children
> > which are largely the same in a logcial sense, just have a single driver that
> > supports two children.
>
> Your point here is clear and makes sense to me, especially now with the write
> sequencer interface moving out. After considering the similarities and
> differences closer, I am still a little wary. Maybe you can help me with these
> concerns:
>
> 1. In the current implementation, drivers would be able to configure their own
> input FF capabilities, and selectively register to input FF callbacks. L50 does
> not register to the set_gain callback, whereas L26 does. I anticipate future
> divergences, such as one driver supporting different effect types (see
> the L50-specific error checking in cs40l50_add()). This would be exacerbated
> by any future additional children.
>
> 2. This may be my lack of imagination, but in the current implementation it
> seems much easier to develop new haptic features that don’t apply to all the
> users of the library. One would simply wrap the feature in a boolean in
> cirrus_haptics, which clients can take or leave. In the one driver
> implementation, it seems you would have to find some clever, generalized
> way of determining whether or not a feature can be used. This would also
> seem to be exacerbated by any future additional children.
>
> 3. The current implementation provides for the individual drivers to setup
> the haptics interface in whatever way peculiar to that device, whether that
> interface be static (L50) or dependent on the loaded firmware (L26).
>

Maybe the solution here is to define a "descriptor", that is a struct which
reserves members for all things that can vary based on device (FF callback
pointers, input device capabilities, etc.). You then define an array of such
structs, with L50 being the only member, and L26 and others to come. Then,
cs40l50_vibra_probe() would simply parse the array index based on the device
ID hinted to it by the parent MFD.

As the person not doing the actual work, my naive opinion is that you face the
same problems whether two different drivers reference a library, or a common
driver supports two devices. The problem is therefore cosmetic, and if so, we
should tend toward a design pattern that seems to be most common in the input
subsystem, which is to support as many devices as possible with a single driver.

> Since I am moving around a lot of code in and out of both -vibra.c and the
> library for the next version, I think it would be helpful for me to wait until the
> next version is submitted to decide on this. Would that be acceptable?

I very much support moving in incremental steps; if you still feel strongly
against my suggestion, let us by all means evaluate your approach in the
context of the other changes.

>
> >
> >
> >>
> >>>> +static void cs_hap_vibe_stop_worker(struct work_struct *work)
> >>>> +{
> >>>> + struct cs_hap *haptics = container_of(work, struct cs_hap,
> >>>> + vibe_stop_work);
> >>>> + int error;
> >>>> +
> >>>> + if (haptics->runtime_pm) {
> >>>> + error = pm_runtime_resume_and_get(haptics->dev);
> >>>> + if (error < 0) {
> >>>> + haptics->stop_error = error;
> >>>> + return;
> >>>> + }
> >>>> + }
> >>>> +
> >>>> + mutex_lock(&haptics->lock);
> >>>> + error = regmap_write(haptics->regmap, haptics->dsp.mailbox_reg,
> >>>> + haptics->dsp.stop_cmd);
> >>>> + mutex_unlock(&haptics->lock);
> >>>> +
> >>>> + if (haptics->runtime_pm) {
> >>>> + pm_runtime_mark_last_busy(haptics->dev);
> >>>> + pm_runtime_put_autosuspend(haptics->dev);
> >>>> + }
> >>>> +
> >>>> + haptics->stop_error = error;
> >>>
> >>> This seems like another argument for not separating the input/FF child
> >>> from the meat of the driver; it just seems messy to pass around error
> >>> codes within a driver's private data like this.
> >>
> >> I removed the start_error and stop_error members. However I think the
> >> erase_error and add_error need to stay. I think this is more of a symptom
> >> of the Input FF layer requiring error reporting and having to use workqueues
> >> for those Input FF callbacks, than it is to do with the separation of these
> >> functions from the driver. Point being, even if these were moved, we would still
> >> need these *_error members. Let me know if I understood you right here.
> >
> > Sure, but why do adding and removing waveforms require workqueues? The DA7280
> > driver doesn't do this; what is different in this case? (That's a genuine
> > question, not an assertion that what you have is wrong, although it seems
> > unique based on my limited search).
>
> The reason why we have worker items for upload and erase input FF callbacks is
> because we need to ensure their ordering with playback worker items, and we need
> those worker items because the Input FF layer calls playbacks in atomic context.

Got it, that makes sense. Presumably, the act of adding or deleting waveforms
directly manipulates XMEM and YMEM regions of the DSP, and we cannot do so while
they are actively being read during playback. Acknowledged on all counts.

>
> Best,
> James
>
>

Kind regards,
Jeff LaBundy