root/drivers/hid/hid-mcp2200.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * MCP2200 - Microchip USB to GPIO bridge
 *
 * Copyright (c) 2023, Johannes Roith <johannes@gnu-linux.rocks>
 *
 * Datasheet: https://ww1.microchip.com/downloads/en/DeviceDoc/22228A.pdf
 * App Note for HID: https://ww1.microchip.com/downloads/en/DeviceDoc/93066A.pdf
 */
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/gpio/driver.h>
#include <linux/hid.h>
#include <linux/hidraw.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include "hid-ids.h"

/* Commands codes in a raw output report */
#define SET_CLEAR_OUTPUTS       0x08
#define CONFIGURE               0x10
#define READ_EE                 0x20
#define WRITE_EE                0x40
#define READ_ALL                0x80

/* MCP GPIO direction encoding */
enum MCP_IO_DIR {
        MCP2200_DIR_OUT = 0x00,
        MCP2200_DIR_IN  = 0x01,
};

/* Altternative pin assignments */
#define TXLED           2
#define RXLED           3
#define USBCFG          6
#define SSPND           7
#define MCP_NGPIO       8

/* CMD to set or clear a GPIO output */
struct mcp_set_clear_outputs {
        u8 cmd;
        u8 dummys1[10];
        u8 set_bmap;
        u8 clear_bmap;
        u8 dummys2[3];
} __packed;

/* CMD to configure the IOs */
struct mcp_configure {
        u8 cmd;
        u8 dummys1[3];
        u8 io_bmap;
        u8 config_alt_pins;
        u8 io_default_val_bmap;
        u8 config_alt_options;
        u8 baud_h;
        u8 baud_l;
        u8 dummys2[6];
} __packed;

/* CMD to read all parameters */
struct mcp_read_all {
        u8 cmd;
        u8 dummys[15];
} __packed;

/* Response to the read all cmd */
struct mcp_read_all_resp {
        u8 cmd;
        u8 eep_addr;
        u8 dummy;
        u8 eep_val;
        u8 io_bmap;
        u8 config_alt_pins;
        u8 io_default_val_bmap;
        u8 config_alt_options;
        u8 baud_h;
        u8 baud_l;
        u8 io_port_val_bmap;
        u8 dummys[5];
} __packed;

struct mcp2200 {
        struct hid_device *hdev;
        struct mutex lock;
        struct completion wait_in_report;
        u8 gpio_dir;
        u8 gpio_val;
        u8 gpio_inval;
        u8 baud_h;
        u8 baud_l;
        u8 config_alt_pins;
        u8 gpio_reset_val;
        u8 config_alt_options;
        int status;
        struct gpio_chip gc;
        u8 hid_report[16];
};

/* this executes the READ_ALL cmd */
static int mcp_cmd_read_all(struct mcp2200 *mcp)
{
        struct mcp_read_all *read_all;
        int len, t;

        reinit_completion(&mcp->wait_in_report);

        mutex_lock(&mcp->lock);

        read_all = (struct mcp_read_all *) mcp->hid_report;
        read_all->cmd = READ_ALL;
        len = hid_hw_output_report(mcp->hdev, (u8 *) read_all,
                                   sizeof(struct mcp_read_all));

        mutex_unlock(&mcp->lock);

        if (len != sizeof(struct mcp_read_all))
                return -EINVAL;

        t = wait_for_completion_timeout(&mcp->wait_in_report,
                                        msecs_to_jiffies(4000));
        if (!t)
                return -ETIMEDOUT;

        /* return status, negative value if wrong response was received */
        return mcp->status;
}

static int mcp_set_multiple(struct gpio_chip *gc, unsigned long *mask,
                            unsigned long *bits)
{
        struct mcp2200 *mcp = gpiochip_get_data(gc);
        u8 value;
        int status;
        struct mcp_set_clear_outputs *cmd;

        mutex_lock(&mcp->lock);
        cmd = (struct mcp_set_clear_outputs *) mcp->hid_report;

        value = mcp->gpio_val & ~*mask;
        value |= (*mask & *bits);

        cmd->cmd = SET_CLEAR_OUTPUTS;
        cmd->set_bmap = value;
        cmd->clear_bmap = ~(value);

        status = hid_hw_output_report(mcp->hdev, (u8 *) cmd,
                       sizeof(struct mcp_set_clear_outputs));

        if (status == sizeof(struct mcp_set_clear_outputs))
                mcp->gpio_val = value;
        else
                status = -EIO;

        mutex_unlock(&mcp->lock);

        return status;
}

