i2c kernel module (very untested and rough around the edges)

This commit is contained in:
Triss 2021-07-12 03:24:13 +02:00
parent f9eb86b60e
commit eaab9e05f8
6 changed files with 316 additions and 12 deletions

View File

@ -1,5 +1,5 @@
obj-m := dmj.o dmj-char.o obj-m := dmj.o dmj-char.o i2c-dmj.o
KDIR := /lib/modules/$(shell uname -r)/build KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd) PWD := $(shell pwd)

View File

@ -20,7 +20,7 @@
#include <linux/types.h> #include <linux/types.h>
#if 0 #if 0
#include <linux/driver/dmj.h> #include <linux/mfd/dmj.h>
#else #else
#include "dmj.h" #include "dmj.h"
#endif #endif
@ -206,7 +206,7 @@ static int dmj_char_probe(struct platform_device *pdev)
dev_info(pd, HARDWARE_NAME " /dev entries driver, major=%d, minor=%d\n", dev_info(pd, HARDWARE_NAME " /dev entries driver, major=%d, minor=%d\n",
dmj_char_major, minor); dmj_char_major, minor);
dmjch = kzalloc(sizeof(*dmjch), GFP_KERNEL); dmjch = devm_kzalloc(pd, sizeof(*dmjch), GFP_KERNEL);
if (!dmjch) return -ENOMEM; if (!dmjch) return -ENOMEM;
platform_set_drvdata(pdev, dmjch); platform_set_drvdata(pdev, dmjch);
@ -245,8 +245,6 @@ static int dmj_char_remove(struct platform_device *pdev)
cdev_del(&dmjch->cdev); cdev_del(&dmjch->cdev);
unregister_chrdev(MKDEV(dmj_char_major, dmjch->minor), CLASS_NAME); unregister_chrdev(MKDEV(dmj_char_major, dmjch->minor), CLASS_NAME);
kfree(dmjch);
return 0; return 0;
} }
@ -318,4 +316,3 @@ MODULE_DESCRIPTION("Character device for the " HARDWARE_NAME " USB multitool");
MODULE_LICENSE("GPL v2"); MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:dmj-char"); MODULE_ALIAS("platform:dmj-char");
MODULE_DEPEND

View File

