diff mbox series

[09/11] aspeed/smc: add DMA calibration settings

Message ID 20180831103816.13479-10-clg@kaod.org
State New
Headers show
Series aspeed: misc fixes and enhancements (SMC) | expand

Commit Message

Cédric Le Goater Aug. 31, 2018, 10:38 a.m. UTC
When doing calibration, the SPI clock rate in the CE0 Control Register
and the read delay cycles in the Read Timing Compensation Register are
replaced by bit[11:4] of the DMA Control Register.

Signed-off-by: Cédric Le Goater <clg@kaod.org>
---
 hw/ssi/aspeed_smc.c | 54 +++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 54 insertions(+)

Comments

Peter Maydell Sept. 18, 2018, 6:54 p.m. UTC | #1
On 31 August 2018 at 11:38, Cédric Le Goater <clg@kaod.org> wrote:
> When doing calibration, the SPI clock rate in the CE0 Control Register
> and the read delay cycles in the Read Timing Compensation Register are
> replaced by bit[11:4] of the DMA Control Register.
>
> Signed-off-by: Cédric Le Goater <clg@kaod.org>
> ---
>  hw/ssi/aspeed_smc.c | 54 +++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 54 insertions(+)
>
> diff --git a/hw/ssi/aspeed_smc.c b/hw/ssi/aspeed_smc.c
> index 534faec4c111..983066f5ad1d 100644
> --- a/hw/ssi/aspeed_smc.c
> +++ b/hw/ssi/aspeed_smc.c
> @@ -695,6 +695,56 @@ static uint64_t aspeed_smc_read(void *opaque, hwaddr addr, unsigned int size)
>      }
>  }
>
> +static uint8_t aspeed_smc_hclk_divisor(uint8_t hclk_mask)
> +{
> +    /* HCLK/1 .. HCLK/16 */
> +    const uint8_t hclk_divisors[] = {
> +        15, 7, 14, 6, 13, 5, 12, 4, 11, 3, 10, 2, 9, 1, 8, 0
> +    };
> +    int i;
> +
> +    for (i = 0; i < ARRAY_SIZE(hclk_divisors); i++) {
> +        if (hclk_mask == hclk_divisors[i]) {
> +            return i + 1;
> +        }
> +    }
> +
> +    qemu_log_mask(LOG_GUEST_ERROR, "invalid HCLK mask %x", hclk_mask);
> +    return 0;
> +}
> +
> +/*
> + * When doing calibration, the SPI clock rate in the CE0 Control
> + * Register and the read delay cycles in the Read Timing
> + * Compensation Register are replaced by bit[11:4] of the DMA
> + * Control Register.
> + */
> +static void aspeed_smc_dma_calibration(AspeedSMCState *s)
> +{
> +    uint8_t delay =
> +        (s->regs[R_DMA_CTRL] >> DMA_CTRL_DELAY_SHIFT) & DMA_CTRL_DELAY_MASK;
> +    uint8_t hclk_mask =
> +        (s->regs[R_DMA_CTRL] >> DMA_CTRL_FREQ_SHIFT) & DMA_CTRL_FREQ_MASK;
> +    uint8_t hclk_div = aspeed_smc_hclk_divisor(hclk_mask);
> +    uint32_t hclk_shift = (hclk_div - 1) << 2;
> +    uint8_t cs;
> +
> +    /* Only HCLK/1 - HCLK/5 have tunable delays */
> +    if (hclk_div && hclk_div < 6) {
> +        s->regs[s->r_timings] &= ~(0xf << hclk_shift);
> +        s->regs[s->r_timings] |= delay << hclk_shift;
> +    }
> +
> +    /*
> +     * TODO: choose CS depending on the DMA address. This is not used
> +     * on the field.
> +     */

Not entirely sure what you have in mind by "on the field" here?
Not used by Linux?

thanks
-- PMM
Cédric Le Goater Sept. 19, 2018, 7:01 a.m. UTC | #2
On 09/18/2018 08:54 PM, Peter Maydell wrote:
> On 31 August 2018 at 11:38, Cédric Le Goater <clg@kaod.org> wrote:
>> When doing calibration, the SPI clock rate in the CE0 Control Register
>> and the read delay cycles in the Read Timing Compensation Register are
>> replaced by bit[11:4] of the DMA Control Register.
>>
>> Signed-off-by: Cédric Le Goater <clg@kaod.org>
>> ---
>>  hw/ssi/aspeed_smc.c | 54 +++++++++++++++++++++++++++++++++++++++++++++
>>  1 file changed, 54 insertions(+)
>>
>> diff --git a/hw/ssi/aspeed_smc.c b/hw/ssi/aspeed_smc.c
>> index 534faec4c111..983066f5ad1d 100644
>> --- a/hw/ssi/aspeed_smc.c
>> +++ b/hw/ssi/aspeed_smc.c
>> @@ -695,6 +695,56 @@ static uint64_t aspeed_smc_read(void *opaque, hwaddr addr, unsigned int size)
>>      }
>>  }
>>
>> +static uint8_t aspeed_smc_hclk_divisor(uint8_t hclk_mask)
>> +{
>> +    /* HCLK/1 .. HCLK/16 */
>> +    const uint8_t hclk_divisors[] = {
>> +        15, 7, 14, 6, 13, 5, 12, 4, 11, 3, 10, 2, 9, 1, 8, 0
>> +    };
>> +    int i;
>> +
>> +    for (i = 0; i < ARRAY_SIZE(hclk_divisors); i++) {
>> +        if (hclk_mask == hclk_divisors[i]) {
>> +            return i + 1;
>> +        }
>> +    }
>> +
>> +    qemu_log_mask(LOG_GUEST_ERROR, "invalid HCLK mask %x", hclk_mask);
>> +    return 0;
>> +}
>> +
>> +/*
>> + * When doing calibration, the SPI clock rate in the CE0 Control
>> + * Register and the read delay cycles in the Read Timing
>> + * Compensation Register are replaced by bit[11:4] of the DMA
>> + * Control Register.
>> + */
>> +static void aspeed_smc_dma_calibration(AspeedSMCState *s)
>> +{
>> +    uint8_t delay =
>> +        (s->regs[R_DMA_CTRL] >> DMA_CTRL_DELAY_SHIFT) & DMA_CTRL_DELAY_MASK;
>> +    uint8_t hclk_mask =
>> +        (s->regs[R_DMA_CTRL] >> DMA_CTRL_FREQ_SHIFT) & DMA_CTRL_FREQ_MASK;
>> +    uint8_t hclk_div = aspeed_smc_hclk_divisor(hclk_mask);
>> +    uint32_t hclk_shift = (hclk_div - 1) << 2;
>> +    uint8_t cs;
>> +
>> +    /* Only HCLK/1 - HCLK/5 have tunable delays */
>> +    if (hclk_div && hclk_div < 6) {
>> +        s->regs[s->r_timings] &= ~(0xf << hclk_shift);
>> +        s->regs[s->r_timings] |= delay << hclk_shift;
>> +    }
>> +
>> +    /*
>> +     * TODO: choose CS depending on the DMA address. This is not used
>> +     * on the field.
>> +     */
> 
> Not entirely sure what you have in mind by "on the field" here?
> Not used by Linux?

Today, the FMC/SPI calibration using the DMA registers is done by the 
U-Boot bootloader from the SDK. I don't know of any other implementation 
a part from the patchset I just sent adding a new Aspeed FMC/SPI driver 
in U-Boot, which is not merged yet.

Linux uses a similar algorithm but without the DMA registers because 
they are not available on the non-FMC/SPI controllers.

That was for the "on the field" survey. 

As for the comment statement it self, 

There is not much reason choosing a DMA Flash address pointing to 
another CS, because the Read Timing Compensation Register values apply 
to all CS on the SPI bus. So the model limitation is not a big problem. 
Nevertheless we could compute the CS from the DMA address and the segment 
registers if needed. 

Thanks,

C. 
C.
diff mbox series

Patch

diff --git a/hw/ssi/aspeed_smc.c b/hw/ssi/aspeed_smc.c
index 534faec4c111..983066f5ad1d 100644
--- a/hw/ssi/aspeed_smc.c
+++ b/hw/ssi/aspeed_smc.c
@@ -695,6 +695,56 @@  static uint64_t aspeed_smc_read(void *opaque, hwaddr addr, unsigned int size)
     }
 }
 
