瀏覽代碼

添加LCD驱动模块及测试程序

liu qidong [url ssh://qidong.liu@10.2.90.253:29418/] 1 周之前
父節點
當前提交
3c4d809716
共有 6 個文件被更改,包括 755 次插入4 次删除
  1. 1 1
      Makefile
  2. 542 0
      lcd_2x20.c
  3. 6 0
      lcd_2x20.h
  4. 14 2
      main.c
  5. 2 1
      test_app/build.sh
  6. 190 0
      test_app/lcd_app.c

+ 1 - 1
Makefile

@@ -3,7 +3,7 @@ CROSS_COMPILE=arm-poky-linux-gnueabi-
 #也可以同时编译多个模块  obj-m += export_symbol.o export_symbol1.o export_symbol2.o
 obj-m := coral.o
 
-coral-objs := main.o led.o light_ring.o ssegment.o ec_version.o buzzer.o fan.o writeprotect.o myname.o cash_drawers.o batteryled.o watchdog.o power.o switches.o backlight.o gsensor.o
+coral-objs := main.o led.o light_ring.o ssegment.o ec_version.o buzzer.o fan.o writeprotect.o myname.o cash_drawers.o batteryled.o watchdog.o power.o switches.o backlight.o gsensor.o lcd_2x20.o
 
 
 KERNELDIR := /lib/modules/$(shell uname -r)/build

+ 542 - 0
lcd_2x20.c

@@ -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");
+}
+
+

+ 6 - 0
lcd_2x20.h

@@ -0,0 +1,6 @@
+#ifndef LCD2002_IOCTL_H
+#define LCD2002_IOCTL_H
+
+int lcd2x20_init(void);
+void lcd2x20_exit(void);
+#endif /* LCD2002_IOCTL_H */

+ 14 - 2
main.c

@@ -31,6 +31,7 @@
 #include "switches.h"
 #include "backlight.h"
 #include "gsensor.h"
+#include "lcd_2x20.h"
 
 struct kobject *vfiec_kobj = NULL;
 
@@ -136,9 +137,19 @@ static int __init all_driver_init(void)
         goto out_ssegment;
     }
 
+    ret = lcd2x20_init();
+    if(ret != 0)
+    {
+        printk(KERN_ERR "lcd2x20_init failed\n");
+        goto out_gensor;
+    }
+
     printk(KERN_INFO "all_driver_init\n");
     return ret;
-
+out_lcd:
+    lcd2x20_exit();
+out_gensor:
+    gsensor_exit_main();
 out_ssegment:
     ssegment_exit();
 out_led:
@@ -188,8 +199,9 @@ static void __exit all_driver_exit(void)
     light_ring_exit();
     led_exit();
     ssegment_exit();
-    kobject_put(vfiec_kobj);
     gsensor_exit_main();
+    lcd2x20_exit();
+    kobject_put(vfiec_kobj);
 }
 
 module_init(all_driver_init);

+ 2 - 1
test_app/build.sh

@@ -4,4 +4,5 @@ gcc Beep_userspace.c -o Beep_userspace
 gcc setss.c -o setss
 gcc test_beep.c -o test_beep
 gcc test_ssegment.c -o test_ssegment
-gcc power_app.c -o power_app
+gcc power_app.c -o power_app
+gcc lcd_app.c -o lcd_app

+ 190 - 0
test_app/lcd_app.c

