iio: dac: mcp4922: Add powerdown support
PATCH 1/1 iio: dac: mcp4922: Add powerdown support
This patch adds support for per-channel powerdown on the Microchip MCP 4902/4912/4922 family of DACs.
--- drivers/iio/dac/mcp4922.c | 126 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 110 insertions(+), 16 deletions(-)
drivers/iio/dac/mcp4922.c +110 -16
@@ -28,6 +28,9 @@#define MCP4922_NUM_CHANNELS 2 +#define MCP4922_OUTA_POWER_DOWN 0x20 +#define MCP4922_OUTB_POWER_DOWN 0xa0 +enum mcp4922_supported_device_ids {ID_MCP4902 ,ID_MCP4912 ,
@@ -37,26 +40,13 @@ enum mcp4922_supported_device_ids {struct mcp4922_state {struct spi_device *spi;unsigned int value[MCP4922_NUM_CHANNELS ]; +bool powerdown[MCP4922_NUM_CHANNELS ]; +unsigned int powerdown_mode;unsigned int vref_mv;struct regulator *vref_reg;u8 mosi[2 ]____cacheline_aligned ; }; -#define MCP4922_CHAN (chan, bits) { \ - .type = IIO_VOLTAGE, \ - .output = 1, \ - .indexed = 1, \ - .channel = chan, \ - .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ - .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ - .scan_type = { \ - .sign = 'u', \ - .realbits = (bits), \ - .storagebits = 16, \ - .shift = 12 - (bits), \ - }, \ -} -static int mcp4922_spi_write (struct mcp4922_state *state,u8 addr,u32 val) { state->mosi[1 ] = val &0xff ;
@@ -106,8 +96,10 @@ static int mcp4922_write_raw(struct iio_dev *indio_dev, val <<= chan->scan_type.shift; ret =mcp4922_spi_write (state, chan->channel, val); -if (!ret) +if (!ret) { state->value[chan->channel] = val; + state->powerdown[chan->channel] = false; + }return ret;default :
@@ -115,6 +107,108 @@ static int mcp4922_write_raw(struct iio_dev *indio_dev, } } +static const char *const mcp4922_powerdown_modes[] = { +"500kohm_to_gnd" +}; + +static int mcp4922_get_powerdown_mode (struct iio_dev *indio_dev, +const struct iio_chan_spec *chan) +{ +struct mcp4922_state *state =iio_priv (indio_dev); + +return state->powerdown_mode; +} + +static int mcp4922_set_powerdown_mode (struct iio_dev *indio_dev, +const struct iio_chan_spec *chan, +unsigned int mode) +{ +struct mcp4922_state *state =iio_priv (indio_dev); + + state->powerdown_mode = mode; + +return 0 ; +} + +static ssize_t mcp4922_read_powerdown (struct iio_dev *indio_dev, +uintptr_t private, +const struct iio_chan_spec *chan, +char *buf) +{ +struct mcp4922_state *state =iio_priv (indio_dev); + +return sprintf (buf,"%d\n" , state->powerdown[chan->channel]); +} + +static ssize_t mcp4922_write_powerdown (struct iio_dev *indio_dev, +uintptr_t private, +const struct iio_chan_spec *chan, +const char *buf,size_t len) +{ +struct mcp4922_state *state =iio_priv (indio_dev); +bool powerdown; +int ret; + + ret =strtobool (buf, &powerdown); +if (ret) +return ret; + +if (powerdown) { + state->mosi[0 ] = (chan->channel ==0 ) ? +MCP4922_OUTA_POWER_DOWN : +MCP4922_OUTB_POWER_DOWN ; + state->mosi[1 ] =0x00 ; + + ret =spi_write (state->spi, state->mosi,2 ); + }else { +/* Restore previous voltage level */ + ret =mcp4922_write_raw (indio_dev, chan, + state->value[chan->channel],0 , +IIO_CHAN_INFO_RAW ); + } +if (!ret) + state->powerdown[chan->channel] = powerdown; + +return ret ? ret : len; +} + +static const struct iio_enum mcp4922_powerdown_mode_enum = { + .items = mcp4922_powerdown_modes, + .num_items =ARRAY_SIZE (mcp4922_powerdown_modes), + .get = mcp4922_get_powerdown_mode, + .set = mcp4922_set_powerdown_mode, +}; + +static const struct iio_chan_spec_ext_info mcp4922_ext_info[] = { + { + .name ="powerdown" , + .read = mcp4922_read_powerdown, + .write = mcp4922_write_powerdown, + .shared =IIO_SEPARATE , + }, +IIO_ENUM ("powerdown_mode" ,IIO_SHARED_BY_TYPE , + &mcp4922_powerdown_mode_enum), +IIO_ENUM_AVAILABLE ("powerdown_mode" , + &mcp4922_powerdown_mode_enum), + { }, +}; + +#define MCP4922_CHAN (chan, bits) { \ + .type = IIO_VOLTAGE, \ + .output = 1, \ + .indexed = 1, \ + .channel = chan, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = 12 - (bits), \ + }, \ + .ext_info = mcp4922_ext_info, \ +} +static const struct iio_chan_spec mcp4922_channels[3 ][MCP4922_NUM_CHANNELS ] = { [ID_MCP4902 ] = {MCP4922_CHAN (0 ,8 ),MCP4922_CHAN (1 ,8 ) }, [ID_MCP4912 ] = {MCP4922_CHAN (0 ,10 ),MCP4922_CHAN (1 ,10 ) },
Signature · 1 line
2.11.0
Jonathan Cameron
maintainerQuoted 4 lines · On Wed, 3 Oct 2018 10:06:38 +0100 Chris Coffey <[email protected]> wrote:
This patch adds support for per-channel powerdown on the Microchip MCP 4902/4912/4922 family of DACs.
Signed-off-by Chris Coffey <[email protected]>
Hi Chris,
Welcome to IIO. There are a few interesting questions raised by this inline. I'm not totally sure on the 'right' answer for the question of whether a value write on a DAC should bring the device out of power down. I think that isn't what most drivers do, but I could be wrong on this.
I would like to get others opinion before we document the preferred option one way or the other.
Thanks,
Jonathan
Quoted 48 lines
--- drivers/iio/dac/mcp4922.c | 126 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 110 insertions(+), 16 deletions(-)drivers/iio/dac/mcp4922.c +5
-15@@ -28,6 +28,9 @@#define MCP4922_NUM_CHANNELS 2 +#define MCP4922_OUTA_POWER_DOWN 0x20 +#define MCP4922_OUTB_POWER_DOWN 0xa0 +enum mcp4922_supported_device_ids {ID_MCP4902 ,ID_MCP4912 ,@@ -37,26 +40,13 @@ enum mcp4922_supported_device_ids {struct mcp4922_state {struct spi_device *spi;unsigned int value[MCP4922_NUM_CHANNELS ]; +bool powerdown[MCP4922_NUM_CHANNELS ]; +unsigned int powerdown_mode;unsigned int vref_mv;struct regulator *vref_reg;u8 mosi[2 ]____cacheline_aligned ; }; -#define MCP4922_CHAN (chan, bits) { \ - .type = IIO_VOLTAGE, \ - .output = 1, \ - .indexed = 1, \ - .channel = chan, \ - .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ - .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ - .scan_type = { \ - .sign = 'u', \ - .realbits = (bits), \ - .storagebits = 16, \ - .shift = 12 - (bits), \ - }, \ -} -static int mcp4922_spi_write (struct mcp4922_state *state,u8 addr,u32 val) { state->mosi[1 ] = val &0xff ;
drivers/iio/dac/mcp4922.c static int mcp4922_write_raw(struct iio_dev *indio_dev, · line 101
Quoted 7 lines
drivers/iio/dac/mcp4922.c +2
-1@@ -106,8 +96,10 @@ static int mcp4922_write_raw(struct iio_dev *indio_dev, val <<= chan->scan_type.shift; ret =mcp4922_spi_write (state, chan->channel, val); -if (!ret) +if (!ret) { state->value[chan->channel] = val; + state->powerdown[chan->channel] = false;
That's interesting. I'm not sure we have any consistency of interface around whether a write to the value when powered down results in the device powering up or not. It certainly feels like we should be consistent on this and document it.
I checked the first random driver I found the ad5360 and it appears to have the powerdown on the front end in some sense in that there is a specific bit to clear in order to power up again and it is not done by changing the current value.
To my mind that is the more logical option, but I'd like others opinions on this.
If that is the consensus then in here you'll need to put a copy of the value somewhere to be set only when we try to come out of powerdown. You already do have such a cache for coming out of powerdown by writing 0 the powerdown enable so not hard to extend to this.
Quoted 17 lines
drivers/iio/dac/mcp4922.c +10
-0+ }return ret;default :@@ -115,6 +107,108 @@ static int mcp4922_write_raw(struct iio_dev *indio_dev, } } +static const char *const mcp4922_powerdown_modes[] = { +"500kohm_to_gnd" +}; + +static int mcp4922_get_powerdown_mode (struct iio_dev *indio_dev, +const struct iio_chan_spec *chan) +{ +struct mcp4922_state *state =iio_priv (indio_dev); +
drivers/iio/dac/mcp4922.c static int mcp4922_write_raw(struct iio_dev *indio_dev, · line 128
Quoted 10 lines
drivers/iio/dac/mcp4922.c +10
-0+return state->powerdown_mode; +} + +static int mcp4922_set_powerdown_mode (struct iio_dev *indio_dev, +const struct iio_chan_spec *chan, +unsigned int mode) +{ +struct mcp4922_state *state =iio_priv (indio_dev); + + state->powerdown_mode = mode;
It's a little unusual to have an enum specified with just one value.. I can see the advantage in terms of this looking like every other powerdown mode control.
I don't suppose it hurts though. Seems a little pointless
to have this set function actually do anything though...
Also no really point in having the get actually read the value.
So I would just provide a stub for this that returns the same value ever time and nothing at all for the set.
It's an odd enough corner case that it is probably not worth making the enum code allow for no set function.
Quoted 86 lines
drivers/iio/dac/mcp4922.c +83
-0+ +return 0 ; +} + +static ssize_t mcp4922_read_powerdown (struct iio_dev *indio_dev, +uintptr_t private, +const struct iio_chan_spec *chan, +char *buf) +{ +struct mcp4922_state *state =iio_priv (indio_dev); + +return sprintf (buf,"%d\n" , state->powerdown[chan->channel]); +} + +static ssize_t mcp4922_write_powerdown (struct iio_dev *indio_dev, +uintptr_t private, +const struct iio_chan_spec *chan, +const char *buf,size_t len) +{ +struct mcp4922_state *state =iio_priv (indio_dev); +bool powerdown; +int ret; + + ret =strtobool (buf, &powerdown); +if (ret) +return ret; + +if (powerdown) { + state->mosi[0 ] = (chan->channel ==0 ) ? +MCP4922_OUTA_POWER_DOWN : +MCP4922_OUTB_POWER_DOWN ; + state->mosi[1 ] =0x00 ; + + ret =spi_write (state->spi, state->mosi,2 ); + }else { +/* Restore previous voltage level */ + ret =mcp4922_write_raw (indio_dev, chan, + state->value[chan->channel],0 , +IIO_CHAN_INFO_RAW ); + } +if (!ret) + state->powerdown[chan->channel] = powerdown; + +return ret ? ret : len; +} + +static const struct iio_enum mcp4922_powerdown_mode_enum = { + .items = mcp4922_powerdown_modes, + .num_items =ARRAY_SIZE (mcp4922_powerdown_modes), + .get = mcp4922_get_powerdown_mode, + .set = mcp4922_set_powerdown_mode, +}; + +static const struct iio_chan_spec_ext_info mcp4922_ext_info[] = { + { + .name ="powerdown" , + .read = mcp4922_read_powerdown, + .write = mcp4922_write_powerdown, + .shared =IIO_SEPARATE , + }, +IIO_ENUM ("powerdown_mode" ,IIO_SHARED_BY_TYPE , + &mcp4922_powerdown_mode_enum), +IIO_ENUM_AVAILABLE ("powerdown_mode" , + &mcp4922_powerdown_mode_enum), + { }, +}; + +#define MCP4922_CHAN (chan, bits) { \ + .type = IIO_VOLTAGE, \ + .output = 1, \ + .indexed = 1, \ + .channel = chan, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = 12 - (bits), \ + }, \ + .ext_info = mcp4922_ext_info, \ +} +static const struct iio_chan_spec mcp4922_channels[3 ][MCP4922_NUM_CHANNELS ] = { [ID_MCP4902 ] = {MCP4922_CHAN (0 ,8 ),MCP4922_CHAN (1 ,8 ) }, [ID_MCP4912 ] = {MCP4922_CHAN (0 ,10 ),MCP4922_CHAN (1 ,10 ) },
Hi Jonathan, thank you for the review. I added a few comments below based on your feedback.
- Chris
Quoted 5 lines · Jonathan Cameron
That's interesting. I'm not sure we have any consistency of interface around whether a write to the value when powered down results in the device powering up or not. It certainly feels like we should be consistent on this and document it.
Quoted 8 lines
I checked the first random driver I found the ad5360 and it appears to have the powerdown on the front end in some sense in that there is a specific bit to clear in order to power up again and it is not done by changing the current value.
To my mind that is the more logical option, but I'd like others opinions on this.
I see what you mean. My intent was to mirror the behavior of userspace programs and APIs that I've written and seen for these chips. They take advantage of the fact a single command to the chip sets the voltage level and power state. That way there is no need to issue a separate power-up command, though it's available in sysfs if a user wants it (e.g., to toggle the channel's power state without specifying a new voltage level).
Like you, I'd be curious to hear what others have to say. My DAC experience is almost exclusively with Microchip devices, so I may be suffering from a bit of tunnel vision here.
Quoted 4 lines
It's a little unusual to have an enum specified with just one value.. I can see the advantage in terms of this looking like every other powerdown mode control.
Quoted 8 lines
I don't suppose it hurts though. Seems a little pointless
to have this set function actually do anything though...Also no really point in having the get actually read the value.
So I would just provide a stub for this that returns the same value ever time and nothing at all for the set.
Makes sense. I was indeed trying to be consistent with the powerdown mode interface in other drivers, but as you note, there's no point in actually doing anything in these get/set functions, as the value will never change.
Jonathan Cameron
maintainerQuoted 27 lines · On Tue, 9 Oct 2018 12:57:07 +0100 Chris Coffey <[email protected]> wrote:
Hi Jonathan, thank you for the review. I added a few comments below based on your feedback.
- Chris
Quoted 5 lines · Jonathan Cameron
That's interesting. I'm not sure we have any consistency of interface around whether a write to the value when powered down results in the device powering up or not. It certainly feels like we should be consistent on this and document it.
Quoted 8 lines
I checked the first random driver I found the ad5360 and it appears to have the powerdown on the front end in some sense in that there is a specific bit to clear in order to power up again and it is not done by changing the current value.
To my mind that is the more logical option, but I'd like others opinions on this.
I see what you mean. My intent was to mirror the behavior of userspace programs and APIs that I've written and seen for these chips. They take advantage of the fact a single command to the chip sets the voltage level and power state. That way there is no need to issue a separate power-up command, though it's available in sysfs if a user wants it (e.g., to toggle the channel's power state without specifying a new voltage level).
Quoted 3 lines
Like you, I'd be curious to hear what others have to say. My DAC experience is almost exclusively with Microchip devices, so I may be suffering from a bit of tunnel vision here.
Agreed. I've added a few additional CCs. If we don't get any replies we may want to raise a specific RFC email on this to catch people's attention.
The cc list is fairly random, so if people wouldn't mind drawing the attention of others to this question that would be very helpful!
Jonathan
Quoted 18 lines
Quoted 5 lines
It's a little unusual to have an enum specified with just one value.. I can see the advantage in terms of this looking like every other powerdown mode control.
Quoted 8 lines
I don't suppose it hurts though. Seems a little pointless
to have this set function actually do anything though...Also no really point in having the get actually read the value.
So I would just provide a stub for this that returns the same value ever time and nothing at all for the set.
Makes sense. I was indeed trying to be consistent with the powerdown mode interface in other drivers, but as you note, there's no point in actually doing anything in these get/set functions, as the value will never change.
DQoNCk9uIDEzLzEwLzIwMTggMTQuMTUsIEpvbmF0aGFuIENhbWVyb24gd3JvdGU6DQo+IE9uIFR1 ZSwgOSBPY3QgMjAxOCAxMjo1NzowNyArMDEwMA0KPiBDaHJpcyBDb2ZmZXkgPGNtY0BiYWJibGVi aXQubmV0PiB3cm90ZToNCj4gDQo+PiBIaSBKb25hdGhhbiwgdGhhbmsgeW91IGZvciB0aGUgcmV2 aWV3LiBJIGFkZGVkIGEgZmV3IGNvbW1lbnRzIGJlbG93DQo+PiBiYXNlZCBvbiB5b3VyIGZlZWRi YWNrLg0KPj4NCj4+IC0gQ2hyaXMNCj4+DQo+PiBPbiBNb24sIE9jdCAwOCwgMjAxOCBhdCAwOTow ODoyOVBNICswMTAwLCBKb25hdGhhbiBDYW1lcm9uIHdyb3RlOg0KPj4+IFRoYXQncyBpbnRlcmVz dGluZy4gIEknbSBub3Qgc3VyZSB3ZSBoYXZlIGFueSBjb25zaXN0ZW5jeSBvZiBpbnRlcmZhY2UN Cj4+PiBhcm91bmQgd2hldGhlciBhIHdyaXRlIHRvIHRoZSB2YWx1ZSB3aGVuIHBvd2VyZWQgZG93 biByZXN1bHRzIGluIHRoZSBkZXZpY2UNCj4+PiBwb3dlcmluZyB1cCBvciBub3QuICBJdCBjZXJ0 YWlubHkgZmVlbHMgbGlrZSB3ZSBzaG91bGQgYmUgY29uc2lzdGVudCBvbiB0aGlzDQo+Pj4gYW5k IGRvY3VtZW50IGl0Lg0KPj4+DQo+Pj4gSSBjaGVja2VkIHRoZSBmaXJzdCByYW5kb20gZHJpdmVy IEkgZm91bmQgdGhlIGFkNTM2MCBhbmQgaXQgYXBwZWFycyB0bw0KPj4+IGhhdmUgdGhlIHBvd2Vy ZG93biBvbiB0aGUgZnJvbnQgZW5kIGluIHNvbWUgc2Vuc2UgaW4gdGhhdCB0aGVyZSBpcyBhDQo+ Pj4gc3BlY2lmaWMgYml0IHRvIGNsZWFyIGluIG9yZGVyIHRvIHBvd2VyIHVwIGFnYWluIGFuZCBp dCBpcyBub3QgZG9uZQ0KPj4+IGJ5IGNoYW5naW5nIHRoZSBjdXJyZW50IHZhbHVlLg0KPj4+DQo+ Pj4gVG8gbXkgbWluZCB0aGF0IGlzIHRoZSBtb3JlIGxvZ2ljYWwgb3B0aW9uLCBidXQgSSdkIGxp a2Ugb3RoZXJzIG9waW5pb25zDQo+Pj4gb24gdGhpcy4NCj4+PiAgICANCj4+IEkgc2VlIHdoYXQg eW91IG1lYW4uIE15IGludGVudCB3YXMgdG8gbWlycm9yIHRoZSBiZWhhdmlvciBvZiB1c2Vyc3Bh Y2UNCj4+IHByb2dyYW1zIGFuZCBBUElzIHRoYXQgSSd2ZSB3cml0dGVuIGFuZCBzZWVuIGZvciB0 aGVzZSBjaGlwcy4gVGhleSB0YWtlDQo+PiBhZHZhbnRhZ2Ugb2YgdGhlIGZhY3QgYSBzaW5nbGUg Y29tbWFuZCB0byB0aGUgY2hpcCBzZXRzIHRoZSB2b2x0YWdlDQo+PiBsZXZlbCBhbmQgcG93ZXIg c3RhdGUuIFRoYXQgd2F5IHRoZXJlIGlzIG5vIG5lZWQgdG8gaXNzdWUgYSBzZXBhcmF0ZQ0KPj4g cG93ZXItdXAgY29tbWFuZCwgdGhvdWdoIGl0J3MgYXZhaWxhYmxlIGluIHN5c2ZzIGlmIGEgdXNl ciB3YW50cyBpdA0KPj4gKGUuZy4sIHRvIHRvZ2dsZSB0aGUgY2hhbm5lbCdzIHBvd2VyIHN0YXRl IHdpdGhvdXQgc3BlY2lmeWluZyBhIG5ldw0KPj4gdm9sdGFnZSBsZXZlbCkuDQo+Pg0KPj4gTGlr ZSB5b3UsIEknZCBiZSBjdXJpb3VzIHRvIGhlYXIgd2hhdCBvdGhlcnMgaGF2ZSB0byBzYXkuIE15 IERBQw0KPj4gZXhwZXJpZW5jZSBpcyBhbG1vc3QgZXhjbHVzaXZlbHkgd2l0aCBNaWNyb2NoaXAg ZGV2aWNlcywgc28gSSBtYXkgYmUNCj4+IHN1ZmZlcmluZyBmcm9tIGEgYml0IG9mIHR1bm5lbCB2 aXNpb24gaGVyZS4NCj4gDQo+IEFncmVlZC4gSSd2ZSBhZGRlZCBhIGZldyBhZGRpdGlvbmFsIEND cy4gIElmIHdlIGRvbid0IGdldCBhbnkNCj4gcmVwbGllcyB3ZSBtYXkgd2FudCB0byByYWlzZSBh IHNwZWNpZmljIFJGQyBlbWFpbCBvbiB0aGlzIHRvIGNhdGNoDQo+IHBlb3BsZSdzIGF0dGVudGlv bi4NCj4gDQo+IFRoZSBjYyBsaXN0IGlzIGZhaXJseSByYW5kb20sIHNvIGlmIHBlb3BsZSB3b3Vs ZG4ndCBtaW5kIGRyYXdpbmcNCj4gdGhlIGF0dGVudGlvbiBvZiBvdGhlcnMgdG8gdGhpcyBxdWVz dGlvbiB0aGF0IHdvdWxkIGJlIHZlcnkgaGVscGZ1bCENCj4gDQo+IEpvbmF0aGFuDQo+IA0KSGkN Cg0KVGhlIERBQzU1NzEgaXMgbGlrZSB0aGlzIG9uZSBhYmxlIHRvIGxlYXZlIHBvd2VyZG93biBt b2RlIHdpdGggYSBzaW5nbGUgDQp3cml0ZSBjb21tYW5kLiBCdXQgSSdtIGJsb2NraW5nIGZyb20g dXNpbmcgaXQgaW4gdGhlIGRyaXZlciBzbyB3ZSBhcmUgDQpjb25zaXN0ZW50IGFjcm9zcyBhbGwg dGhlIERBQyBkZXZpY2VzLiAoSG9wZWZ1bGx5KQ0KRm9yIG1lIGl0IG1ha2VzIHNlbnNlIHRvIHVz ZSB0aGUgcG93ZXJkb3duIEFQSSB0byBjb250cm9sIGFsbCBwb3dlcmRvd24gDQpzZXR0aW5ncy4u Lg0KDQovU2Vhbg0K