Re: [PATCH v2 4/6] bus: add driver for the Technologic Systems NBUS
From: Sebastien Bourdelin
Date: Wed Feb 22 2017 - 11:57:03 EST
Hi Linus,
First thanks for your code review!
On 02/04/2017 05:14 AM, Linus Walleij wrote:
> On Fri, Feb 3, 2017 at 8:47 PM, Sebastien Bourdelin
> <sebastien.bourdelin@xxxxxxxxxxxxxxxxxxxx> wrote:
>
>> This driver implements a GPIOs bit-banged bus, called the NBUS by
>> Technologic Systems. It is used to communicate with the peripherals in
>> the FPGA on the TS-4600 SoM.
>>
>> ---
>> Changes v1 -> v2:
>> - rebase on master
>> - the driver now populate its child nodes
>> - remove the 'default y' option from the Kconfig
>> - rework the driver to not use singleton anymore (suggested by Linus
>> Walleij)
>> - replace the use of the legacy GPIO API with gpiod (suggested by
>> Linus Walleij)
>> - use the ts vendor prefix for gpios (suggested by Rob Herring)
>>
>> Signed-off-by: Sebastien Bourdelin <sebastien.bourdelin@xxxxxxxxxxxxxxxxxxxx>
>
> This is starting to look nice!
>
> More comments:
>
> First, you sprinked "inline" like cinnamon over everything, skip that. The
> compiler will inline selectively as it seems fit if you turn on optimization.
>
Ok.
>> +static DEFINE_MUTEX(ts_nbus_lock);
>
> Move this into struct ts_nbus, initialize the mutex in probe()
> and just mutex_lock(&ts_nbus->lock); etc.
>
Ok.
>> + * the txrx gpio is used by the FPGA to know if the following transactions
>> + * should be handled to read or write a value.
>> + */
>> +static inline void ts_nbus_set_mode(struct ts_nbus *ts_nbus, int mode)
>
> Why inline? Let the compiler decide about that.
>
Ok.
>> +{
>> + if (mode == TS_NBUS_READ_MODE)
>
> This mode parameter is too complicated. Isn't it just a bool?
> (not superimportant)
>
>> + gpiod_set_value_cansleep(ts_nbus->txrx, 0);
>> + else
>> + gpiod_set_value_cansleep(ts_nbus->txrx, 1);
>> +}
>
> You're certainly just using it as a bool.
>
I will remove this function to simplify the logic.
>> +static inline void ts_nbus_set_direction(struct ts_nbus *ts_nbus, int direction)
>> +{
>> + int i;
>> +
>> + for (i = 0; i < ts_nbus->data->ndescs; i++) {
>> + if (direction == TS_NBUS_DIRECTION_IN)
>> + gpiod_direction_input(ts_nbus->data->desc[i]);
>> + else
>> + gpiod_direction_output(ts_nbus->data->desc[i], 1);
>
> Add a comment here explaining why you driver the line high by default
> when setting it to output mode. It certainly makes sense but how
> does the electronics work and why?
>
Ok.
>> +static inline void ts_nbus_reset_bus(struct ts_nbus *ts_nbus)
>> +{
>> + int i;
>> + int values[ts_nbus->data->ndescs];
>> +
>> + for (i = 0; i < ts_nbus->data->ndescs; i++)
>> + values[i] = 0;
>> +
>> + gpiod_set_array_value_cansleep(ts_nbus->data->ndescs,
>> + ts_nbus->data->desc, values);
>> + gpiod_set_value_cansleep(ts_nbus->csn, 0);
>> + gpiod_set_value_cansleep(ts_nbus->strobe, 0);
>> + gpiod_set_value_cansleep(ts_nbus->ale, 0);
>
> Add a comment about the process taken when this bus is reset. We
> see what is happening, but why?
>
Ok.
>> +/*
>> + * let the FPGA knows it can process.
>> + */
>> +static inline void ts_nbus_start_transaction(struct ts_nbus *ts_nbus)
>
> Why inline?
>
I will remove all the inline.
>> +{
>> + gpiod_set_value_cansleep(ts_nbus->strobe, 1);
>> +}
>
> For example this is a well commented function. We see what is happening
> and stobe has an evident name. Nice work!
>
>> +/*
>> + * return the byte value read from the data gpios.
>> + */
>> +static inline u8 ts_nbus_read_byte(struct ts_nbus *ts_nbus)
>
> Why inline?
>
Ditto.
> Then this cannot fail can it?
>
> I think this accessor should be changed to return an error code.
>
> static int ts_nbus_read_byte(struct ts_nbus *ts_nbus, u8 *val);
>
> Then if the return value from the function is != 0 it failed. Simple
> to check, just pass a pointer to the value you are getting in
> val.
>
> I KNOW I KNOW, not you have to put in error handling everywhere,
> what a DRAG! But we usually follow that pattern because...
>
>> +{
>> + struct gpio_descs *gpios = ts_nbus->data;
>> + int i;
>> + u8 value = 0;
>> +
>> + for (i = 0; i < gpios->ndescs; i++)
>
> Using gpios->ndescs is a bit overparametrized right? Isn't it
> simply 8 bits? Just putting 8 there is fine IMO. All 8 descs
> must be available for the thing to work anyway right?
>
>> + if (gpiod_get_value_cansleep(gpios->desc[i]))
>
> This can fail and you should return an error if < 0...
>
> You are ignoring that right now. That is why the function should
> be returning an error code that is usually 0.
>
You are right,
this can fail, i will propagate the "gpiod_get_value_cansleep(...)"
errno value.
>> + value |= 1 << i;
>
> Use:
> #include <linux/bitops.h>
>
> value |= BIT(i);
>
Ok.
>> +/*
>> + * set the data gpios accordingly to the byte value.
>> + */
>> +static inline void ts_nbus_write_byte(struct ts_nbus *ts_nbus, u8 byte)
>> +{
>> + struct gpio_descs *gpios = ts_nbus->data;
>> + int i;
>> + int values[gpios->ndescs];
>> +
>> + for (i = 0; i < gpios->ndescs; i++)
>> + if (byte & (1 << i))
>> + values[i] = 1;
>> + else
>> + values[i] = 0;
>> +
>> + gpiod_set_array_value_cansleep(gpios->ndescs, gpios->desc, values);
>
> This can also fail and you should check the return code and print an error
> message if it does.
>
As far as i understood, the "gpiod_set_array_value_cansleep(...)"
function doesn't return
anything, it will return immediately if gpios->desc is null but nothing
else.
Did i miss something?
>> +/*
>> + * reading the bus consists of resetting the bus, then notifying the FPGA to
>> + * send the data in the data gpios and return the read value.
>> + */
>> +static inline u8 ts_nbus_read_bus(struct ts_nbus *ts_nbus)
>
> All this inline...
Ok :)
>
>> +/*
>> + * writing to the bus consists of resetting the bus, then define the type of
>> + * command (address/value), write the data and notify the FPGA to retrieve the
>> + * value in the data gpios.
>> + */
>> +static inline void ts_nbus_write_bus(struct ts_nbus *ts_nbus, int cmd, u8 value)
>> +{
>> + ts_nbus_reset_bus(ts_nbus);
>> +
>> + if (cmd == TS_NBUS_WRITE_ADR)
>> + gpiod_set_value_cansleep(ts_nbus->ale, 1);
>> +
>> + ts_nbus_write_byte(ts_nbus, value);
>> + ts_nbus_start_transaction(ts_nbus);
>
> Error codes?
>
Same question here, these functions only make call to
"gpiod_set_value_cansleep(...)",
which as far as i understood doesn't return any error code.
>> + /* reading value MSB first */
>> + do {
>> + val = 0;
>> + for (i = 1; i >= 0; i--)
>> + val |= (ts_nbus_read_bus(ts_nbus) << (i * 8));
>
> Hard to deal with errors in this loop!
I will reformat this part.
>
>> + gpiod_set_value_cansleep(ts_nbus->csn, 1);
>
> Here too.
>
Same question, doesn't seem to have an error code.
>> +++ b/include/linux/ts-nbus.h
>> @@ -0,0 +1,26 @@
>> +/*
>> + * Copyright (c) 2016 - Savoir-faire Linux
>> + * Author: Sebastien Bourdelin <sebastien.bourdelin@xxxxxxxxxxxxxxxxxxxx>
>> + *
>> + * This file is licensed under the terms of the GNU General Public
>> + * License version 2. This program is licensed "as is" without any
>> + * warranty of any kind, whether express or implied.
>> + */
>> +
>> +#ifndef _TS_NBUS_H
>> +#define _TS_NBUS_H
>> +
>> +struct ts_nbus {
>> + struct pwm_device *pwm;
>> + struct gpio_descs *data;
>> + struct gpio_desc *csn;
>> + struct gpio_desc *txrx;
>> + struct gpio_desc *strobe;
>> + struct gpio_desc *ale;
>> + struct gpio_desc *rdy;
>> +};
>
> Is any consumer really looking into this struct? If not, why
> do you expose it?
>
> Move it to the ts-nbus.c file.
>
> The only thing you need in this header is:
>
> struct ts_nbus;
>
> Just that one line. The rest of the code, like the declarations
> below and all call sites, are just dealing with "pointers to something",
> they don't need to know what it is. You'll be amazed to see how it compiles.
> struct gpio_desc works exactly like that.
>
Ok.
>> +extern u16 ts_nbus_read(struct ts_nbus *, u8 adr);
>> +extern int ts_nbus_write(struct ts_nbus *, u8 adr, u16 value);
>
> I suspect the first function should be augmented to return an error code.
>
Yes it will.
> Yours,
> Linus Walleij
>
Thanks you Linus,
Best Regards,
Sebastien Bourdelin.