@ -3,6 +3,9 @@
* Driver for the DapperMime-JTAG USB multitool: base MFD driver * Driver for the DapperMime-JTAG USB multitool: base MFD driver
* *
* Copyright (c) sys64738 and haskal * Copyright (c) sys64738 and haskal
*
* Adapted from:
* dln2.c Copyright (c) 2014 Intel Corporation
*/ */
#include <linux/kernel.h> #include <linux/kernel.h>
@ -403,9 +406,12 @@ static int dmj_hw_init(struct dmj_dev *dmj)
/* MFD stuff */ /* MFD stuff */
static const struct mfd_cell dmj_mfd_devs[] = { static const struct mfd_cell dmj_mfd_char[] = {
{ .name = "dmj-char" }, { .name = "dmj-char" },
}; };
static const struct mfd_cell dmj_mfd_i2c[] = {
{ .name = "dmj-i2c" },
};
/* USB device control */ /* USB device control */
@ -453,9 +459,9 @@ static int dmj_probe(struct usb_interface *itf, const struct usb_device_id *usb_
} }
if (dmj->dmj_mode == 1) { if (dmj->dmj_mode == 1) {
ret = mfd_add_hotplug_devices(dev, dmj_mfd_devs, ARRAY_SIZE(dmj_mfd_devs)); ret = mfd_add_hotplug_devices(dev, dmj_mfd_char, ARRAY_SIZE(dmj_mfd_char));
if (ret) { if (ret) {
dev_err(dev, "failed to add MFD devices\n"); dev_err(dev, "failed to add MFD character devices\n");
goto out_free; goto out_free;
} }
@ -463,7 +469,11 @@ static int dmj_probe(struct usb_interface *itf, const struct usb_device_id *usb_
// TODO: add SPI MFD // TODO: add SPI MFD
} }
if (dmj->dmj_mode & DMJ_FEATURE_MODE1_I2C) { if (dmj->dmj_mode & DMJ_FEATURE_MODE1_I2C) {
// TODO: add I2C MFD ret = mfd_add_hotplug_devices(dev, dmj_mfd_i2c, ARRAY_SIZE(dmj_mfd_i2c));
if (ret) {
dev_err(dev, "failed to add MFD I2C devices\n");
goto out_free;
}
} }
if (dmj->dmj_mode & DMJ_FEATURE_MODE1_TEMPSENSOR) { if (dmj->dmj_mode & DMJ_FEATURE_MODE1_TEMPSENSOR) {
// TODO: add tempsensor MFD // TODO: add tempsensor MFD

View File

@ -37,6 +37,18 @@ struct dmj_platform_data {
#define DMJ_XFER_FLAGS_PARSE_RESP (1<<0) #define DMJ_XFER_FLAGS_PARSE_RESP (1<<0)
#define DMJ_XFER_FLAGS_FILL_RECVBUF (1<<1) #define DMJ_XFER_FLAGS_FILL_RECVBUF (1<<1)
inline static const char *dmj_get_protoerr(int err)
{
switch (err) {
case DMJ_RESP_STAT_OK: return "ok";
case DMJ_RESP_STAT_ILLCMD: return "unknown/unimplemented command";
case DMJ_RESP_STAT_BADMODE: return "bad mode";
case DMJ_RESP_STAT_NOSUCHMODE: return "no such mode available";
case DMJ_RESP_STAT_BADARG: return "illegal argument";
default: return "???";
}
}
int dmj_transfer(struct platform_device *pdev, int cmd, int recvflags, int dmj_transfer(struct platform_device *pdev, int cmd, int recvflags,
const void *wbuf, int wbufsize, void *rbuf, int *rbufsize); const void *wbuf, int wbufsize, void *rbuf, int *rbufsize);

285
host/modules/i2c-dmj.c Normal file
View File

@ -0,0 +1,285 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Driver for the DapperMime-JTAG USB multitool: USB-I2C adapter
*
* Copyright (c) sys64738 and haskal
*
* Adapted from:
* i2c-dln2.c Copyright (c) 2014 Intel Corporation
* i2c-tiny-usb.c Copyright (C) 2006-2007 Till Harbaum (Till@Harbaum.org)
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/platform_device.h>
#include <linux/acpi.h>
#if 0
#include <linux/mfd/dmj.h>
#else
#include "dmj.h"
#endif
#define HARDWARE_NAME "DapperMime-JTAG"
#define DMJ_I2C_MAX_XSFER_SIZE 64
#define DMJ_I2C_CMD_ECHO 0x00
#define DMJ_I2C_CMD_GET_FUNC 0x01
#define DMJ_I2C_CMD_SET_DELAY 0x02
#define DMJ_I2C_CMD_GET_STATUS 0x03
#define DMJ_I2C_CMD_DO_XFER 0x04
#define DMJ_I2C_CMD_DO_XFER_B 0x05
#define DMJ_I2C_CMD_DO_XFER_E 0x06
#define DMJ_I2C_CMD_DO_XFER_BE 0x07
#define DMJ_I2C_FLG_XFER_B 0x01
#define DMJ_I2C_FLG_XFER_E 0x02
#define DMJ_I2C_STAT_IDLE 0
#define DMJ_I2C_STAT_ACK 1
#define DMJ_I2C_STAT_NAK 2
static unsigned short delay = 10;
module_param(delay, ushort, 0);
MODULE_PARM_DESC(delay, "bit delay in microseconds (default is 10us for 100kHz)");
struct dmj_i2c {
struct platform_device *pdev;
struct i2c_adapter adapter;
};
static int dmj_i2c_read(struct dmj_i2c *dmji, struct i2c_msg *msg, int cmd)
{
struct device *dev = &dmji->pdev->dev;
uint8_t *respbuf;
int ret, len;
uint8_t cmdbuf[1+2+2+2]; /* cmd, flags, addr, len */
len = msg->len;
respbuf = kzalloc(len, GFP_KERNEL); /* 0 length: returns status byte */
if (!respbuf) return -ENOMEM;
cmdbuf[0] = cmd;
cmdbuf[1] = (msg->flags >> 0) & 0xff;
cmdbuf[2] = (msg->flags >> 8) & 0xff;
cmdbuf[3] = (msg->addr >> 0) & 0xff;
cmdbuf[4] = (msg->addr >> 8) & 0xff;
cmdbuf[5] = (msg->len >> 0) & 0xff;
cmdbuf[6] = (msg->len >> 8) & 0xff;
ret = dmj_transfer(dmji->pdev, DMJ_CMD_MODE1_I2C, DMJ_XFER_FLAGS_PARSE_RESP,
cmdbuf, sizeof(cmdbuf), respbuf, &len);
if (ret < 0) {
dev_err(dev, "read: USB comms error: %d\n", ret);
goto err_free;
} else if (ret) {
dev_err(dev, "read: protocol error: %s (%d)\n", dmj_get_protoerr(ret), ret);
ret = -EIO;
goto err_free;
}
memcpy(msg->buf, respbuf, msg->len);
kfree(respbuf);
return len;
err_free:
kfree(respbuf);
return ret;
}
static int dmj_i2c_write(struct dmj_i2c *dmji, struct i2c_msg *msg, int cmd)
{
struct device *dev = &dmji->pdev->dev;
uint8_t *cmdbuf;
int ret, len;
len = msg->len + 1+2+2+2;
cmdbuf = kzalloc(len, GFP_KERNEL); /* cmd, flags, addr, len */
if (!cmdbuf) return -ENOMEM;
cmdbuf[0] = cmd;
cmdbuf[1] = (msg->flags >> 0) & 0xff;
cmdbuf[2] = (msg->flags >> 8) & 0xff;
cmdbuf[3] = (msg->addr >> 0) & 0xff;
cmdbuf[4] = (msg->addr >> 8) & 0xff;
cmdbuf[5] = (msg->len >> 0) & 0xff;
cmdbuf[6] = (msg->len >> 8) & 0xff;
memcpy(&cmdbuf[7], msg->buf, msg->len);
ret = dmj_write(dmji->pdev, DMJ_CMD_MODE1_I2C, cmdbuf, len);
if (ret < 0) {
dev_err(dev, "write: USB comms error: %d\n", ret);
goto err_free;
} else if (ret) {
dev_err(dev, "write: protocol error: %s (%d)\n", dmj_get_protoerr(ret), ret);
ret = -EIO;
goto err_free;
}
kfree(cmdbuf);
return msg->len;
err_free:
kfree(cmdbuf);
return ret;
}
static int dmj_i2c_xfer(struct i2c_adapter *a, struct i2c_msg *msgs, int nmsg)
{
struct dmj_i2c *dmji = i2c_get_adapdata(a);
struct device *dev = &dmji->pdev->dev;
struct i2c_msg *pmsg;
int i, ret, cmd, stlen;
uint8_t status, i2ccmd;
for (i = 0; i < nmsg; ++i) {
cmd = DMJ_I2C_CMD_DO_XFER;
if (i == 0) cmd |= DMJ_I2C_FLG_XFER_B;
if (i == nmsg-1) cmd |= DMJ_I2C_FLG_XFER_E;
pmsg = &msgs[i];
dev_dbg(&a->dev,
" %d: %s (flags %04x) %d bytes to 0x%02x\n",
i, pmsg->flags & I2C_M_RD ? "read" : "write",
pmsg->flags, pmsg->len, pmsg->addr);
if (pmsg->flags & I2C_M_RD) {
ret = dmj_i2c_read(dmji, pmsg, cmd);
if (ret < 0) goto err_ret;
if (ret != pmsg->len) {
dev_err(dev, "xfer rd: bad length %d vs %d\n", ret, pmsg->len);
ret = -EMSGSIZE;
goto err_ret;
}
} else {
ret = dmj_i2c_write(dmji, pmsg, cmd);
if (ret < 0) goto err_ret;
if (ret != pmsg->len) {
dev_err(dev, "xfer wr: bad length %d vs %d\n", ret, pmsg->len);
ret = -EMSGSIZE;
goto err_ret;
}
}
/* read status */
i2ccmd = DMJ_I2C_CMD_GET_STATUS;
stlen = sizeof(status);
ret = dmj_transfer(dmji->pdev, DMJ_CMD_MODE1_I2C, DMJ_XFER_FLAGS_PARSE_RESP,
&i2ccmd, sizeof(i2ccmd), &status, &stlen);
if (ret < 0) {
dev_err(dev, "xfer get stat: USB comms error: %d\n", ret);
goto err_ret;
} else if (ret) {
dev_err(dev, "xfer get stat: protocol error: %s (%d)\n", dmj_get_protoerr(ret), ret);
ret = -EIO;
goto err_ret;
} else if (stlen != sizeof(status)) {
dev_err(dev, "xfer get stat: unexpected return length: want %zu, got %d\n", sizeof(status), stlen);
ret = -EMSGSIZE;
goto err_ret;
}
dev_dbg(dev, " status = %d\n", status);
if (status == DMJ_I2C_STAT_NAK) {
ret = -ENXIO;
goto err_ret;
}
}
ret = i;
err_ret:
return ret;
}
static uint32_t dmj_i2c_func(struct i2c_adapter *a)
{
struct dmj_i2c *dmji = i2c_get_adapdata(a);
struct device *dev = &dmji->pdev->dev;
__le32 func = 0;
int len = sizeof(func), ret;
uint8_t i2ccmd = DMJ_I2C_CMD_GET_FUNC;
ret = dmj_transfer(dmji->pdev, DMJ_CMD_MODE1_I2C, DMJ_XFER_FLAGS_PARSE_RESP,
&i2ccmd, sizeof(i2ccmd), &func, &len);
if (ret < 0) {
dev_err(dev, "func: USB comms error: %d\n", ret);
return 0;
} else if (ret) {
dev_err(dev, "func: protocol error: %s (%d)\n", dmj_get_protoerr(ret), ret);
return 0;
} else if (len != sizeof(func)) {
dev_err(dev, "func: unexpected return length: want %zu, got %d\n", sizeof(func), len);
return 0;
}
dev_warn(dev, "I2C functionality: 0x%08x\n", le32_to_cpu(func));
return le32_to_cpu(func);
}
static const struct i2c_algorithm dmj_i2c_algo = {
.master_xfer = dmj_i2c_xfer,
.functionality = dmj_i2c_func
};
static const struct i2c_adapter_quirks dmj_i2c_quirks = {
.max_read_len = DMJ_I2C_MAX_XSFER_SIZE,
.max_write_len = DMJ_I2C_MAX_XSFER_SIZE,
};
static int dmj_i2c_probe(struct platform_device *pdev)
{
int ret;
struct dmj_i2c *dmji;
struct device *dev = &pdev->dev;
// TODO: check if mode 1 and I2C available?
// TODO: test ECHO cmd
dmji = devm_kzalloc(dev, sizeof(*dmji), GFP_KERNEL);
if (!dmji) return -ENOMEM;
dmji->pdev = pdev;
dmji->adapter.owner = THIS_MODULE;
dmji->adapter.class = I2C_CLASS_HWMON;
dmji->adapter.algo = &dmj_i2c_algo;
dmji->adapter.quirks = &dmj_i2c_quirks; /* TODO: is this needed? probably... */
dmji->adapter.dev.of_node = dev->of_node;
i2c_set_adapdata(&dmji->adapter, dmji);
// TODO: set delay from module param
snprintf(dmji->adapter.name, sizeof(dmji->adapter.name), "%s-%s",
"dln2-i2c", dev_name(pdev->dev.parent));
platform_set_drvdata(pdev, dmji);
return i2c_add_adapter(&dmji->adapter);
}
static int dmj_i2c_remove(struct platform_device *pdev)
{
struct dmj_i2c *dmji = platform_get_drvdata(pdev);
i2c_del_adapter(&dmji->adapter);
return 0;
}
static struct platform_driver dmj_i2c_drv = {
.driver.name = "dmj-i2c",
.probe = dmj_i2c_probe,
.remove = dmj_i2c_remove
};
module_platform_driver(dmj_i2c_drv);
MODULE_AUTHOR("sys64738 <sys64738@disroot.org>");
MODULE_AUTHOR("haskal <haskal@awoo.systems>");
MODULE_DESCRIPTION("I2C interface driver for the " HARDWARE_NAME " USB multitool");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:dmj-i2c");

View File

@ -263,8 +263,7 @@ void i2ctu_bulk_cmd(void) {
vnd_cfg_write_resp(cfg_resp_ok, (uint32_t)us, txbuf); vnd_cfg_write_resp(cfg_resp_ok, (uint32_t)us, txbuf);
} else if (cmd.len == 0) { } else if (cmd.len == 0) {
handle_probe(&cmd); handle_probe(&cmd);
txbuf[0] = status; vnd_cfg_write_resp(cfg_resp_ok, 0, NULL);
vnd_cfg_write_resp(cfg_resp_ok, 1, txbuf);
} else { } else {
us = cmd.len; us = cmd.len;
if (us > sizeof rxbuf) us = sizeof rxbuf; if (us > sizeof rxbuf) us = sizeof rxbuf;
@ -272,6 +271,7 @@ void i2ctu_bulk_cmd(void) {
rxbuf[i] = vnd_cfg_read_byte(); rxbuf[i] = vnd_cfg_read_byte();
handle_write(&cmd); handle_write(&cmd);
vnd_cfg_write_resp(cfg_resp_ok, 0, NULL);
} }
} break; } break;