Driver: Refactor MCP23009 GPIO expander into uORB driver

This commit is contained in:
Niklas Hauser
2023-06-19 07:58:21 +02:00
committed by Beat Küng
parent 3303323971
commit 8fe65c6722
19 changed files with 697 additions and 300 deletions
@@ -1,6 +1,6 @@
/****************************************************************************
*
* Copyright (c) 2020 PX4 Development Team. All rights reserved.
* Copyright (c) 2023 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -31,182 +31,142 @@
*
****************************************************************************/
#include <nuttx/ioexpander/gpio.h>
#include <drivers/drv_sensor.h>
#include <px4_platform/gpio/mcp23009.hpp>
#include "mcp23009_registers.hpp"
#include <lib/drivers/device/Device.hpp>
#include <uORB/topics/gpio_config.h>
#include <uORB/topics/gpio_in.h>
#include <uORB/topics/gpio_out.h>
#include <uORB/topics/gpio_request.h>
#include <uORB/Publication.hpp>
#include <uORB/SubscriptionCallback.hpp>
using namespace Microchip_MCP23009;
const struct gpio_operations_s MCP23009::gpio_ops = {
go_read : MCP23009::go_read,
go_write : MCP23009::go_write,
go_attach : nullptr,
go_enable : nullptr,
go_setpintype : MCP23009::go_setpintype,
static uint32_t DEVID{0};
struct mcp23009_gpio_dev_s {
struct gpio_dev_s gpio;
uint8_t mask;
};
MCP23009::MCP23009(int bus, int address, int first_minor, int bus_frequency) :
I2C(DRV_GPIO_DEVTYPE_MCP23009, "MCP23009", bus, address, bus_frequency),
_first_minor(first_minor)
/* Copy the read input data */
class ReadCallback : public uORB::SubscriptionCallback
{
}
public:
using SubscriptionCallback::SubscriptionCallback;
MCP23009::~MCP23009()
{
/* set all as input & unregister */
for (int i = 0; i < num_gpios; ++i) {
go_setpintype(i, GPIO_INPUT_PIN);
gpio_pin_unregister(&_gpio[i].gpio, _first_minor + i);
}
}
void call() override
{
px4::msg::GpioIn new_input;
int MCP23009::go_read(struct gpio_dev_s *dev, bool *value)
{
mcp23009_gpio_dev_s *gpio = (struct mcp23009_gpio_dev_s *)dev;
return gpio->obj->go_read(gpio->id, value);
}
if (update(&new_input) && new_input.device_id == DEVID) {
input = new_input.state;
}
int MCP23009::go_write(struct gpio_dev_s *dev, bool value)
{
mcp23009_gpio_dev_s *gpio = (struct mcp23009_gpio_dev_s *)dev;
return gpio->obj->go_write(gpio->id, value);
}
int MCP23009::go_setpintype(struct gpio_dev_s *dev, enum gpio_pintype_e pintype)
{
mcp23009_gpio_dev_s *gpio = (struct mcp23009_gpio_dev_s *)dev;
return gpio->obj->go_setpintype(gpio->id, pintype);
}
int MCP23009::read_reg(Register address, uint8_t &data)
{
return transfer((uint8_t *)&address, 1, &data, 1);
}
int MCP23009::write_reg(Register address, uint8_t value)
{
uint8_t data[2] = {(uint8_t)address, value};
return transfer(data, sizeof(data), nullptr, 0);
}
int MCP23009::init(uint8_t direction, uint8_t intital, uint8_t pull_up)
{
/* do I2C init (and probe) first */
int ret = I2C::init();
if (ret != PX4_OK) {
return ret;
}
/* Use this state as the out puts */
uint8_t input;
};
ret = write_reg(Register::OLAT, intital);
ret |= write_reg(Register::IODIR, direction);
ret |= write_reg(Register::GPPU, pull_up);
static uORB::Publication<px4::msg::GpioRequest> toGpioRequest{ORB_ID(gpio_request)};
static ReadCallback fromGpioIn{ORB_ID(gpio_in)};
static int mcp23009_read(struct gpio_dev_s *dev, bool *value)
{
mcp23009_gpio_dev_s *gpio = (struct mcp23009_gpio_dev_s *)dev;
*value = fromGpioIn.input & gpio->mask;
return OK;
}
if (ret != PX4_OK) {
return ret;
static uORB::Publication<gpio_out_s> toGpioOut{ORB_ID(gpio_out)};
static int mcp23009_write(struct gpio_dev_s *dev, bool value)
{
mcp23009_gpio_dev_s *gpio = (struct mcp23009_gpio_dev_s *)dev;
gpio_out_s msg{
hrt_absolute_time(),
DEVID,
gpio->mask, // clear mask
value ? gpio->mask : 0u, // set mask
};
return toGpioOut.publish(msg) ? OK : -ETIMEDOUT;
}
static uORB::Publication<gpio_config_s> toGpioConfig{ORB_ID(gpio_config)};
static int mcp23009_setpintype(struct gpio_dev_s *dev, enum gpio_pintype_e pintype)
{
mcp23009_gpio_dev_s *gpio = (struct mcp23009_gpio_dev_s *)dev;
gpio_config_s msg{
hrt_absolute_time(),
DEVID,
gpio->mask,
};
switch (pintype) {
case GPIO_INPUT_PIN:
msg.config = gpio_config_s::INPUT;
break;
case GPIO_INPUT_PIN_PULLUP:
msg.config = gpio_config_s::INPUT_PULLUP;
break;
case GPIO_OUTPUT_PIN:
msg.config = gpio_config_s::OUTPUT;
break;
default:
return -ENOTSUP;
}
/* register the pins */
for (int i = 0; i < num_gpios; ++i) {
_gpio[i].gpio.gp_pintype = GPIO_INPUT_PIN;
_gpio[i].gpio.gp_ops = &gpio_ops;
_gpio[i].id = i;
_gpio[i].obj = this;
ret = gpio_pin_register(&_gpio[i].gpio, _first_minor + i);
return toGpioConfig.publish(msg) ? OK : -ETIMEDOUT;
}
if (ret != PX4_OK) {
// ----------------------------------------------------------------------------
static const struct gpio_operations_s mcp23009_gpio_ops {
mcp23009_read,
mcp23009_write,
nullptr,
nullptr,
mcp23009_setpintype,
};
static constexpr uint8_t NUM_GPIOS = 8;
static mcp23009_gpio_dev_s _gpio[NUM_GPIOS] {
{ {GPIO_INPUT_PIN, {}, &mcp23009_gpio_ops}, (1u << 0) },
{ {GPIO_INPUT_PIN, {}, &mcp23009_gpio_ops}, (1u << 1) },
{ {GPIO_INPUT_PIN, {}, &mcp23009_gpio_ops}, (1u << 2) },
{ {GPIO_INPUT_PIN, {}, &mcp23009_gpio_ops}, (1u << 3) },
{ {GPIO_INPUT_PIN, {}, &mcp23009_gpio_ops}, (1u << 4) },
{ {GPIO_INPUT_PIN, {}, &mcp23009_gpio_ops}, (1u << 5) },
{ {GPIO_INPUT_PIN, {}, &mcp23009_gpio_ops}, (1u << 6) },
{ {GPIO_INPUT_PIN, {}, &mcp23009_gpio_ops}, (1u << 7) }
};
// ----------------------------------------------------------------------------
int mcp23009_register_gpios(uint8_t i2c_bus, uint8_t i2c_addr, int first_minor)
{
const auto device_id = device::Device::DeviceId{
device::Device::DeviceBusType_I2C, i2c_bus, i2c_addr, DRV_GPIO_DEVTYPE_MCP23009};
DEVID = device_id.devid;
for (int i = 0; i < NUM_GPIOS; ++i) {
int ret = gpio_pin_register(&_gpio[i].gpio, first_minor + i);
if (ret != OK) {
return ret;
}
}
return ret;
fromGpioIn.registerCallback();
return OK;
}
int MCP23009::probe()
int mcp23009_unregister_gpios(int first_minor)
{
// no whoami, try to read IOCON
uint8_t data;
return read_reg(Register::IOCON, data);
}
int MCP23009::go_read(int id, bool *value)
{
uint8_t data;
int ret = read_reg(Register::GPIO, data);
if (ret != 0) {
return ret;
}
*value = data & (1 << id);
return 0;
}
int MCP23009::go_write(int id, bool value)
{
uint8_t data;
int ret = read_reg(Register::GPIO, data);
if (ret != 0) {
return ret;
}
if (value) {
data |= (1 << id);
} else {
data &= ~(1 << id);
}
return write_reg(Register::GPIO, data);
}
int MCP23009::go_setpintype(int id, enum gpio_pintype_e pintype)
{
uint8_t direction;
int ret = read_reg(Register::IODIR, direction);
if (ret != 0) {
return ret;
}
uint8_t pullup;
ret = read_reg(Register::GPPU, pullup);
if (ret != 0) {
return ret;
}
switch (pintype) {
case GPIO_INPUT_PIN:
direction |= (1 << id);
pullup &= ~(1 << id);
break;
case GPIO_INPUT_PIN_PULLUP:
direction |= (1 << id);
pullup |= (1 << id);
break;
case GPIO_OUTPUT_PIN:
direction &= ~(1 << id);
break;
default:
return -EINVAL;
}
_gpio[id].gpio.gp_pintype = pintype;
ret = write_reg(Register::GPPU, pullup);
if (ret != 0) {
return ret;
}
return write_reg(Register::IODIR, direction);
for (int i = 0; i < NUM_GPIOS; ++i) {
mcp23009_setpintype(&_gpio[i].gpio, GPIO_INPUT_PIN);
gpio_pin_unregister(&_gpio[i].gpio, first_minor + i);
}
fromGpioIn.unregisterCallback();
return OK;
}
@@ -1,68 +0,0 @@
/****************************************************************************
*
* Copyright (c) 2020 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
#pragma once
#include <stdint.h>
// TODO: move to a central header
static constexpr uint8_t Bit0 = (1 << 0);
static constexpr uint8_t Bit1 = (1 << 1);
static constexpr uint8_t Bit2 = (1 << 2);
static constexpr uint8_t Bit3 = (1 << 3);
static constexpr uint8_t Bit4 = (1 << 4);
static constexpr uint8_t Bit5 = (1 << 5);
static constexpr uint8_t Bit6 = (1 << 6);
static constexpr uint8_t Bit7 = (1 << 7);
namespace Microchip_MCP23009
{
enum class
Register : uint8_t {
IODIR = 0x00,
IPOL = 0x01,
GPINTEN = 0x02,
DEFVAL = 0x03,
INTCON = 0x04,
IOCON = 0x05,
GPPU = 0x06,
INTF = 0x07,
INTCAP = 0x08,
GPIO = 0x09,
OLAT = 0x0a
};
} // namespace Microchip_MCP23009
@@ -1,6 +1,6 @@
/****************************************************************************
*
* Copyright (c) 2020 PX4 Development Team. All rights reserved.
* Copyright (c) 2023 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -33,49 +33,7 @@
#pragma once
#include <drivers/device/i2c.h>
#include <stdint.h>
#include <nuttx/ioexpander/gpio.h>
using namespace time_literals;
namespace Microchip_MCP23009
{
enum class Register : uint8_t;
}
class MCP23009 : public device::I2C
{
public:
MCP23009(int bus, int address, int first_minor = 0, int bus_frequency = 400000);
virtual ~MCP23009();
int init(uint8_t direction, uint8_t intital = 0, uint8_t pull_up = 0);
protected:
int probe() override;
private:
static constexpr int num_gpios = 8;
static const gpio_operations_s gpio_ops;
struct mcp23009_gpio_dev_s {
struct gpio_dev_s gpio;
uint8_t id;
MCP23009 *obj;
};
static int go_read(struct gpio_dev_s *dev, bool *value);
static int go_write(struct gpio_dev_s *dev, bool value);
static int go_setpintype(struct gpio_dev_s *dev, enum gpio_pintype_e pintype);
int go_read(int id, bool *value);
int go_write(int id, bool value);
int go_setpintype(int id, enum gpio_pintype_e pintype);
int read_reg(Microchip_MCP23009::Register address, uint8_t &data);
int write_reg(Microchip_MCP23009::Register address, uint8_t data);
const int _first_minor;
mcp23009_gpio_dev_s _gpio[num_gpios] {};
};
int mcp23009_register_gpios(uint8_t i2c_bus, uint8_t i2c_addr, int first_minor = 0);
int mcp23009_unregister_gpios(int first_minor = 0);