|
@@ -0,0 +1,542 @@
|
|
|
|
|
+#include <linux/module.h>
|
|
|
|
|
+#include <linux/kernel.h>
|
|
|
|
|
+#include <linux/fs.h>
|
|
|
|
|
+#include <linux/i2c.h>
|
|
|
|
|
+#include <linux/uaccess.h>
|
|
|
|
|
+#include <linux/miscdevice.h>
|
|
|
|
|
+#include <linux/pci.h>
|
|
|
|
|
+#include <linux/mutex.h>
|
|
|
|
|
+#include <linux/delay.h>
|
|
|
|
|
+#include <linux/slab.h>
|
|
|
|
|
+#include <linux/string.h>
|
|
|
|
|
+
|
|
|
|
|
+#define DRIVER_NAME "lcd2002"
|
|
|
|
|
+#define LCD_I2C_ADDR_DEFAULT 0x3E
|
|
|
|
|
+
|
|
|
|
|
+/* 控制字节定义 */
|
|
|
|
|
+#define LCD_CONTROL_BYTE 0x00
|
|
|
|
|
+#define LCD_DATA_BYTE 0x40
|
|
|
|
|
+
|
|
|
|
|
+/* 基本指令 */
|
|
|
|
|
+#define LCD_CLEAR_DISPLAY 0x01
|
|
|
|
|
+#define LCD_RETURN_HOME 0x02
|
|
|
|
|
+#define LCD_ENTRY_MODE_SET 0x04
|
|
|
|
|
+#define LCD_DISPLAY_CONTROL 0x08
|
|
|
|
|
+#define LCD_CURSOR_SHIFT 0x10
|
|
|
|
|
+#define LCD_FUNCTION_SET 0x20
|
|
|
|
|
+#define LCD_SET_CGRAM_ADDR 0x40
|
|
|
|
|
+#define LCD_SET_DDRAM_ADDR 0x80
|
|
|
|
|
+
|
|
|
|
|
+/* Entry Mode */
|
|
|
|
|
+#define LCD_ENTRY_RIGHT 0x00
|
|
|
|
|
+#define LCD_ENTRY_LEFT 0x02
|
|
|
|
|
+
|
|
|
|
|
+/* Display Control */
|
|
|
|
|
+#define LCD_DISPLAY_ON 0x04
|
|
|
|
|
+#define LCD_DISPLAY_OFF 0x00
|
|
|
|
|
+#define LCD_CURSOR_ON 0x02
|
|
|
|
|
+#define LCD_CURSOR_OFF 0x00
|
|
|
|
|
+#define LCD_BLINK_ON 0x01
|
|
|
|
|
+#define LCD_BLINK_OFF 0x00
|
|
|
|
|
+
|
|
|
|
|
+#define LCD_LINE1_ADDR 0x00
|
|
|
|
|
+#define LCD_LINE2_ADDR 0x40
|
|
|
|
|
+#define LCD_COLS 20
|
|
|
|
|
+#define LCD_ROWS 2
|
|
|
|
|
+
|
|
|
|
|
+/* IOCTL */
|
|
|
|
|
+#define LCD_IOCTL_MAGIC 'L'
|
|
|
|
|
+#define LCD_IOCTL_CLEAR _IO(LCD_IOCTL_MAGIC, 0)
|
|
|
|
|
+#define LCD_IOCTL_HOME _IO(LCD_IOCTL_MAGIC, 1)
|
|
|
|
|
+#define LCD_IOCTL_SET_CURSOR _IOW(LCD_IOCTL_MAGIC, 2, struct lcd_pos)
|
|
|
|
|
+#define LCD_IOCTL_DISPLAY_CTRL _IOW(LCD_IOCTL_MAGIC, 3, unsigned char[3])
|
|
|
|
|
+
|
|
|
|
|
+struct lcd_pos {
|
|
|
|
|
+ unsigned char col;
|
|
|
|
|
+ unsigned char row;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+struct lcd2002_dev {
|
|
|
|
|
+ struct i2c_adapter *adapter;
|
|
|
|
|
+ struct mutex lock;
|
|
|
|
|
+ unsigned char addr;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+static struct lcd2002_dev *lcd_dev = NULL;
|
|
|
|
|
+
|
|
|
|
|
+/* 模块参数 - 多种匹配方式 */
|
|
|
|
|
+static char *pci_addr = "0000:00:15.0";
|
|
|
|
|
+module_param(pci_addr, charp, 0444);
|
|
|
|
|
+MODULE_PARM_DESC(pci_addr, "PCI address (e.g., 0000:00:15.0)");
|
|
|
|
|
+
|
|
|
|
|
+static char *adapter_name = NULL; /* 如: "Synopsys DesignWare I2C adapter" */
|
|
|
|
|
+module_param(adapter_name, charp, 0444);
|
|
|
|
|
+MODULE_PARM_DESC(adapter_name, "I2C adapter name (e.g., Synopsys DesignWare)");
|
|
|
|
|
+
|
|
|
|
|
+static int i2c_bus = -1; /* 直接指定总线号,如 1 对应 /dev/i2c-1 */
|
|
|
|
|
+module_param(i2c_bus, int, 0444);
|
|
|
|
|
+MODULE_PARM_DESC(i2c_bus, "I2C bus number (e.g., 1 for /dev/i2c-1)");
|
|
|
|
|
+
|
|
|
|
|
+static unsigned char i2c_address = LCD_I2C_ADDR_DEFAULT;
|
|
|
|
|
+module_param(i2c_address, byte, 0444);
|
|
|
|
|
+MODULE_PARM_DESC(i2c_address, "LCD I2C address (default: 0x3E)");
|
|
|
|
|
+
|
|
|
|
|
+static int debug = 1;
|
|
|
|
|
+module_param(debug, int, 0644);
|
|
|
|
|
+MODULE_PARM_DESC(debug, "Debug level (0=none, 1=normal, 2=verbose)");
|
|
|
|
|
+
|
|
|
|
|
+/* 延时 */
|
|
|
|
|
+static inline void lcd_delay_ms(unsigned int ms)
|
|
|
|
|
+{
|
|
|
|
|
+ msleep(ms);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* I2C写 */
|
|
|
|
|
+static int lcd_i2c_write(unsigned char *buf, int len)
|
|
|
|
|
+{
|
|
|
|
|
+ struct i2c_msg msg;
|
|
|
|
|
+ int ret;
|
|
|
|
|
+
|
|
|
|
|
+ if (!lcd_dev || !lcd_dev->adapter)
|
|
|
|
|
+ return -ENODEV;
|
|
|
|
|
+
|
|
|
|
|
+ msg.addr = lcd_dev->addr;
|
|
|
|
|
+ msg.flags = 0;
|
|
|
|
|
+ msg.len = len;
|
|
|
|
|
+ msg.buf = buf;
|
|
|
|
|
+
|
|
|
|
|
+ ret = i2c_transfer(lcd_dev->adapter, &msg, 1);
|
|
|
|
|
+ if (ret != 1) {
|
|
|
|
|
+ dev_err(&lcd_dev->adapter->dev, "I2C write failed: %d\n", ret);
|
|
|
|
|
+ return ret < 0 ? ret : -EIO;
|
|
|
|
|
+ }
|
|
|
|
|
+ return 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 发送指令 */
|
|
|
|
|
+static int lcd_send_command(unsigned char cmd)
|
|
|
|
|
+{
|
|
|
|
|
+ unsigned char buf[2];
|
|
|
|
|
+ int ret;
|
|
|
|
|
+
|
|
|
|
|
+ buf[0] = LCD_CONTROL_BYTE;
|
|
|
|
|
+ buf[1] = cmd;
|
|
|
|
|
+
|
|
|
|
|
+ mutex_lock(&lcd_dev->lock);
|
|
|
|
|
+ ret = lcd_i2c_write(buf, 2);
|
|
|
|
|
+ mutex_unlock(&lcd_dev->lock);
|
|
|
|
|
+
|
|
|
|
|
+ if (ret == 0)
|
|
|
|
|
+ lcd_delay_ms(2);
|
|
|
|
|
+ return ret;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 发送数据 */
|
|
|
|
|
+static int lcd_send_data(unsigned char data)
|
|
|
|
|
+{
|
|
|
|
|
+ unsigned char buf[2];
|
|
|
|
|
+ int ret;
|
|
|
|
|
+
|
|
|
|
|
+ buf[0] = LCD_DATA_BYTE;
|
|
|
|
|
+ buf[1] = data;
|
|
|
|
|
+
|
|
|
|
|
+ mutex_lock(&lcd_dev->lock);
|
|
|
|
|
+ ret = lcd_i2c_write(buf, 2);
|
|
|
|
|
+ mutex_unlock(&lcd_dev->lock);
|
|
|
|
|
+
|
|
|
|
|
+ if (ret == 0)
|
|
|
|
|
+ lcd_delay_ms(1);
|
|
|
|
|
+ return ret;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 初始化LCD */
|
|
|
|
|
+static int lcd_init_display(void)
|
|
|
|
|
+{
|
|
|
|
|
+ int ret;
|
|
|
|
|
+
|
|
|
|
|
+ lcd_delay_ms(50);
|
|
|
|
|
+
|
|
|
|
|
+ /* 功能设置: 2行, 5x8点阵 (0x28) */
|
|
|
|
|
+ ret = lcd_send_command(LCD_FUNCTION_SET | 0x08);
|
|
|
|
|
+ if (ret < 0) return ret;
|
|
|
|
|
+ lcd_delay_ms(5);
|
|
|
|
|
+
|
|
|
|
|
+ /* 显示开,光标关,闪烁关 (0x0C) */
|
|
|
|
|
+ ret = lcd_send_command(LCD_DISPLAY_CONTROL | LCD_DISPLAY_ON | LCD_CURSOR_OFF | LCD_BLINK_OFF);
|
|
|
|
|
+ if (ret < 0) return ret;
|
|
|
|
|
+
|
|
|
|
|
+ /* 清屏 */
|
|
|
|
|
+ ret = lcd_send_command(LCD_CLEAR_DISPLAY);
|
|
|
|
|
+ if (ret < 0) return ret;
|
|
|
|
|
+ lcd_delay_ms(3);
|
|
|
|
|
+
|
|
|
|
|
+ /* 输入模式: 光标右移 (0x06) */
|
|
|
|
|
+ ret = lcd_send_command(LCD_ENTRY_MODE_SET | LCD_ENTRY_RIGHT);
|
|
|
|
|
+
|
|
|
|
|
+ return ret;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 设置光标 */
|
|
|
|
|
+static int lcd_set_cursor(unsigned char col, unsigned char row)
|
|
|
|
|
+{
|
|
|
|
|
+ unsigned char addr;
|
|
|
|
|
+ if (col >= LCD_COLS || row >= LCD_ROWS)
|
|
|
|
|
+ return -EINVAL;
|
|
|
|
|
+ addr = (row == 0) ? (LCD_LINE1_ADDR + col) : (LCD_LINE2_ADDR + col);
|
|
|
|
|
+ return lcd_send_command(LCD_SET_DDRAM_ADDR | addr);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 清屏 */
|
|
|
|
|
+static int lcd_clear(void)
|
|
|
|
|
+{
|
|
|
|
|
+ int ret = lcd_send_command(LCD_CLEAR_DISPLAY);
|
|
|
|
|
+ if (ret == 0) lcd_delay_ms(2);
|
|
|
|
|
+ return ret;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 显示控制 */
|
|
|
|
|
+static int lcd_display_control(unsigned char display, unsigned char cursor, unsigned char blink)
|
|
|
|
|
+{
|
|
|
|
|
+ return lcd_send_command(LCD_DISPLAY_CONTROL | display | cursor | blink);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 写字符串 */
|
|
|
|
|
+static int lcd_write_string(const char *str, size_t len)
|
|
|
|
|
+{
|
|
|
|
|
+ size_t i;
|
|
|
|
|
+ int ret = 0;
|
|
|
|
|
+ unsigned char row = 0, col = 0;
|
|
|
|
|
+
|
|
|
|
|
+ for (i = 0; i < len; i++) {
|
|
|
|
|
+ char c = str[i];
|
|
|
|
|
+ if (c == '\n') {
|
|
|
|
|
+ row = (row + 1) % LCD_ROWS;
|
|
|
|
|
+ col = 0;
|
|
|
|
|
+ ret = lcd_set_cursor(col, row);
|
|
|
|
|
+ } else if (c == '\r') {
|
|
|
|
|
+ col = 0;
|
|
|
|
|
+ ret = lcd_set_cursor(col, row);
|
|
|
|
|
+ } else if (c == '\f') {
|
|
|
|
|
+ ret = lcd_clear();
|
|
|
|
|
+ row = col = 0;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ if (col >= LCD_COLS) {
|
|
|
|
|
+ row = (row + 1) % LCD_ROWS;
|
|
|
|
|
+ col = 0;
|
|
|
|
|
+ ret = lcd_set_cursor(col, row);
|
|
|
|
|
+ if (ret < 0) break;
|
|
|
|
|
+ }
|
|
|
|
|
+ ret = lcd_send_data(c);
|
|
|
|
|
+ col++;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (ret < 0) break;
|
|
|
|
|
+ }
|
|
|
|
|
+ return ret;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 调试:打印适配器信息 */
|
|
|
|
|
+static void print_adapter_info(struct i2c_adapter *adap, int nr)
|
|
|
|
|
+{
|
|
|
|
|
+ struct device *parent;
|
|
|
|
|
+ char pci_str[64] = "N/A";
|
|
|
|
|
+
|
|
|
|
|
+ if (!debug) return;
|
|
|
|
|
+
|
|
|
|
|
+ pr_info(" [%d] %s (class: 0x%lx)\n", nr, adap->name, adap->class);
|
|
|
|
|
+
|
|
|
|
|
+ parent = adap->dev.parent;
|
|
|
|
|
+ while (parent) {
|
|
|
|
|
+ if (parent->bus == &pci_bus_type) {
|
|
|
|
|
+ struct pci_dev *pdev = to_pci_dev(parent);
|
|
|
|
|
+ snprintf(pci_str, sizeof(pci_str), "%04x:%02x:%02x.%x",
|
|
|
|
|
+ pci_domain_nr(pdev->bus),
|
|
|
|
|
+ pdev->bus->number,
|
|
|
|
|
+ PCI_SLOT(pdev->devfn),
|
|
|
|
|
+ PCI_FUNC(pdev->devfn));
|
|
|
|
|
+ pr_info(" -> PCI: %s [%s]\n", pci_str, dev_name(parent));
|
|
|
|
|
+ break;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ pr_info(" -> %s [%s]\n", dev_name(parent), parent->bus ? parent->bus->name : "no-bus");
|
|
|
|
|
+ }
|
|
|
|
|
+ parent = parent->parent;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 方式1: 通过PCI地址查找 */
|
|
|
|
|
+static struct i2c_adapter *find_by_pci(const char *target_pci)
|
|
|
|
|
+{
|
|
|
|
|
+ struct i2c_adapter *adapter;
|
|
|
|
|
+ int nr = 0;
|
|
|
|
|
+ bool found = false;
|
|
|
|
|
+
|
|
|
|
|
+ pr_info("Searching for PCI %s...\n", target_pci);
|
|
|
|
|
+
|
|
|
|
|
+ while ((adapter = i2c_get_adapter(nr))) {
|
|
|
|
|
+ struct device *parent = adapter->dev.parent;
|
|
|
|
|
+ char pci_buf[32] = "";
|
|
|
|
|
+
|
|
|
|
|
+ if (debug >= 2)
|
|
|
|
|
+ print_adapter_info(adapter, nr);
|
|
|
|
|
+
|
|
|
|
|
+ /* 递归向上查找PCI设备 */
|
|
|
|
|
+ while (parent && !found) {
|
|
|
|
|
+ if (parent->bus == &pci_bus_type) {
|
|
|
|
|
+ struct pci_dev *pdev = to_pci_dev(parent);
|
|
|
|
|
+ snprintf(pci_buf, sizeof(pci_buf), "%04x:%02x:%02x.%x",
|
|
|
|
|
+ pci_domain_nr(pdev->bus),
|
|
|
|
|
+ pdev->bus->number,
|
|
|
|
|
+ PCI_SLOT(pdev->devfn),
|
|
|
|
|
+ PCI_FUNC(pdev->devfn));
|
|
|
|
|
+
|
|
|
|
|
+ if (strcmp(pci_buf, target_pci) == 0) {
|
|
|
|
|
+ pr_info("Found match at i2c-%d: %s\n", nr, adapter->name);
|
|
|
|
|
+ found = true;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ break; /* 只检查第一级PCI父设备 */
|
|
|
|
|
+ }
|
|
|
|
|
+ parent = parent->parent;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (found)
|
|
|
|
|
+ return adapter;
|
|
|
|
|
+
|
|
|
|
|
+ i2c_put_adapter(adapter);
|
|
|
|
|
+ nr++;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return NULL;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 方式2: 通过名称查找 */
|
|
|
|
|
+static struct i2c_adapter *find_by_name(const char *name)
|
|
|
|
|
+{
|
|
|
|
|
+ struct i2c_adapter *adapter;
|
|
|
|
|
+ int nr = 0;
|
|
|
|
|
+
|
|
|
|
|
+ pr_info("Searching for adapter name '%s'...\n", name);
|
|
|
|
|
+
|
|
|
|
|
+ while ((adapter = i2c_get_adapter(nr))) {
|
|
|
|
|
+ if (debug >= 2)
|
|
|
|
|
+ print_adapter_info(adapter, nr);
|
|
|
|
|
+
|
|
|
|
|
+ if (strstr(adapter->name, name)) {
|
|
|
|
|
+ pr_info("Found match at i2c-%d: %s\n", nr, adapter->name);
|
|
|
|
|
+ return adapter;
|
|
|
|
|
+ }
|
|
|
|
|
+ i2c_put_adapter(adapter);
|
|
|
|
|
+ nr++;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return NULL;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 方式3: 通过总线号 */
|
|
|
|
|
+static struct i2c_adapter *find_by_bus(int bus)
|
|
|
|
|
+{
|
|
|
|
|
+ pr_info("Trying i2c bus %d...\n", bus);
|
|
|
|
|
+ return i2c_get_adapter(bus);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 查找适配器主函数 */
|
|
|
|
|
+static struct i2c_adapter *find_adapter(void)
|
|
|
|
|
+{
|
|
|
|
|
+ struct i2c_adapter *adapter = NULL;
|
|
|
|
|
+
|
|
|
|
|
+ /* 优先级1: 直接指定总线号 */
|
|
|
|
|
+ if (i2c_bus >= 0) {
|
|
|
|
|
+ adapter = find_by_bus(i2c_bus);
|
|
|
|
|
+ if (adapter) goto done;
|
|
|
|
|
+ pr_warn("Specified i2c_bus %d not found\n", i2c_bus);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /* 优先级2: PCI地址 */
|
|
|
|
|
+ if (pci_addr && strlen(pci_addr) > 0) {
|
|
|
|
|
+ adapter = find_by_pci(pci_addr);
|
|
|
|
|
+ if (adapter) goto done;
|
|
|
|
|
+ pr_warn("PCI address %s not found\n", pci_addr);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /* 优先级3: 适配器名称 */
|
|
|
|
|
+ if (adapter_name && strlen(adapter_name) > 0) {
|
|
|
|
|
+ adapter = find_by_name(adapter_name);
|
|
|
|
|
+ if (adapter) goto done;
|
|
|
|
|
+ pr_warn("Adapter name '%s' not found\n", adapter_name);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /* 回退: 自动检测常见名称 (Synopsys DesignWare) */
|
|
|
|
|
+ pr_info("Trying auto-detect (Synopsys DesignWare)...\n");
|
|
|
|
|
+ adapter = find_by_name("Synopsys DesignWare");
|
|
|
|
|
+ if (adapter) goto done;
|
|
|
|
|
+
|
|
|
|
|
+ /* 最后回退: 使用 i2c-1 (根据用户环境) */
|
|
|
|
|
+ pr_info("Trying fallback i2c-1...\n");
|
|
|
|
|
+ adapter = find_by_bus(1);
|
|
|
|
|
+
|
|
|
|
|
+done:
|
|
|
|
|
+ return adapter;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 文件操作 */
|
|
|
|
|
+static int lcd_open(struct inode *inode, struct file *file)
|
|
|
|
|
+{
|
|
|
|
|
+ return lcd_dev ? 0 : -ENODEV;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static int lcd_release(struct inode *inode, struct file *file)
|
|
|
|
|
+{
|
|
|
|
|
+ return 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static ssize_t lcd_write(struct file *file, const char __user *buf,
|
|
|
|
|
+ size_t count, loff_t *ppos)
|
|
|
|
|
+{
|
|
|
|
|
+ char *kbuf;
|
|
|
|
|
+ int ret;
|
|
|
|
|
+
|
|
|
|
|
+ if (count == 0) return 0;
|
|
|
|
|
+ if (count > 256) count = 256;
|
|
|
|
|
+
|
|
|
|
|
+ kbuf = kmalloc(count + 1, GFP_KERNEL);
|
|
|
|
|
+ if (!kbuf) return -ENOMEM;
|
|
|
|
|
+
|
|
|
|
|
+ if (copy_from_user(kbuf, buf, count)) {
|
|
|
|
|
+ kfree(kbuf);
|
|
|
|
|
+ return -EFAULT;
|
|
|
|
|
+ }
|
|
|
|
|
+ kbuf[count] = '\0';
|
|
|
|
|
+
|
|
|
|
|
+ ret = lcd_write_string(kbuf, count);
|
|
|
|
|
+ kfree(kbuf);
|
|
|
|
|
+
|
|
|
|
|
+ return ret < 0 ? ret : count;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static long lcd_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
|
|
|
|
+{
|
|
|
|
|
+ int ret = 0;
|
|
|
|
|
+
|
|
|
|
|
+ switch (cmd) {
|
|
|
|
|
+ case LCD_IOCTL_CLEAR:
|
|
|
|
|
+ ret = lcd_clear();
|
|
|
|
|
+ break;
|
|
|
|
|
+ case LCD_IOCTL_HOME:
|
|
|
|
|
+ ret = lcd_send_command(LCD_RETURN_HOME);
|
|
|
|
|
+ if (ret == 0) lcd_delay_ms(2);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case LCD_IOCTL_SET_CURSOR: {
|
|
|
|
|
+ struct lcd_pos pos;
|
|
|
|
|
+ if (copy_from_user(&pos, (void __user *)arg, sizeof(pos)))
|
|
|
|
|
+ return -EFAULT;
|
|
|
|
|
+ ret = lcd_set_cursor(pos.col, pos.row);
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ case LCD_IOCTL_DISPLAY_CTRL: {
|
|
|
|
|
+ unsigned char ctrl[3];
|
|
|
|
|
+ if (copy_from_user(ctrl, (void __user *)arg, sizeof(ctrl)))
|
|
|
|
|
+ return -EFAULT;
|
|
|
|
|
+ ret = lcd_display_control(ctrl[0], ctrl[1], ctrl[2]);
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ default:
|
|
|
|
|
+ ret = -EINVAL;
|
|
|
|
|
+ }
|
|
|
|
|
+ return ret;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static const struct file_operations lcd_fops = {
|
|
|
|
|
+ .owner = THIS_MODULE,
|
|
|
|
|
+ .open = lcd_open,
|
|
|
|
|
+ .release = lcd_release,
|
|
|
|
|
+ .write = lcd_write,
|
|
|
|
|
+ .unlocked_ioctl = lcd_ioctl,
|
|
|
|
|
+ .compat_ioctl = lcd_ioctl,
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+static struct miscdevice lcd_miscdev = {
|
|
|
|
|
+ .minor = MISC_DYNAMIC_MINOR,
|
|
|
|
|
+ .name = "lcd2002",
|
|
|
|
|
+ .fops = &lcd_fops,
|
|
|
|
|
+ .mode = 0666,
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+int lcd2x20_init(void)
|
|
|
|
|
+{
|
|
|
|
|
+ int ret;
|
|
|
|
|
+
|
|
|
|
|
+ pr_info("LCD2002 driver loading (PCI:%s, Bus:%d, Addr:0x%02X)...\n",
|
|
|
|
|
+ pci_addr ? pci_addr : "none", i2c_bus, i2c_address);
|
|
|
|
|
+
|
|
|
|
|
+ lcd_dev = kzalloc(sizeof(*lcd_dev), GFP_KERNEL);
|
|
|
|
|
+ if (!lcd_dev)
|
|
|
|
|
+ return -ENOMEM;
|
|
|
|
|
+
|
|
|
|
|
+ mutex_init(&lcd_dev->lock);
|
|
|
|
|
+ lcd_dev->addr = i2c_address;
|
|
|
|
|
+
|
|
|
|
|
+ /* 查找适配器 */
|
|
|
|
|
+ lcd_dev->adapter = find_adapter();
|
|
|
|
|
+ if (!lcd_dev->adapter) {
|
|
|
|
|
+ pr_err("Failed to find any suitable I2C adapter!\n");
|
|
|
|
|
+ pr_err("Available adapters (from i2cdetect -l):\n");
|
|
|
|
|
+ {
|
|
|
|
|
+ int nr = 0;
|
|
|
|
|
+ struct i2c_adapter *a;
|
|
|
|
|
+ while ((a = i2c_get_adapter(nr++))) {
|
|
|
|
|
+ pr_err(" i2c-%d: %s\n", nr-1, a->name);
|
|
|
|
|
+ i2c_put_adapter(a);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ ret = -ENODEV;
|
|
|
|
|
+ goto err_free;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ pr_info("Using I2C adapter: %s\n", lcd_dev->adapter->name);
|
|
|
|
|
+
|
|
|
|
|
+ /* 初始化LCD */
|
|
|
|
|
+ ret = lcd_init_display();
|
|
|
|
|
+ if (ret < 0) {
|
|
|
|
|
+ pr_err("LCD init failed: %d\n", ret);
|
|
|
|
|
+ goto err_put;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ ret = misc_register(&lcd_miscdev);
|
|
|
|
|
+ if (ret < 0) {
|
|
|
|
|
+ pr_err("Misc register failed: %d\n", ret);
|
|
|
|
|
+ goto err_put;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ pr_info("LCD2002 ready at /dev/%s\n", lcd_miscdev.name);
|
|
|
|
|
+
|
|
|
|
|
+ /* 欢迎信息 */
|
|
|
|
|
+ lcd_clear();
|
|
|
|
|
+ lcd_write_string("LCD2002 Driver OK", 17);
|
|
|
|
|
+ lcd_set_cursor(0, 1);
|
|
|
|
|
+ lcd_write_string("Intel ADL-N97", 13);
|
|
|
|
|
+
|
|
|
|
|
+ return 0;
|
|
|
|
|
+
|
|
|
|
|
+err_put:
|
|
|
|
|
+ i2c_put_adapter(lcd_dev->adapter);
|
|
|
|
|
+err_free:
|
|
|
|
|
+ kfree(lcd_dev);
|
|
|
|
|
+ lcd_dev = NULL;
|
|
|
|
|
+ return ret;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void lcd2x20_exit(void)
|
|
|
|
|
+{
|
|
|
|
|
+ if (!lcd_dev) return;
|
|
|
|
|
+
|
|
|
|
|
+ misc_deregister(&lcd_miscdev);
|
|
|
|
|
+ lcd_send_command(LCD_DISPLAY_CONTROL | LCD_DISPLAY_OFF);
|
|
|
|
|
+
|
|
|
|
|
+ if (lcd_dev->adapter)
|
|
|
|
|
+ i2c_put_adapter(lcd_dev->adapter);
|
|
|
|
|
+
|
|
|
|
|
+ kfree(lcd_dev);
|
|
|
|
|
+ lcd_dev = NULL;
|
|
|
|
|
+ pr_info("LCD2002 driver unloaded\n");
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+
|