static int mcp_set(struct gpio_chip *gc, unsigned int gpio_nr, int value)
{
        unsigned long mask = 1 << gpio_nr;
        unsigned long bmap_value = value << gpio_nr;

        return mcp_set_multiple(gc, &mask, &bmap_value);
}

static int mcp_get_multiple(struct gpio_chip *gc, unsigned long *mask,
                unsigned long *bits)
{
        u32 val;
        struct mcp2200 *mcp = gpiochip_get_data(gc);
        int status;

        status = mcp_cmd_read_all(mcp);
        if (status)
                return status;

        val = mcp->gpio_inval;
        *bits = (val & *mask);
        return 0;
}

static int mcp_get(struct gpio_chip *gc, unsigned int gpio_nr)
{
        unsigned long mask = 0, bits = 0;

        mask = (1 << gpio_nr);
        mcp_get_multiple(gc, &mask, &bits);
        return bits > 0;
}

static int mcp_get_direction(struct gpio_chip *gc, unsigned int gpio_nr)
{
        struct mcp2200 *mcp = gpiochip_get_data(gc);

        return (mcp->gpio_dir & (MCP2200_DIR_IN << gpio_nr))
                ? GPIO_LINE_DIRECTION_IN : GPIO_LINE_DIRECTION_OUT;
}

static int mcp_set_direction(struct gpio_chip *gc, unsigned int gpio_nr,
                             enum MCP_IO_DIR io_direction)
{
        struct mcp2200 *mcp = gpiochip_get_data(gc);
        struct mcp_configure *conf;
        int status;
        /* after the configure cmd we will need to set the outputs again */
        unsigned long mask = ~(mcp->gpio_dir); /* only set outputs */
        unsigned long bits = mcp->gpio_val;
        /* Offsets of alternative pins in config_alt_pins, 0 is not used */
        u8 alt_pin_conf[8] = {SSPND, USBCFG, 0, 0, 0, 0, RXLED, TXLED};
        u8 config_alt_pins = mcp->config_alt_pins;

        /* Read in the reset baudrate first, we need it later */
        status = mcp_cmd_read_all(mcp);
        if (status != 0)
                return status;

        mutex_lock(&mcp->lock);
        conf = (struct mcp_configure  *) mcp->hid_report;

        /* configure will reset the chip! */
        conf->cmd = CONFIGURE;
        conf->io_bmap = (mcp->gpio_dir & ~(1 << gpio_nr))
                | (io_direction << gpio_nr);
        /* Don't overwrite the reset parameters */
        conf->baud_h = mcp->baud_h;
        conf->baud_l = mcp->baud_l;
        conf->config_alt_options = mcp->config_alt_options;
        conf->io_default_val_bmap = mcp->gpio_reset_val;
        /* Adjust alt. func if necessary */
        if (alt_pin_conf[gpio_nr])
                config_alt_pins &= ~(1 << alt_pin_conf[gpio_nr]);
        conf->config_alt_pins = config_alt_pins;

        status = hid_hw_output_report(mcp->hdev, (u8 *) conf,
                                      sizeof(struct mcp_set_clear_outputs));

        if (status == sizeof(struct mcp_set_clear_outputs)) {
                mcp->gpio_dir = conf->io_bmap;
                mcp->config_alt_pins = config_alt_pins;
        } else {
                mutex_unlock(&mcp->lock);
                return -EIO;
        }

        mutex_unlock(&mcp->lock);

        /* Configure CMD will clear all IOs -> rewrite them */
        mcp_set_multiple(gc, &mask, &bits);
        return 0;
}

static int mcp_direction_input(struct gpio_chip *gc, unsigned int gpio_nr)
{
        return mcp_set_direction(gc, gpio_nr, MCP2200_DIR_IN);
}