+static uint8_t aspeed_smc_hclk_divisor(uint8_t hclk_mask)
+{
+    /* HCLK/1 .. HCLK/16 */
+    const uint8_t hclk_divisors[] = {
+        15, 7, 14, 6, 13, 5, 12, 4, 11, 3, 10, 2, 9, 1, 8, 0
+    };
+    int i;
+
+    for (i = 0; i < ARRAY_SIZE(hclk_divisors); i++) {
+        if (hclk_mask == hclk_divisors[i]) {
+            return i + 1;
+        }
+    }
+
+    qemu_log_mask(LOG_GUEST_ERROR, "invalid HCLK mask %x", hclk_mask);
+    return 0;
+}
+
+/*
+ * When doing calibration, the SPI clock rate in the CE0 Control
+ * Register and the read delay cycles in the Read Timing
+ * Compensation Register are replaced by bit[11:4] of the DMA
+ * Control Register.
+ */
+static void aspeed_smc_dma_calibration(AspeedSMCState *s)
+{
+    uint8_t delay =
+        (s->regs[R_DMA_CTRL] >> DMA_CTRL_DELAY_SHIFT) & DMA_CTRL_DELAY_MASK;
+    uint8_t hclk_mask =
+        (s->regs[R_DMA_CTRL] >> DMA_CTRL_FREQ_SHIFT) & DMA_CTRL_FREQ_MASK;
+    uint8_t hclk_div = aspeed_smc_hclk_divisor(hclk_mask);
+    uint32_t hclk_shift = (hclk_div - 1) << 2;
+    uint8_t cs;
+
+    /* Only HCLK/1 - HCLK/5 have tunable delays */
+    if (hclk_div && hclk_div < 6) {
+        s->regs[s->r_timings] &= ~(0xf << hclk_shift);
+        s->regs[s->r_timings] |= delay << hclk_shift;
+    }
+
+    /*
+     * TODO: choose CS depending on the DMA address. This is not used
+     * on the field.
+     */
+    cs = 0;
+    s->regs[s->r_ctrl0 + cs] &=
+        ~(CE_CTRL_CLOCK_FREQ_MASK << CE_CTRL_CLOCK_FREQ_SHIFT);
+    s->regs[s->r_ctrl0 + cs] |= CE_CTRL_CLOCK_FREQ(hclk_div);
+}
+
 /*
  * Accumulate the result of the reads to provide a checksum that will
  * be used to validate the read timing settings.
@@ -709,6 +759,10 @@  static void aspeed_smc_dma_checksum(AspeedSMCState *s)
         return;
     }
 
+    if (s->regs[R_DMA_CTRL] & DMA_CTRL_CALIB) {
+        aspeed_smc_dma_calibration(s);
+    }
+
     while (s->regs[R_DMA_LEN]) {
         cpu_physical_memory_read(s->regs[R_DMA_FLASH_ADDR], &data, 4);