@@ -0,0 +1,190 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <stdint.h>
+
+#define LCD_DEVICE "/dev/lcd2002"
+
+/* 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;
+};
+
+/* 显示控制参数 */
+#define DISPLAY_ON  0x04
+#define DISPLAY_OFF 0x00
+#define CURSOR_ON   0x02
+#define CURSOR_OFF  0x00
+#define BLINK_ON    0x01
+#define BLINK_OFF   0x00
+
+int fd;
+
+void test_basic_display(void)
+{
+    printf("Test 1: Basic Display\n");
+    
+    ioctl(fd, LCD_IOCTL_CLEAR);
+    usleep(100000);
+    
+    write(fd, "Hello, Intel ADL!", 17);
+    sleep(2);
+    
+    /* 写入第二行 */
+    struct lcd_pos pos = {0, 1};
+    ioctl(fd, LCD_IOCTL_SET_CURSOR, &pos);
+    write(fd, "N97 Platform", 12);
+    sleep(2);
+}
+
+void test_cursor_and_blink(void)
+{
+    printf("Test 2: Cursor and Blink\n");
+    
+    ioctl(fd, LCD_IOCTL_CLEAR);
+    write(fd, "Cursor Test:", 12);
+    
+    struct lcd_pos pos = {0, 1};
+    ioctl(fd, LCD_IOCTL_SET_CURSOR, &pos);
+    write(fd, "See me?", 7);
+    
+    /* 打开光标 */
+    unsigned char ctrl[3] = {DISPLAY_ON, CURSOR_ON, BLINK_OFF};
+    ioctl(fd, LCD_IOCTL_DISPLAY_CTRL, ctrl);
+    sleep(2);
+    
+    /* 打开闪烁 */
+    ctrl[2] = BLINK_ON;
+    ioctl(fd, LCD_IOCTL_DISPLAY_CTRL, ctrl);
+    sleep(2);
+    
+    /* 恢复正常 */
+    ctrl[1] = CURSOR_OFF;
+    ctrl[2] = BLINK_OFF;
+    ioctl(fd, LCD_IOCTL_DISPLAY_CTRL, ctrl);
+}
+
+void test_long_text_wrap(void)
+{
+    printf("Test 3: Text Wrapping\n");
+    
+    ioctl(fd, LCD_IOCTL_CLEAR);
+    
+    /* 写入超过20个字符,测试自动换行 */
+    write(fd, "This is a long text that should wrap to next line automatically", 61);
+    sleep(3);
+}
+
+void test_special_chars(void)
+{
+    printf("Test 4: Special Characters\n");
+    
+    ioctl(fd, LCD_IOCTL_CLEAR);
+    write(fd, "Line1\nLine2", 11);  /* \n应该换行 */
+    sleep(2);
+    
+    ioctl(fd, LCD_IOCTL_CLEAR);
+    write(fd, "Test\rOverwrite", 14);  /* \r应该回车 */
+    sleep(2);
+}
+
+void test_full_display(void)
+{
+    printf("Test 5: Full 20x2 Display\n");
+    
+    ioctl(fd, LCD_IOCTL_CLEAR);
+    
+    char line1[21], line2[21];
+    int i;
+    
+    /* 第一行数字 */
+    for (i = 0; i < 20; i++)
+        line1[i] = '0' + (i % 10);
+    line1[20] = '\0';
+    
+    /* 第二行字母 */
+    for (i = 0; i < 20; i++)
+        line2[i] = 'A' + (i % 26);
+    line2[20] = '\0';
+    
+    write(fd, line1, 20);
+    struct lcd_pos pos = {0, 1};
+    ioctl(fd, LCD_IOCTL_SET_CURSOR, &pos);
+    write(fd, line2, 20);
+    sleep(3);
+}
+
+void test_clear_and_home(void)
+{
+    printf("Test 6: Clear and Home\n");
+    
+    ioctl(fd, LCD_IOCTL_CLEAR);
+    write(fd, "Clear in 2 sec...", 17);
+    sleep(2);
+    
+    ioctl(fd, LCD_IOCTL_CLEAR);
+    write(fd, "Cleared!", 8);
+    sleep(1);
+    
+    ioctl(fd, LCD_IOCTL_HOME);
+    sleep(1);
+}
+
+int main(int argc, char *argv[])
+{
+    printf("LCD2002 Test Program\n");
+    printf("Opening %s...\n", LCD_DEVICE);
+    
+    fd = open(LCD_DEVICE, O_WRONLY);
+    if (fd < 0) {
+        perror("Failed to open LCD device");
+        printf("Please check: sudo modprobe lcd2002_drv\n");
+        printf("Or: sudo mknod /dev/lcd2002 c <major> <minor>\n");
+        return 1;
+    }
+    
+    printf("LCD opened successfully\n\n");
+    
+    /* 运行测试 */
+    test_basic_display();
+    printf("\n");
+    
+    test_cursor_and_blink();
+    printf("\n");
+    
+    test_long_text_wrap();
+    printf("\n");
+    
+    test_special_chars();
+    printf("\n");
+    
+    test_full_display();
+    printf("\n");
+    
+    test_clear_and_home();
+    printf("\n");
+    
+    /* 结束显示 */
+    ioctl(fd, LCD_IOCTL_CLEAR);
+    write(fd, "Test Complete!", 14);
+    struct lcd_pos pos = {4, 1};
+    ioctl(fd, LCD_IOCTL_SET_CURSOR, &pos);
+    write(fd, "Goodbye!", 8);
+    sleep(2);
+    
+    ioctl(fd, LCD_IOCTL_CLEAR);
+    close(fd);
+    
+    printf("All tests completed.\n");
+    return 0;
+}