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

From: Jeff LaBundy
Date: Sat Nov 25 2023 - 19:53:29 EST


Hi James,

On Wed, Nov 01, 2023 at 08:46:12PM +0000, James Ogletree wrote:
> Hi Jeff,
>
> You’ve given such great feedback covering many aspects of the
> driver, on this patch and the whole series, which has required very
> careful consideration, so thank you for your patience.

Your high-quality submissions make the review process efficient and
productive :) Please excuse my delayed response.

>
> > On Oct 24, 2023, at 9:04 PM, Jeff LaBundy <jeff@xxxxxxxxxxx> wrote:
> >
> > On Wed, Oct 18, 2023 at 05:57:23PM +0000, James Ogletree wrote:
> >>
> >> +static int cs_hap_pseq_init(struct cs_hap *haptics)
> >> +{
> >> + struct cs_hap_pseq_op *op;
> >> + int error, i, num_words;
> >> + u8 operation;
> >> + u32 *words;
> >> +
> >> + if (!haptics->dsp.pseq_size || !haptics->dsp.pseq_reg)
> >> + return 0;
> >> +
> >> + INIT_LIST_HEAD(&haptics->pseq_head);
> >
> > Anything that allocates or initializes an element that is normally held
> > in a driver's private data, like a list head or mutex, belongs in probe()
> > in my opinion. It's less of an issue here, but for more complex cases
> > where we may set something up in probe() and tear it down in remove(),
> > the driver is easier to maintain if helper functions such as cs_hap_pseq_init()
> > only manipulate or organize the data, rather than absorb additional work.
>
> I agree with your reasoning, however, doesn’t this then turn on the question of
> who the rightful owner of the write sequencer code is? If the pseq code
> belongs in the MFD driver, it ought to be moved to the driver’s private data
> structure there, however if it fits here, then not so. A counter example would
> be cs_dsp.c, a library which contains within it some INIT_LIST_HEAD calls.
> Perhaps the dust should settle on that dispute, and in regards to that, please
> read my comments below.

I think the list head belongs in the MFD's private data (cs40l50_private)
because the pseq code belongs in the MFD; more details as to why below.

>
> >> +
> >> + 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.

> This leaves either the input driver or the library, and it makes more
> sense to be in the library since it is shared code with L26. This was
> my reasoning, let me know whether you think it is sound.

I'm not sold on the input driver and the methods in what is now an FF
library living in separate files; more on that below.

>
> > 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.
>

If the core functions (e.g. firmware loading and interrupt handling) of L26
and L50 are drastically different, then it's perfectly acceptable to have
different MFD drivers for the two. There are plenty of examples of devices
throughout MFD who are close cousins to one another.

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.

2. That common code encapsulates too many things (e.g. pseq facilities). A
library may make sense, but not restricted to input children.

For code that is common to either MFD (e.g. pseq), it is largely a judgement
call and maintainer preference as to where that code lives. One approach could
be to simply include all common code in drivers/mfd/cs40l50.c today. When
drivers/mfd/cs40l26.c lands, it can simply #include <linux/mfd/cs40l50.h>, and
its Kconfig can select MFD_CS40L50. Both modules would get built, but only one
would bind and probe.

Another approach could be to move the pseq code from the L50 MFD into a library
(e.g. drivers/mfs/cs40l50-common.c) selected by both L50 and L26 Kconfigs. That
is of course some rip-up, but at least the code is not jumping subsystems at this
point, which it otherwise might if you leave it in an input library for now.

That being said, you are the one who is responsible for supporting the driver in
the future, so the decision is ultimately yours. You have a much better sense of
the hardware variants and the relationships among them. It's a delicate balance
of preparing for what customers may request, and any decision will inevitably be
wrong in some way :) I think the important thing is to not restrict yourself too
much up front, and my humble opinion is that slightly redrawing the dotted lines
in your "block diagram" may ease some pain down the road.

>
> >> +int cs_hap_pseq_write(struct cs_hap *haptics, u32 addr,
> >> + u32 data, bool update, u8 op_code)
> >> +{
> >> + struct cs_hap_pseq_op *op, *op_end, *op_new;
> >> + struct cs_dsp_chunk ch;
> >> + u32 pseq_bytes;
> >> + int error;
> >> +
> >> + op_new = devm_kzalloc(haptics->dev, sizeof(*op_new), GFP_KERNEL);
> >> + if (!op_new)
> >> + return -ENOMEM;
> >> +
> >> + op_new->operation = op_code;
> >> +
> >> + ch = cs_dsp_chunk((void *) op_new->words,
> >> + PSEQ_OP_WRITE_FULL_WORDS * sizeof(u32));
> >> + cs_dsp_chunk_write(&ch, 8, op_code);
> >> + switch (op_code) {
> >> + case PSEQ_OP_WRITE_FULL:
> >> + cs_dsp_chunk_write(&ch, 32, addr);
> >> + cs_dsp_chunk_write(&ch, 32, data);
> >> + break;
> >> + case PSEQ_OP_WRITE_L16:
> >> + case PSEQ_OP_WRITE_H16:
> >> + cs_dsp_chunk_write(&ch, 24, addr);
> >> + cs_dsp_chunk_write(&ch, 16, data);
> >> + break;
> >> + default:
> >> + error = -EINVAL;
> >> + goto op_new_free;
> >> + }
> >> +
> >> + op_new->size = cs_dsp_chunk_bytes(&ch);
> >> +
> >> + if (update) {
> >> + op = cs_hap_pseq_find_op(op_new, &haptics->pseq_head);
> >> + if (!op) {
> >> + error = -EINVAL;
> >> + goto op_new_free;
> >> + }
> >> + }
> >
> > It seems we are relying on the developer to remember if he or she has
> > already written 'addr' using a previous call to cs_hap_pseq_write(),
> > then set the update flag accordingly; is that accurate?
> >
> > If so, that is a high risk for bugs to be introduced as the driver is
> > maintained. Can we not search for an existing address/data entry upon
> > any call to cs_hap_pseq_write() using cs_hap_pseq_find_op(), and add
> > or replace a new address/data entry automatically?
>
> There are currently sequences on L26 where we want to write to the same
> address twice. In such cases we wouldn't want the driver to automatically
> look up and update the first entry during the second write call, hence the
> need for the update flag. I think with this use case in mind, updating
> the entries automatically wouldn't be feasible.

ACK. Good call on returning -EINVAL if update is true yet the entry was
never added; that seems like a reasonable level of protection.

>
> >> +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).

>
> Best,
> James
>
>

Kind regards,
Jeff LaBundy