[RFC 2/2] mmc: meson-mx-sdio: Add a driver for the Amlogic Meson8 and Meson8b SoCs
Martin Blumenstingl
martin.blumenstingl at googlemail.com
Fri Jun 9 14:51:36 PDT 2017
Hi Ulf,
On Wed, Jun 7, 2017 at 6:15 PM, Ulf Hansson <ulf.hansson at linaro.org> wrote:
>>> [...]
>>>
>>>> +static void meson_mx_mmc_apply_ios(struct mmc_host *mmc, struct mmc_ios *ios)
>>>> +{
>>>> + struct meson_mx_mmc_slot *slot = mmc_priv(mmc);
>>>> + unsigned long clk_rate = ios->clock;
>>>> + int ret;
>>>> +
>>>> + switch (ios->bus_width) {
>>>> + case MMC_BUS_WIDTH_1:
>>>> + meson_mx_mmc_mask_bits(mmc, MESON_MX_SDIO_CONF,
>>>> + MESON_MX_SDIO_CONF_BUS_WIDTH, 0);
>>>> + break;
>>>> +
>>>> + case MMC_BUS_WIDTH_4:
>>>> + meson_mx_mmc_mask_bits(mmc, MESON_MX_SDIO_CONF,
>>>> + MESON_MX_SDIO_CONF_BUS_WIDTH,
>>>> + MESON_MX_SDIO_CONF_BUS_WIDTH);
>>>> + break;
>>>> +
>>>> + case MMC_BUS_WIDTH_8:
>>>> + default:
>>>> + dev_err(mmc_dev(mmc), "unsupported bus width: %d\n",
>>>> + ios->bus_width);
>>>> + slot->error = -EINVAL;
>>>> + return;
>>>> + }
>>>> +
>>>> + if (clk_rate) {
>>>> + if (WARN_ON(clk_rate > mmc->f_max))
>>>> + clk_rate = mmc->f_max;
>>>> + else if (WARN_ON(clk_rate < mmc->f_min))
>>>> + clk_rate = mmc->f_min;
>>>> +
>>>> + ret = clk_set_rate(slot->host->cfg_div_clk, ios->clock);
>>>> + if (ret) {
>>>> + dev_warn(mmc_dev(mmc),
>>>> + "failed to set MMC clock to %lu: %d\n",
>>>> + clk_rate, ret);
>>>> + slot->error = ret;
>>>> + return;
>>>> + }
>>>> +
>>>> + mmc->actual_clock = clk_get_rate(slot->host->cfg_div_clk);
>>>> + }
>>>
>>> In some cases the mmc core request the clock rate to be zero (to gate
>>> the clock) which is needed to for example switch to UHS speed mode. If
>>> you intend to support that, you need to manage this at this point.
>> thanks for the explanation - unfortunately the lack of a datasheet
>> which properly describes the clocks makes this a bit hard to
>> implement.
>> the vendor driver simply ignores any set_ios request with clock = 0
>> it seems that there is also no dedicated "stop clock" bit (and we only
>> have a clock divider). all I could find is FORCE_HALT (bit 30 in the
>> IRQC register), where the vendor driver explains: "Force halt SDIO by
>> software. Halt in this sdio host controller means stop to transmit or
>> receive data from sd card. and then sd card clock will be shutdown.
>> Software can force to halt anytime, and hardware will automatically
>> halt the sdio when reading fifo is full or writing fifo is empty"
>>
>> should I simply remove that if (...) and try to assign 0 to the clock anyways?
>
> I am not sure. Perhaps you are left with clk_disable_unprepare(), and
> hope no other is using the clock.
>
> Although, I suggest you address this as separate change on top.
OK, getting the basic thing working first sounds like a good idea
> [...]
>
>>>> + if (!(cmd->flags & MMC_RSP_CRC))
>>>> + send |= MESON_MX_SDIO_SEND_RESP_WITHOUT_CRC7;
>>>> +
>>>> + if (cmd->flags & MMC_RSP_BUSY)
>>>> + send |= MESON_MX_SDIO_SEND_CHECK_DAT0_BUSY;
>>>
>>> In case the controller has HW support of busy detection, please
>>> consider to enable MMC_CAP_WAIT_WHILE_BUSY for this driver. Then also
>>> assign host->max_busy_timeout a good value.
>> the IRQS register has bit 4 (CMD_BUSY) - but apart from that there is
>> no other documentation (about timeout values, etc.). the vendor driver
>> also neither uses MMC_CAP_WAIT_WHILE_BUSY nor host->max_busy_timeout
>> should I leave this as it is?
>
> Please don't just leave it as is. This is an important thing to get right.
>
> You should be able to explore this area and see how the controller
> behaves without too much of documentation. Regarding timeouts, it may
> very well be that the controller don't have a timeout, which is why
> you need a software timeout. That's not so uncommon actually.
during my experiments I've never seen an interrupt when a command
timed out (nor could I find information about a timeout register in
the documentation). do you have any pointers (like a previous mail
where you've explained) how I can "explore the controller's timeout
behavior"?
> [...]
>
>>>
>>>> +static void meson_mx_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
>>>> +{
>>>> + struct meson_mx_mmc_slot *slot = mmc_priv(mmc);
>>>> +
>>>> + if (spin_trylock(&slot->host->lock)) {
>>>> + /*
>>>> + * only apply the mmc_ios if we are idle to not break any
>>>> + * ongoing transfer. in case we are busy meson_mx_mmc_start_cmd
>>>> + * will take care of applying the mmc_ios later on.
>>>> + */
>>>> + if (slot->host->status == MESON_MX_MMC_STATUS_IDLE)
>>>> + meson_mx_mmc_apply_ios(mmc, ios);
>>>
>>> No this doesn't work!
>>>
>>> In case the status != MESON_MX_MMC_STATUS_IDLE or if the attempt to
>>> take the lock fails, you will just silently ignore to set the new ios
>>> settings.
>>>
>>> The mmc core implements the mmc/sd/sdio specifications, so when you
>>> return from the ->set_ios() host ops, the mmc core relies on the host
>>> to have applied the settings to conform the the specs. You can not
>>> delay that to a later point.
>> OK, I did not know that this was part of the mmc/sd/sdio spec
>> the problem I (and the vendor driver) was trying to solve here is the
>> fact that we could end up with a call chain like this:
>> - slot0.set_ios(400 kHz)
>> - slot1.set_ios(50 MHz)
>> - slot0.request(cmd)
>> - slot1.request(cmd)
>> (or anything else, where the IOS change between the .set_ios and
>> .request invocation of the same slot)
>>
>> the main problem here is that the clock divider register bits are
>> shared across all slots!
>>
>> can I simply always apply the IOS here in .set_ios and then still
>> re-apply them in .request() if needed?
>
> Thinking more about this, and the answer is unfortunately *no*.
>
> The mmc core uses mmc_claim|release_host() to get exclusive access to
> operate the host via the host ops callbacks.
>
> For some cases, that involves a sequence of commands/requests being
> carried out via invoking to the host ops. During some of these
> sequences, one can definitely not allow to change ios, because another
> host/slot needs it in between. That will break the protocol - for
> sure.
>
> This also make me realize that the few other host drivers. which tries
> to supports multiple slots are all broken. On the other side, that
> just confirms my doubt about this; this is just a theoretical thing
> one want to support or used in environments that doesn't requires
> "product quality".
>
> So to be able to support "multiple slots", we need to teach the mmc
> core about hosts that supports multiple slots. This needs to be done
> in a way that mmc_claim_host() gets exclusive right to run a host, but
> without other hosts sharing the same controller are allowed to run
> in-between.
>
> It shouldn't be that hard to implement, although I strongly recommend
> you to address this in a second step. In other words, I suggest you to
> drop the entire multiple slot support in the first step, then we can
> deal with that on top instead.
many thanks for the detailed explanation again!
I would be fine with dropping multiple slot support for the moment
*if* we can agree on the fact that the devicetree binding can support
multiple slots in theory (my idea here is: keep the child-nodes with
compatible = "mmc-slot" mandatory - but only allow one such child node
for now). I don't want to break DT compatibility after a few
weeks/months for a new driver. is that OK for you as well?
> [...]
>
>>>> +static void meson_mx_mmc_enable_sdio_irq(struct mmc_host *mmc, int enable)
>>>> +{
>>>> + struct meson_mx_mmc_slot *slot = mmc_priv(mmc);
>>>> + unsigned long irqflags;
>>>> +
>>>> + spin_lock_irqsave(&slot->host->irq_lock, irqflags);
>>>> +
>>>> + meson_mx_mmc_mask_bits(mmc, MESON_MX_SDIO_MULT,
>>>> + MESON_MX_SDIO_MULT_PORT_SEL_MASK,
>>>> + FIELD_PREP(MESON_MX_SDIO_MULT_PORT_SEL_MASK,
>>>> + slot->id));
>>>> +
>>>> + /* ACK pending interrupt */
>>>> + meson_mx_mmc_mask_bits(mmc, MESON_MX_SDIO_IRQS,
>>>> + MESON_MX_SDIO_IRQS_IF_INT,
>>>> + MESON_MX_SDIO_IRQS_IF_INT);
>>>> +
>>>> + meson_mx_mmc_mask_bits(mmc, MESON_MX_SDIO_IRQC,
>>>> + MESON_MX_SDIO_IRQC_ARC_IF_INT_EN,
>>>> + enable ? MESON_MX_SDIO_IRQC_ARC_IF_INT_EN : 0);
>>>> +
>>>> + if (enable)
>>>> + slot->host->sdio_irq_slot = slot;
>>>> + else
>>>> + slot->host->sdio_irq_slot = NULL;
>>>
>>> This looks weird. You support up to three slots per host, but only one
>>> can do sdio_irq?
>>>
>>> BTW, what happens if there are is a ongoing data transfer on an SD
>>> card slot, while there is an SDIO irq raised on the SDIO card slot? Do
>>> you cope with this correctly?
>> good question indeed. I guess I'll remove SDIO interrupt support for
>> now as I am not sure how this is supposed to work in the vendor driver
>> (which just uses the last active slot to figure out if there was an
>> SDIO interrupt)
>
> If you drop the multiple slot support, this should be easier to
> verify. However, if you prefer adding it in step on top, I am of
> course fine with that as well.
I guess I'll postpone this until I have a working wifi driver (the
rtl8723bs driver doesn't seem to work for me due to whatever reason)
> [...]
>
>>> [...]
>>>
>>> Another overall comment, which relates to the host locking mechanism
>>> and the problem with ->set_ios(). Perhaps you can look into how the
>>> cavium mmc driver has solved the similar problems as it also manages
>>> several slots per host.
>> thank your for the hint with the cavium driver. using a semaphore to
>> serialize the requests of multiple slots seems a good idea too.
>> to avoid a "broken by design" RFC v2, can you please give me some more
>> details how the cavium driver code matches with the mmc/sd/sdio spec
>> and the mmc framework?
>
> Unfortunate no, you will have to ask the cavium folkz about these
> details. However, it may very well be that this is also broken,
> according to my comments about multiple slot support.
OK, I guess (like you explained above) it's easier to not look at any
maybe-broken (existing) implementations and try to add multi slot
support to the MMC core instead (I will open a separate topic for this
on linux-mmc once I have a bit more time)
>>
>> as I explained above the clock divider and bus-width register bits are
>> shared across all slots. so if one device operates in 1-bit mode while
>> the other uses 4-bit mode (or different clock rates, it just doesn't
>> matter) then we need to make sure that these settings stay the same
>> between .set_ios() and .request, until the request is completed
>> (regardless of whether that was successful or not/a timeout occurred).
>>
>> from what I can read in the cavium driver (where the situation seems
>> to be the same), it uses acquire_bus() at the beginning of
>> .set_ios/.request and release_bus() at the end of it and keeping a
>> backup of the registers which are modified during .set_ios. once it
>> switches to a different slot it restores the register values for the
>> new slot (this can also happen in .request). this solves the problem
>> of keeping the correct IOS when switching slots at any point
>
> Yes, then it seems like also this driver is broken.
>
>>
>> however, what I don't understand so far is how it synchronizes the
>> access to the CMD response bits which are read in the interrupt. what
>> if slot0 sends a command, then right after that (before the the card
>> in slot0 replied) slot1 sends a command -> how is it going to know to
>> which slot the response belongs? (I am facing the same problem with
>> the the meson-mx-sdio driver, the solution is to allow only one
>> request at a time and queue all other requests -> I am not sure if
>> this is good solution though)
>
> I assume the lock is held throughout the entire period serving a
> request. However that doesn't cover all scenarios, as explained above.
OK, I guess we're done with that topic now - let's skip multi slot
support for version one of my driver
Regards,
Martin
More information about the linux-amlogic
mailing list