|
@@ -0,0 +1,754 @@
|
|
|
|
|
+#include <linux/module.h>
|
|
|
|
|
+#include <linux/kernel.h>
|
|
|
|
|
+#include <linux/init.h>
|
|
|
|
|
+#include <linux/fs.h>
|
|
|
|
|
+#include <linux/cdev.h>
|
|
|
|
|
+#include <linux/device.h>
|
|
|
|
|
+#include <linux/uaccess.h>
|
|
|
|
|
+#include <linux/slab.h>
|
|
|
|
|
+#include <linux/pci.h>
|
|
|
|
|
+#include <linux/i2c.h>
|
|
|
|
|
+#include <linux/acpi.h>
|
|
|
|
|
+#include <linux/interrupt.h>
|
|
|
|
|
+#include <linux/wait.h>
|
|
|
|
|
+#include <linux/sched.h>
|
|
|
|
|
+#include <linux/poll.h>
|
|
|
|
|
+#include <linux/mutex.h>
|
|
|
|
|
+#include <linux/delay.h>
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+#include <linux/sched.h>
|
|
|
|
|
+#include <linux/timer.h>
|
|
|
|
|
+#include <linux/workqueue.h>
|
|
|
|
|
+
|
|
|
|
|
+#define PCA9685_ADDR 0x40
|
|
|
|
|
+
|
|
|
|
|
+/* I2C设备地址 */
|
|
|
|
|
+#define PCA9685_DEFAULT_ADDR 0x40
|
|
|
|
|
+#define PCA9685_GENERAL_CALL 0x00
|
|
|
|
|
+
|
|
|
|
|
+/* PCA9685寄存器定义 */
|
|
|
|
|
+#define PCA9685_MODE1 0x00
|
|
|
|
|
+#define PCA9685_MODE2 0x01
|
|
|
|
|
+#define PCA9685_SUBADR1 0x02
|
|
|
|
|
+#define PCA9685_SUBADR2 0x03
|
|
|
|
|
+#define PCA9685_SUBADR3 0x04
|
|
|
|
|
+#define PCA9685_ALLCALLADR 0x05
|
|
|
|
|
+#define PCA9685_LED0_ON_L 0x06
|
|
|
|
|
+#define PCA9685_LED0_ON_H 0x07
|
|
|
|
|
+#define PCA9685_LED0_OFF_L 0x08
|
|
|
|
|
+#define PCA9685_LED0_OFF_H 0x09
|
|
|
|
|
+#define PCA9685_ALL_LED_ON_L 0xFA
|
|
|
|
|
+#define PCA9685_ALL_LED_ON_H 0xFB
|
|
|
|
|
+#define PCA9685_ALL_LED_OFF_L 0xFC
|
|
|
|
|
+#define PCA9685_ALL_LED_OFF_H 0xFD
|
|
|
|
|
+#define PCA9685_PRESCALE 0xFE
|
|
|
|
|
+#define PCA9685_TESTMODE 0xFF
|
|
|
|
|
+
|
|
|
|
|
+/* MODE1寄存器位定义 */
|
|
|
|
|
+#define MODE1_RESTART 0x80
|
|
|
|
|
+#define MODE1_EXTCLK 0x40
|
|
|
|
|
+#define MODE1_AI 0x20
|
|
|
|
|
+#define MODE1_SLEEP 0x10
|
|
|
|
|
+#define MODE1_SUB1 0x08
|
|
|
|
|
+#define MODE1_SUB2 0x04
|
|
|
|
|
+#define MODE1_SUB3 0x02
|
|
|
|
|
+#define MODE1_ALLCALL 0x01
|
|
|
|
|
+
|
|
|
|
|
+/* MODE2寄存器位定义 */
|
|
|
|
|
+#define MODE2_INVRT 0x10
|
|
|
|
|
+#define MODE2_OCH 0x08
|
|
|
|
|
+#define MODE2_OUTDRV 0x04
|
|
|
|
|
+#define MODE2_OUTNE1 0x02
|
|
|
|
|
+#define MODE2_OUTNE0 0x01
|
|
|
|
|
+
|
|
|
|
|
+/* 参数 */
|
|
|
|
|
+#define PCA9685_PWM_RESOLUTION 4096
|
|
|
|
|
+#define PCA9685_OSC_FREQ 25000000
|
|
|
|
|
+#define PCA9685_MIN_FREQ 24
|
|
|
|
|
+#define PCA9685_MAX_FREQ 1526
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+/* 错误码 */
|
|
|
|
|
+#define PCA9685_OK 0
|
|
|
|
|
+#define PCA9685_ERR_OPEN -1
|
|
|
|
|
+#define PCA9685_ERR_ADDR -2
|
|
|
|
|
+#define PCA9685_ERR_WRITE -3
|
|
|
|
|
+#define PCA9685_ERR_READ -4
|
|
|
|
|
+#define PCA9685_ERR_PARAM -5
|
|
|
|
|
+#define PCA9685_ERR_INIT -6
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+#define COLOR_RED 0xff0000 //{255, 0, 0}
|
|
|
|
|
+#define COLOR_GREEN 0x00ff00 //{0, 255, 0}
|
|
|
|
|
+#define COLOR_BLUE 0x0000ff //{0, 0, 255}
|
|
|
|
|
+#define COLOR_WHITE 0xffffff //{255, 255, 255}
|
|
|
|
|
+#define COLOR_FUCHSIA 0xff00ff //{255, 0, 255}
|
|
|
|
|
+#define COLOR_YELLOW 0xffff00 //{255, 255, 0}
|
|
|
|
|
+#define COLOR_CYAN 0x00ffff //{0, 255, 255}
|
|
|
|
|
+
|
|
|
|
|
+#define LIGHT_MODE_OFF 0x01
|
|
|
|
|
+#define LIGHT_MODE_ON 0x02
|
|
|
|
|
+#define LIGHT_MODE_FLASH_1SEC 0x03
|
|
|
|
|
+#define LIGHT_MODE_FLASH_2SEC 0x04
|
|
|
|
|
+#define LIGHT_MODE_FLASH_3SEC 0x05
|
|
|
|
|
+#define LIGHT_MODE_FLASH_SLOW 0x06
|
|
|
|
|
+#define LIGHT_MODE_FLASH_MEDIUM 0x07
|
|
|
|
|
+#define LIGHT_MODE_FLASH_FAST 0x08
|
|
|
|
|
+
|
|
|
|
|
+/* 内部数据结构 */
|
|
|
|
|
+struct lightring_data
|
|
|
|
|
+{
|
|
|
|
|
+ u32 brightness;
|
|
|
|
|
+ u32 color;
|
|
|
|
|
+ u32 max_fade_brightness;
|
|
|
|
|
+ u32 mode;
|
|
|
|
|
+ u32 flash_time;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+/* 驱动私有数据结构 */
|
|
|
|
|
+struct light_ring_i2c_dev
|
|
|
|
|
+{
|
|
|
|
|
+ struct pci_dev *pdev; /* PCI设备指针 */
|
|
|
|
|
+ struct i2c_adapter *adapter; /* I2C适配器指针 */
|
|
|
|
|
+ struct i2c_client client; /* I2C客户端指针 */
|
|
|
|
|
+ struct cdev cdev; /* 字符设备结构 */
|
|
|
|
|
+ struct class *class; /* 设备类 */
|
|
|
|
|
+ struct device *device; /* 设备指针 */
|
|
|
|
|
+ dev_t dev_num; /* 设备号 */
|
|
|
|
|
+ struct mutex lock; /* 互斥锁 */
|
|
|
|
|
+ int bus_number; /* 保存的总线编号 */
|
|
|
|
|
+ struct lightring_data *lightring; /* 内部数据结构 */
|
|
|
|
|
+ struct delayed_work delay_work1;
|
|
|
|
|
+};
|
|
|
|
|
+int set_color(unsigned int color);
|
|
|
|
|
+static struct light_ring_i2c_dev *global_dev = NULL;
|
|
|
|
|
+
|
|
|
|
|
+static void delay_work_func(struct work_struct *work)
|
|
|
|
|
+{
|
|
|
|
|
+ static int flag = 0;
|
|
|
|
|
+ printk("this is delay_work_func !\n");
|
|
|
|
|
+ //自己再启动自己,也可以在其他地方启动
|
|
|
|
|
+ if(global_dev->lightring->flash_time != 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ if(flag == 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ // 显示颜色
|
|
|
|
|
+ set_color(global_dev->lightring->color);
|
|
|
|
|
+ flag = 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ // 关闭灯带
|
|
|
|
|
+ set_color(0);
|
|
|
|
|
+ flag = 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ schedule_delayed_work(&global_dev->delay_work1, msecs_to_jiffies(global_dev->lightring->flash_time));
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// s32 i2c_smbus_write_byte_data(struct i2c_client *client, u8 command, u8 value);
|
|
|
|
|
+// s32 i2c_smbus_read_byte_data(struct i2c_client *client, u8 command);
|
|
|
|
|
+
|
|
|
|
|
+int set_color(unsigned int color)
|
|
|
|
|
+{
|
|
|
|
|
+ int i = 0;
|
|
|
|
|
+ int ret = 0;
|
|
|
|
|
+ unsigned char red;
|
|
|
|
|
+ unsigned char green;
|
|
|
|
|
+ unsigned char blue;
|
|
|
|
|
+
|
|
|
|
|
+ unsigned char red_pwm_l = 0;
|
|
|
|
|
+ unsigned char red_pwm_h = 0;
|
|
|
|
|
+ unsigned char green_pwm_l = 0;
|
|
|
|
|
+ unsigned char green_pwm_h = 0;
|
|
|
|
|
+ unsigned char blue_pwm_l = 0;
|
|
|
|
|
+ unsigned char blue_pwm_h = 0;
|
|
|
|
|
+
|
|
|
|
|
+ red = 255 - ((color >> 16) & 0xff);
|
|
|
|
|
+ green = 255 - ((color >> 8) & 0xff);
|
|
|
|
|
+ blue = 255 - (color & 0xff);
|
|
|
|
|
+
|
|
|
|
|
+ red_pwm_l = (red * 4095 / 255) & 0xff;
|
|
|
|
|
+ red_pwm_h = ((red * 4095 / 255) >> 8) & 0x0f;
|
|
|
|
|
+ green_pwm_l = (green * 4095 / 255) & 0xff;
|
|
|
|
|
+ green_pwm_h = ((green * 4095 / 255) >> 8) & 0x0f;
|
|
|
|
|
+ blue_pwm_l = (blue * 4095 / 255) & 0xff;
|
|
|
|
|
+ blue_pwm_h = ((blue * 4095 / 255) >> 8) & 0x0f;
|
|
|
|
|
+
|
|
|
|
|
+ for (i = 0; i < 3; i++)
|
|
|
|
|
+ {
|
|
|
|
|
+ ret |= i2c_smbus_write_byte_data(&global_dev->client, 0x06 + i * 12, 0);
|
|
|
|
|
+ ret |= i2c_smbus_write_byte_data(&global_dev->client, 0x07 + i * 12, 0);
|
|
|
|
|
+ ret |= i2c_smbus_write_byte_data(&global_dev->client, 0x08 + i * 12, red_pwm_l);
|
|
|
|
|
+ ret |= i2c_smbus_write_byte_data(&global_dev->client, 0x09 + i * 12, red_pwm_h);
|
|
|
|
|
+ ret |= i2c_smbus_write_byte_data(&global_dev->client, 0x0a + i * 12, 0);
|
|
|
|
|
+ ret |= i2c_smbus_write_byte_data(&global_dev->client, 0x0b + i * 12, 0);
|
|
|
|
|
+ ret |= i2c_smbus_write_byte_data(&global_dev->client, 0x0c + i * 12, green_pwm_l);
|
|
|
|
|
+ ret |= i2c_smbus_write_byte_data(&global_dev->client, 0x0d + i * 12, green_pwm_h);
|
|
|
|
|
+ ret |= i2c_smbus_write_byte_data(&global_dev->client, 0x0e + i * 12, 0);
|
|
|
|
|
+ ret |= i2c_smbus_write_byte_data(&global_dev->client, 0x0f + i * 12, 0);
|
|
|
|
|
+ ret |= i2c_smbus_write_byte_data(&global_dev->client, 0x10 + i * 12, blue_pwm_l);
|
|
|
|
|
+ ret |= i2c_smbus_write_byte_data(&global_dev->client, 0x11 + i * 12, blue_pwm_h);
|
|
|
|
|
+ }
|
|
|
|
|
+ if(ret != 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ printk("set color failed\n");
|
|
|
|
|
+ return -1;
|
|
|
|
|
+ }
|
|
|
|
|
+ return 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static ssize_t brightness_show(struct kobject *kobj, struct kobj_attribute *attr,
|
|
|
|
|
+ char *buf)
|
|
|
|
|
+{
|
|
|
|
|
+ return sprintf(buf, "%u\n", global_dev->lightring->brightness);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static ssize_t brightness_store(struct kobject *kobj, struct kobj_attribute *attr,
|
|
|
|
|
+ const char *buf, size_t count)
|
|
|
|
|
+{
|
|
|
|
|
+ u32 val;
|
|
|
|
|
+ int ret;
|
|
|
|
|
+
|
|
|
|
|
+ ret = kstrtou32(buf, 10, &val);
|
|
|
|
|
+ if (ret < 0)
|
|
|
|
|
+ return ret;
|
|
|
|
|
+
|
|
|
|
|
+ /* 硬件操作:设置亮度 */
|
|
|
|
|
+ global_dev->lightring->brightness = val;
|
|
|
|
|
+ pr_info("Lightring: brightness set to %u\n", val);
|
|
|
|
|
+
|
|
|
|
|
+ return count;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static struct kobj_attribute brightness_attr =
|
|
|
|
|
+ __ATTR(brightness, 0644, brightness_show, brightness_store);
|
|
|
|
|
+
|
|
|
|
|
+/* ==================== color ==================== */
|
|
|
|
|
+static ssize_t color_show(struct kobject *kobj, struct kobj_attribute *attr,
|
|
|
|
|
+ char *buf)
|
|
|
|
|
+{
|
|
|
|
|
+ return sprintf(buf, "0x%06x\n", global_dev->lightring->color);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static ssize_t color_store(struct kobject *kobj, struct kobj_attribute *attr,
|
|
|
|
|
+ const char *buf, size_t count)
|
|
|
|
|
+{
|
|
|
|
|
+ u32 val;
|
|
|
|
|
+ int ret;
|
|
|
|
|
+
|
|
|
|
|
+ if (strncmp(buf, "White", 5) == 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ printk("Lightring: color set to White\n");
|
|
|
|
|
+ val = COLOR_WHITE;
|
|
|
|
|
+ }
|
|
|
|
|
+ else if (strncmp(buf, "Red", 3) == 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ printk("Lightring: color set to Red\n");
|
|
|
|
|
+ val = COLOR_RED;
|
|
|
|
|
+ }
|
|
|
|
|
+ else if (strncmp(buf, "Green", 5) == 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ printk("Lightring: color set to Green\n");
|
|
|
|
|
+ val = COLOR_GREEN;
|
|
|
|
|
+ }
|
|
|
|
|
+ else if (strncmp(buf, "Blue", 4) == 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ printk("Lightring: color set to Blue\n");
|
|
|
|
|
+ val = COLOR_BLUE;
|
|
|
|
|
+ }
|
|
|
|
|
+ else if (strncmp(buf, "Fuchsia", 7) == 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ printk("Lightring: color set to Fuchsia\n");
|
|
|
|
|
+ val = COLOR_FUCHSIA;
|
|
|
|
|
+ }
|
|
|
|
|
+ else if (strncmp(buf, "Yellow", 6) == 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ printk("Lightring: color set to Yellow\n");
|
|
|
|
|
+ val = COLOR_YELLOW;
|
|
|
|
|
+ }
|
|
|
|
|
+ else if (strncmp(buf, "Cyan", 4) == 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ printk("Lightring: color set to Cyan\n");
|
|
|
|
|
+ val = COLOR_CYAN;
|
|
|
|
|
+ }
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ printk("Lightring: color format error\n");
|
|
|
|
|
+ return -1;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ global_dev->lightring->color = val;
|
|
|
|
|
+
|
|
|
|
|
+ ret = set_color(val);
|
|
|
|
|
+ if (ret < 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ pr_err("Lightring: set color failed\n");
|
|
|
|
|
+ return ret;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ pr_info("Lightring: color set to 0x%06x\n", val);
|
|
|
|
|
+
|
|
|
|
|
+ return count;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static struct kobj_attribute color_attr =
|
|
|
|
|
+ __ATTR(color, 0644, color_show, color_store);
|
|
|
|
|
+
|
|
|
|
|
+/* ==================== max_fade_brightness ==================== */
|
|
|
|
|
+static ssize_t max_fade_brightness_show(struct kobject *kobj,
|
|
|
|
|
+ struct kobj_attribute *attr,
|
|
|
|
|
+ char *buf)
|
|
|
|
|
+{
|
|
|
|
|
+ return sprintf(buf, "%u\n", global_dev->lightring->max_fade_brightness);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static ssize_t max_fade_brightness_store(struct kobject *kobj,
|
|
|
|
|
+ struct kobj_attribute *attr,
|
|
|
|
|
+ const char *buf, size_t count)
|
|
|
|
|
+{
|
|
|
|
|
+ u32 val;
|
|
|
|
|
+ int ret;
|
|
|
|
|
+
|
|
|
|
|
+ ret = kstrtou32(buf, 10, &val);
|
|
|
|
|
+ if (ret < 0)
|
|
|
|
|
+ return ret;
|
|
|
|
|
+
|
|
|
|
|
+ global_dev->lightring->max_fade_brightness = val;
|
|
|
|
|
+ pr_info("Lightring: max_fade_brightness set to %u\n", val);
|
|
|
|
|
+
|
|
|
|
|
+ return count;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static struct kobj_attribute max_fade_brightness_attr =
|
|
|
|
|
+ __ATTR(max_fade_brightness, 0644, max_fade_brightness_show,
|
|
|
|
|
+ max_fade_brightness_store);
|
|
|
|
|
+
|
|
|
|
|
+/* ==================== mode ==================== */
|
|
|
|
|
+static ssize_t mode_show(struct kobject *kobj, struct kobj_attribute *attr,
|
|
|
|
|
+ char *buf)
|
|
|
|
|
+{
|
|
|
|
|
+ const char *mode_str;
|
|
|
|
|
+
|
|
|
|
|
+ switch (global_dev->lightring->mode)
|
|
|
|
|
+ {
|
|
|
|
|
+ case 0:
|
|
|
|
|
+ mode_str = "static";
|
|
|
|
|
+ break;
|
|
|
|
|
+ case 1:
|
|
|
|
|
+ mode_str = "breathing";
|
|
|
|
|
+ break;
|
|
|
|
|
+ case 2:
|
|
|
|
|
+ mode_str = "rainbow";
|
|
|
|
|
+ break;
|
|
|
|
|
+ case 3:
|
|
|
|
|
+ mode_str = "fade";
|
|
|
|
|
+ break;
|
|
|
|
|
+ default:
|
|
|
|
|
+ mode_str = "unknown";
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return sprintf(buf, "%s (%u)\n", mode_str, global_dev->lightring->mode);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static ssize_t mode_store(struct kobject *kobj, struct kobj_attribute *attr,
|
|
|
|
|
+ const char *buf, size_t count)
|
|
|
|
|
+{
|
|
|
|
|
+ u32 val;
|
|
|
|
|
+ int ret;
|
|
|
|
|
+ int mode = 0;
|
|
|
|
|
+ int time = 0;
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ if (strncmp(buf, "off", 3) == 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ printk("Lightring: mode set to off\n");
|
|
|
|
|
+ mode = LIGHT_MODE_OFF;
|
|
|
|
|
+ time = 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ else if (strncmp(buf, "on", 2) == 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ printk("Lightring: mode set to on\n");
|
|
|
|
|
+ mode = LIGHT_MODE_ON;
|
|
|
|
|
+ time = 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ else if (strncmp(buf, "flash_1sec", 10) == 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ printk("Lightring: mode set to flash_1sec\n");
|
|
|
|
|
+ mode = LIGHT_MODE_FLASH_1SEC;
|
|
|
|
|
+ time = 1000;
|
|
|
|
|
+ }
|
|
|
|
|
+ else if (strncmp(buf, "flash_2sec", 10) == 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ printk("Lightring: mode set to flash_2sec\n");
|
|
|
|
|
+ mode = LIGHT_MODE_FLASH_2SEC;
|
|
|
|
|
+ time = 2000;
|
|
|
|
|
+ }
|
|
|
|
|
+ else if (strncmp(buf, "flash_3sec", 10) == 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ printk("Lightring: mode set to flash_3sec\n");
|
|
|
|
|
+ mode = LIGHT_MODE_FLASH_3SEC;
|
|
|
|
|
+ time = 3000;
|
|
|
|
|
+ }
|
|
|
|
|
+ else if (strncmp(buf, "flash_slow", 10) == 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ printk("Lightring: mode set to flash_slow\n");
|
|
|
|
|
+ mode = LIGHT_MODE_FLASH_SLOW;
|
|
|
|
|
+ time = 4000;
|
|
|
|
|
+ }
|
|
|
|
|
+ else if (strncmp(buf, "flash_medium", 12) == 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ printk("Lightring: mode set to flash_medium\n");
|
|
|
|
|
+ mode = LIGHT_MODE_FLASH_MEDIUM;
|
|
|
|
|
+ time = 2000;
|
|
|
|
|
+ }
|
|
|
|
|
+ else if (strncmp(buf, "flash_fast", 10) == 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ printk("Lightring: mode set to flash_fast\n");
|
|
|
|
|
+ mode = LIGHT_MODE_FLASH_FAST;
|
|
|
|
|
+ time = 200;
|
|
|
|
|
+ }
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ printk("Lightring: mode format error\n");
|
|
|
|
|
+ return -1;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ global_dev->lightring->mode = mode;
|
|
|
|
|
+ global_dev->lightring->flash_time = time;
|
|
|
|
|
+ if(time != 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ schedule_delayed_work(&global_dev->delay_work1, msecs_to_jiffies(global_dev->lightring->flash_time));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ pr_info("global_dev->lightring: mode set to %u :flash_time=%d\n", global_dev->lightring->mode, global_dev->lightring->flash_time);
|
|
|
|
|
+
|
|
|
|
|
+ return count;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static struct kobj_attribute mode_attr =
|
|
|
|
|
+ __ATTR(mode, 0644, mode_show, mode_store);
|
|
|
|
|
+
|
|
|
|
|
+/* ==================== 属性组 ==================== */
|
|
|
|
|
+static struct attribute *lightring_attrs[] = {
|
|
|
|
|
+ &brightness_attr.attr,
|
|
|
|
|
+ &color_attr.attr,
|
|
|
|
|
+ &max_fade_brightness_attr.attr,
|
|
|
|
|
+ &mode_attr.attr,
|
|
|
|
|
+ NULL,
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+static struct attribute_group lightring_attr_group = {
|
|
|
|
|
+ .attrs = lightring_attrs,
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// static struct kobject *vfiec_kobj;
|
|
|
|
|
+extern struct kobject *vfiec_kobj;
|
|
|
|
|
+static struct kobject *lightring_kobj;
|
|
|
|
|
+
|
|
|
|
|
+static struct i2c_adapter *find_i2c_adapter_by_pci(struct pci_dev *pdev)
|
|
|
|
|
+{
|
|
|
|
|
+ struct i2c_adapter *adapter = NULL;
|
|
|
|
|
+ struct device *dev;
|
|
|
|
|
+ struct fwnode_handle *fwnode;
|
|
|
|
|
+
|
|
|
|
|
+ if (!pdev)
|
|
|
|
|
+ return NULL;
|
|
|
|
|
+
|
|
|
|
|
+ /* 获取PCI设备的fwnode */
|
|
|
|
|
+ fwnode = dev_fwnode(&pdev->dev);
|
|
|
|
|
+ if (!fwnode)
|
|
|
|
|
+ {
|
|
|
|
|
+ pr_err("PCI device has no fwnode\n");
|
|
|
|
|
+ return NULL;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /* 对于内核5.15,需要使用其他方法查找i2c适配器 */
|
|
|
|
|
+
|
|
|
|
|
+ /* 方法A: 通过设备链接查找 */
|
|
|
|
|
+ struct device_link *link;
|
|
|
|
|
+
|
|
|
|
|
+ /* 遍历PCI设备的消费者链接 */
|
|
|
|
|
+ list_for_each_entry(link, &pdev->dev.links.consumers, s_node)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (link->supplier == &pdev->dev)
|
|
|
|
|
+ {
|
|
|
|
|
+ /* 检查消费者是否是i2c适配器 */
|
|
|
|
|
+ if (link->consumer->type &&
|
|
|
|
|
+ link->consumer->type->name &&
|
|
|
|
|
+ strcmp(link->consumer->type->name, "i2c_adapter") == 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ adapter = i2c_verify_adapter(link->consumer);
|
|
|
|
|
+ if (adapter)
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (adapter)
|
|
|
|
|
+ {
|
|
|
|
|
+ i2c_put_adapter(adapter); /* 先释放引用 */
|
|
|
|
|
+ adapter = i2c_get_adapter(adapter->nr); /* 重新获取 */
|
|
|
|
|
+ printk("i2c_get_adapter\n");
|
|
|
|
|
+ return adapter;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /* 方法B: 遍历所有i2c适配器 */
|
|
|
|
|
+ int i;
|
|
|
|
|
+ for (i = 0; i < 255; i++)
|
|
|
|
|
+ {
|
|
|
|
|
+ adapter = i2c_get_adapter(i);
|
|
|
|
|
+ if (adapter)
|
|
|
|
|
+ {
|
|
|
|
|
+ /* 检查适配器的父设备是否是我们的PCI设备 */
|
|
|
|
|
+ if (adapter->dev.parent == &pdev->dev)
|
|
|
|
|
+ {
|
|
|
|
|
+ return adapter;
|
|
|
|
|
+ }
|
|
|
|
|
+ i2c_put_adapter(adapter);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return NULL;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 初始化PCI设备 */
|
|
|
|
|
+static struct pci_dev *init_pci_device(void)
|
|
|
|
|
+{
|
|
|
|
|
+ struct pci_dev *pdev;
|
|
|
|
|
+
|
|
|
|
|
+ /* 查找PCI设备: domain=0, bus=0, devfn=PCI_DEVFN(0x1f, 4) */
|
|
|
|
|
+ pdev = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(0x1f, 4));
|
|
|
|
|
+ if (!pdev)
|
|
|
|
|
+ {
|
|
|
|
|
+ pr_err("Failed to find PCI device 0000:00:1f.4\n");
|
|
|
|
|
+ return NULL;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ pr_info("Found PCI device: %04x:%04x\n", pdev->vendor, pdev->device);
|
|
|
|
|
+ return pdev;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+int pca9685_all_off(void)
|
|
|
|
|
+{
|
|
|
|
|
+ if (i2c_smbus_write_byte_data(&global_dev->client, PCA9685_ALL_LED_ON_L, 0) < 0)
|
|
|
|
|
+ return PCA9685_ERR_WRITE;
|
|
|
|
|
+ if (i2c_smbus_write_byte_data(&global_dev->client, PCA9685_ALL_LED_ON_H, 0) < 0)
|
|
|
|
|
+ return PCA9685_ERR_WRITE;
|
|
|
|
|
+ if (i2c_smbus_write_byte_data(&global_dev->client, PCA9685_ALL_LED_OFF_L, 0) < 0)
|
|
|
|
|
+ return PCA9685_ERR_WRITE;
|
|
|
|
|
+ if (i2c_smbus_write_byte_data(&global_dev->client, PCA9685_ALL_LED_OFF_H, 0x10) < 0)
|
|
|
|
|
+ return PCA9685_ERR_WRITE;
|
|
|
|
|
+ return PCA9685_OK;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+int pca9685_set_open_drain(bool enable)
|
|
|
|
|
+{
|
|
|
|
|
+ uint8_t mode2;
|
|
|
|
|
+ int ret;
|
|
|
|
|
+ ret = i2c_smbus_read_byte_data(&global_dev->client, PCA9685_MODE2);
|
|
|
|
|
+ if (ret < 0)
|
|
|
|
|
+ return PCA9685_ERR_READ;
|
|
|
|
|
+ mode2 = ret & 0xff;
|
|
|
|
|
+ if (enable)
|
|
|
|
|
+ mode2 &= ~MODE2_OUTDRV;
|
|
|
|
|
+ else
|
|
|
|
|
+ mode2 |= MODE2_OUTDRV;
|
|
|
|
|
+ return i2c_smbus_write_byte_data(&global_dev->client, PCA9685_MODE2, mode2);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+int pca9685_set_pwm_freq(int freq)
|
|
|
|
|
+{
|
|
|
|
|
+ int ret = 0;
|
|
|
|
|
+ uint8_t prescale, old_mode, new_mode;
|
|
|
|
|
+ int prescale_val = (PCA9685_OSC_FREQ / (4096 * freq)) - 1;
|
|
|
|
|
+ prescale = (uint8_t)(prescale_val);
|
|
|
|
|
+ if (prescale < 3)
|
|
|
|
|
+ prescale = 3;
|
|
|
|
|
+
|
|
|
|
|
+ ret = i2c_smbus_read_byte_data(&global_dev->client, PCA9685_MODE1);
|
|
|
|
|
+ if (ret < 0)
|
|
|
|
|
+ return PCA9685_ERR_READ;
|
|
|
|
|
+ old_mode = ret & 0xff;
|
|
|
|
|
+ new_mode = (old_mode & ~MODE1_RESTART) | MODE1_SLEEP;
|
|
|
|
|
+
|
|
|
|
|
+ if (i2c_smbus_write_byte_data(&global_dev->client, PCA9685_MODE1, new_mode) < 0)
|
|
|
|
|
+ return PCA9685_ERR_WRITE;
|
|
|
|
|
+ if (i2c_smbus_write_byte_data(&global_dev->client, PCA9685_PRESCALE, prescale) < 0)
|
|
|
|
|
+ return PCA9685_ERR_WRITE;
|
|
|
|
|
+ if (i2c_smbus_write_byte_data(&global_dev->client, PCA9685_MODE1, old_mode) < 0)
|
|
|
|
|
+ return PCA9685_ERR_WRITE;
|
|
|
|
|
+
|
|
|
|
|
+ udelay(5000);
|
|
|
|
|
+ if (i2c_smbus_write_byte_data(&global_dev->client, PCA9685_MODE1, old_mode | MODE1_RESTART | MODE1_AI) < 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ return PCA9685_ERR_WRITE;
|
|
|
|
|
+ }
|
|
|
|
|
+ return 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void init_pca9685(void)
|
|
|
|
|
+{
|
|
|
|
|
+ int ret = 0;
|
|
|
|
|
+ // reset
|
|
|
|
|
+ ret |= i2c_smbus_write_byte_data(&global_dev->client, 0x00, 0x06);
|
|
|
|
|
+ udelay(10000);
|
|
|
|
|
+
|
|
|
|
|
+ ret |= i2c_smbus_write_byte_data(&global_dev->client, PCA9685_MODE2, MODE2_OUTDRV | MODE2_OCH);
|
|
|
|
|
+ ret |= i2c_smbus_write_byte_data(&global_dev->client, PCA9685_MODE1, MODE1_RESTART | MODE1_AI | MODE1_ALLCALL);
|
|
|
|
|
+ printk("init_pca9685: %d\n", ret);
|
|
|
|
|
+
|
|
|
|
|
+ ret = pca9685_set_pwm_freq(50);
|
|
|
|
|
+ if (ret != 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ printk("pca9685_set_pwm_freq failed: %d\n", ret);
|
|
|
|
|
+ }
|
|
|
|
|
+ ret = pca9685_all_off();
|
|
|
|
|
+ if (ret != 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ printk("pca9685_all_off failed: %d\n", ret);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ ret = pca9685_set_open_drain(true);
|
|
|
|
|
+ if (ret != 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ printk("pca9685_set_open_drain failed: %d\n", ret);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+int light_ring_init(void)
|
|
|
|
|
+{
|
|
|
|
|
+ int ret;
|
|
|
|
|
+ struct i2c_board_info info;
|
|
|
|
|
+ struct light_ring_i2c_dev *dev;
|
|
|
|
|
+
|
|
|
|
|
+ pr_info("light_ring_i2c_dev initializing...\n");
|
|
|
|
|
+
|
|
|
|
|
+ /* 分配设备结构 */
|
|
|
|
|
+ dev = kzalloc(sizeof(struct light_ring_i2c_dev), GFP_KERNEL);
|
|
|
|
|
+ if (!dev)
|
|
|
|
|
+ {
|
|
|
|
|
+ pr_err("Failed to allocate device structure\n");
|
|
|
|
|
+ return -ENOMEM;
|
|
|
|
|
+ }
|
|
|
|
|
+ global_dev = dev;
|
|
|
|
|
+ mutex_init(&dev->lock);
|
|
|
|
|
+
|
|
|
|
|
+ /* 1. 初始化PCI设备 */
|
|
|
|
|
+ dev->pdev = init_pci_device();
|
|
|
|
|
+ if (!dev->pdev)
|
|
|
|
|
+ {
|
|
|
|
|
+ pr_err("Failed to initialize PCI device\n");
|
|
|
|
|
+ ret = -ENODEV;
|
|
|
|
|
+ goto err_free_dev;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /* 2. 通过PCI设备查找I2C适配器 */
|
|
|
|
|
+ dev->adapter = find_i2c_adapter_by_pci(dev->pdev);
|
|
|
|
|
+ if (!dev->adapter)
|
|
|
|
|
+ {
|
|
|
|
|
+ pr_err("Failed to find I2C adapter\n");
|
|
|
|
|
+ ret = -ENODEV;
|
|
|
|
|
+ goto err_put_pci;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /* 保存总线编号用于调试 */
|
|
|
|
|
+ dev->bus_number = dev->adapter->nr;
|
|
|
|
|
+ pr_info("Found I2C adapter on bus %d\n", dev->bus_number);
|
|
|
|
|
+
|
|
|
|
|
+ // 检查适配器是否支持字节数据读写
|
|
|
|
|
+ if (!i2c_check_functionality(dev->adapter, I2C_FUNC_SMBUS_READ_BYTE_DATA |
|
|
|
|
|
+ I2C_FUNC_SMBUS_WRITE_BYTE_DATA))
|
|
|
|
|
+ {
|
|
|
|
|
+ dev_err(&dev->adapter->dev, "Adapter does not support required SMBus features\n");
|
|
|
|
|
+ return -ENOTSUPP; // 返回不支持错误码
|
|
|
|
|
+ }
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ pr_info("Adapter supports required SMBus features\n");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /* 初始化I2C客户端 */
|
|
|
|
|
+ dev->client.adapter = dev->adapter; // 你已有的 adapter
|
|
|
|
|
+ dev->client.addr = PCA9685_DEFAULT_ADDR; // 从设备地址
|
|
|
|
|
+ dev->client.flags = 0; // 7位地址模式
|
|
|
|
|
+
|
|
|
|
|
+ INIT_DELAYED_WORK(&dev->delay_work1, delay_work_func);
|
|
|
|
|
+
|
|
|
|
|
+ init_pca9685();
|
|
|
|
|
+
|
|
|
|
|
+ global_dev->lightring = kzalloc(sizeof(*global_dev->lightring), GFP_KERNEL);
|
|
|
|
|
+ if (!global_dev->lightring)
|
|
|
|
|
+ {
|
|
|
|
|
+ pr_err("Failed to allocate lightring structure\n");
|
|
|
|
|
+ ret = -ENOMEM;
|
|
|
|
|
+ goto err_put_pci;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /* 默认值 */
|
|
|
|
|
+ global_dev->lightring->brightness = 128;
|
|
|
|
|
+ global_dev->lightring->color = 0xFFFFFF; /* 白色 */
|
|
|
|
|
+ global_dev->lightring->max_fade_brightness = 255;
|
|
|
|
|
+ global_dev->lightring->mode = 0; /* static */
|
|
|
|
|
+
|
|
|
|
|
+ /* 创建 /sys/kernel/vfiec */
|
|
|
|
|
+ // vfiec_kobj = kobject_create_and_add("vfiec", kernel_kobj);
|
|
|
|
|
+ // if (!vfiec_kobj)
|
|
|
|
|
+ // {
|
|
|
|
|
+ // ret = -ENOMEM;
|
|
|
|
|
+ // goto err_free;
|
|
|
|
|
+ // }
|
|
|
|
|
+
|
|
|
|
|
+ /* 创建 /sys/kernel/vfiec/lightring */
|
|
|
|
|
+ lightring_kobj = kobject_create_and_add("lightring", vfiec_kobj);
|
|
|
|
|
+ if (!lightring_kobj)
|
|
|
|
|
+ {
|
|
|
|
|
+ ret = -ENOMEM;
|
|
|
|
|
+ goto err_free;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /* 创建属性文件 */
|
|
|
|
|
+ ret = sysfs_create_group(lightring_kobj, &lightring_attr_group);
|
|
|
|
|
+ if (ret)
|
|
|
|
|
+ {
|
|
|
|
|
+ pr_err("Failed to create sysfs group: %d\n", ret);
|
|
|
|
|
+ goto err_lightring;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return 0;
|
|
|
|
|
+err_lightring:
|
|
|
|
|
+ kobject_put(lightring_kobj);
|
|
|
|
|
+// err_vfiec:
|
|
|
|
|
+// kobject_put(vfiec_kobj);
|
|
|
|
|
+err_free:
|
|
|
|
|
+ kfree(global_dev->lightring);
|
|
|
|
|
+err_put_pci:
|
|
|
|
|
+ pci_dev_put(dev->pdev);
|
|
|
|
|
+err_free_dev:
|
|
|
|
|
+ kfree(dev);
|
|
|
|
|
+ global_dev = NULL;
|
|
|
|
|
+ return ret;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void light_ring_exit(void)
|
|
|
|
|
+{
|
|
|
|
|
+ struct light_ring_i2c_dev *dev = global_dev;
|
|
|
|
|
+ global_dev->lightring->flash_time = 0;
|
|
|
|
|
+ cancel_delayed_work_sync(&global_dev->delay_work1);
|
|
|
|
|
+ sysfs_remove_group(lightring_kobj, &lightring_attr_group);
|
|
|
|
|
+ kobject_put(lightring_kobj);
|
|
|
|
|
+ // kobject_put(vfiec_kobj);
|
|
|
|
|
+ kfree(global_dev->lightring);
|
|
|
|
|
+ printk(KERN_INFO "LED module unloaded\n");
|
|
|
|
|
+ if (dev->adapter)
|
|
|
|
|
+ i2c_put_adapter(dev->adapter);
|
|
|
|
|
+
|
|
|
|
|
+ if (dev->pdev)
|
|
|
|
|
+ pci_dev_put(dev->pdev);
|
|
|
|
|
+
|
|
|
|
|
+ kfree(dev);
|
|
|
|
|
+ global_dev = NULL;
|
|
|
|
|
+}
|
|
|
|
|
+
|