static int mcp_direction_output(struct gpio_chip *gc, unsigned int gpio_nr,
                                int value)
{
        int ret;
        unsigned long mask, bmap_value;

        mask = 1 << gpio_nr;
        bmap_value = value << gpio_nr;

        ret = mcp_set_direction(gc, gpio_nr, MCP2200_DIR_OUT);
        if (ret)
                return ret;

        return mcp_set_multiple(gc, &mask, &bmap_value);
}

static const struct gpio_chip template_chip = {
        .label                  = "mcp2200",
        .owner                  = THIS_MODULE,
        .get_direction          = mcp_get_direction,
        .direction_input        = mcp_direction_input,
        .direction_output       = mcp_direction_output,
        .set                    = mcp_set,
        .set_multiple           = mcp_set_multiple,
        .get                    = mcp_get,
        .get_multiple           = mcp_get_multiple,
        .base                   = -1,
        .ngpio                  = MCP_NGPIO,
        .can_sleep              = true,
};

/*
 * MCP2200 uses interrupt endpoint for input reports. This function
 * is called by HID layer when it receives i/p report from mcp2200,
 * which is actually a response to the previously sent command.
 */
static int mcp2200_raw_event(struct hid_device *hdev, struct hid_report *report,
                u8 *data, int size)
{
        struct mcp2200 *mcp = hid_get_drvdata(hdev);
        struct mcp_read_all_resp *all_resp;

        switch (data[0]) {
        case READ_ALL:
                all_resp = (struct mcp_read_all_resp *) data;
                mcp->status = 0;
                mcp->gpio_inval = all_resp->io_port_val_bmap;
                mcp->baud_h = all_resp->baud_h;
                mcp->baud_l = all_resp->baud_l;
                mcp->gpio_reset_val = all_resp->io_default_val_bmap;
                mcp->config_alt_pins = all_resp->config_alt_pins;
                mcp->config_alt_options = all_resp->config_alt_options;
                break;
        default:
                mcp->status = -EIO;
                break;
        }

        complete(&mcp->wait_in_report);
        return 0;
}

static int mcp2200_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
        int ret;
        struct mcp2200 *mcp;

        mcp = devm_kzalloc(&hdev->dev, sizeof(*mcp), GFP_KERNEL);
        if (!mcp)
                return -ENOMEM;

        ret = hid_parse(hdev);
        if (ret) {
                hid_err(hdev, "can't parse reports\n");
                return ret;
        }

        ret = hid_hw_start(hdev, 0);
        if (ret) {
                hid_err(hdev, "can't start hardware\n");
                return ret;
        }

        hid_info(hdev, "USB HID v%x.%02x Device [%s] on %s\n", hdev->version >> 8,
                        hdev->version & 0xff, hdev->name, hdev->phys);

        ret = hid_hw_open(hdev);
        if (ret) {
                hid_err(hdev, "can't open device\n");
                hid_hw_stop(hdev);
                return ret;
        }

        mutex_init(&mcp->lock);
        init_completion(&mcp->wait_in_report);
        hid_set_drvdata(hdev, mcp);
        mcp->hdev = hdev;

        mcp->gc = template_chip;
        mcp->gc.parent = &hdev->dev;

        ret = devm_gpiochip_add_data(&hdev->dev, &mcp->gc, mcp);
        if (ret < 0) {
                hid_err(hdev, "Unable to register gpiochip\n");
                hid_hw_close(hdev);
                hid_hw_stop(hdev);
                return ret;
        }

        return 0;
}

static void mcp2200_remove(struct hid_device *hdev)
{
        hid_hw_close(hdev);
        hid_hw_stop(hdev);
}

static const struct hid_device_id mcp2200_devices[] = {
        { HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_MCP2200) },
        { }
};
MODULE_DEVICE_TABLE(hid, mcp2200_devices);

static struct hid_driver mcp2200_driver = {
        .name           = "mcp2200",
        .id_table       = mcp2200_devices,
        .probe          = mcp2200_probe,
        .remove         = mcp2200_remove,
        .raw_event      = mcp2200_raw_event,
};

/* Register with HID core */
module_hid_driver(mcp2200_driver);

MODULE_AUTHOR("Johannes Roith <johannes@gnu-linux.rocks>");
MODULE_DESCRIPTION("MCP2200 Microchip HID USB to GPIO bridge");
MODULE_LICENSE("GPL");