From 1fa683922f05c6311a9431d928d990ebfa7eaf51 Mon Sep 17 00:00:00 2001 From: Seo Chaegeon Date: Mon, 29 Dec 2025 14:58:31 +0900 Subject: [PATCH] Add files via upload --- Makefile | 9 + application.c | 404 ++++++++++++++++++++++++++++++++++ driver.c | 595 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1008 insertions(+) create mode 100644 Makefile create mode 100644 application.c create mode 100644 driver.c diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ae8af76 --- /dev/null +++ b/Makefile @@ -0,0 +1,9 @@ +obj-m += driver.o + +KDIR := /home/ubuntu/linux + +all: + make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -C $(KDIR) M=$(PWD) modules + +clean: + make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -C $(KDIR) M=$(PWD) clean diff --git a/application.c b/application.c new file mode 100644 index 0000000..23ab49d --- /dev/null +++ b/application.c @@ -0,0 +1,404 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define CLOCK_DEV "/dev/clock_drv" + + +#define I2C_DEV "/dev/i2c-1" +#define OLED_I2C_ADDR 0x3C + +#define OLED_W 128 +#define OLED_H 64 +static uint8_t fb[OLED_W * OLED_H / 8]; + +static int i2c_fd = -1; + +static long long now_ms(void) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (long long)ts.tv_sec * 1000LL + ts.tv_nsec / 1000000LL; +} + +static int iround_div(long long sum, int cnt) { + if (cnt <= 0) return -1; + if (sum >= 0) return (int)((sum + cnt/2) / cnt); + return (int)((sum - cnt/2) / cnt); +} + + +static int i2c_open_oled(void) { + i2c_fd = open(I2C_DEV, O_RDWR); + if (i2c_fd < 0) { perror("open i2c"); return -1; } + if (ioctl(i2c_fd, I2C_SLAVE, OLED_I2C_ADDR) < 0) { + perror("ioctl I2C_SLAVE"); + close(i2c_fd); + i2c_fd = -1; + return -1; + } + return 0; +} + +static void oled_cmd(uint8_t c) { + uint8_t buf[2] = {0x00, c}; + (void)write(i2c_fd, buf, 2); +} + +static void oled_data_chunk(const uint8_t *d, size_t n) { + uint8_t buf[1 + 16]; + while (n > 0) { + size_t m = (n > 16) ? 16 : n; + buf[0] = 0x40; + memcpy(&buf[1], d, m); + (void)write(i2c_fd, buf, 1 + m); + d += m; + n -= m; + } +} + +static void oled_init(void) { + oled_cmd(0xAE); + oled_cmd(0xD5); oled_cmd(0x80); + oled_cmd(0xA8); oled_cmd(0x3F); + oled_cmd(0xD3); oled_cmd(0x00); + oled_cmd(0x40); + oled_cmd(0x8D); oled_cmd(0x14); + oled_cmd(0x20); oled_cmd(0x00); + oled_cmd(0xA1); + oled_cmd(0xC8); + oled_cmd(0xDA); oled_cmd(0x12); + oled_cmd(0x81); oled_cmd(0xCF); + oled_cmd(0xD9); oled_cmd(0xF1); + oled_cmd(0xDB); oled_cmd(0x40); + oled_cmd(0xA4); + oled_cmd(0xA6); + oled_cmd(0xAF); +} + +static void oled_flush(void) { + oled_cmd(0x21); oled_cmd(0); oled_cmd(127); + oled_cmd(0x22); oled_cmd(0); oled_cmd(7); + oled_data_chunk(fb, sizeof(fb)); +} + + +static void fb_clear(void) { memset(fb, 0, sizeof(fb)); } + +static void fb_set_px(int x, int y, int on) { + if (x<0||x>=OLED_W||y<0||y>=OLED_H) return; + int idx = (y/8)*OLED_W + x; + uint8_t mask = (1u << (y%8)); + if (on) fb[idx] |= mask; + else fb[idx] &= ~mask; +} + +static const uint8_t font5x7_digits[][5] = { + {0x3E,0x51,0x49,0x45,0x3E},{0x00,0x42,0x7F,0x40,0x00}, + {0x42,0x61,0x51,0x49,0x46},{0x21,0x41,0x45,0x4B,0x31}, + {0x18,0x14,0x12,0x7F,0x10},{0x27,0x45,0x45,0x45,0x39}, + {0x3C,0x4A,0x49,0x49,0x30},{0x01,0x71,0x09,0x05,0x03}, + {0x36,0x49,0x49,0x49,0x36},{0x06,0x49,0x49,0x29,0x1E} +}; +static const uint8_t font_colon[5] = {0x00,0x36,0x36,0x00,0x00}; + +static const uint8_t font_E[5]={0x7F,0x49,0x49,0x49,0x41}; +static const uint8_t font_D[5]={0x7F,0x41,0x41,0x22,0x1C}; +static const uint8_t font_I[5]={0x00,0x41,0x7F,0x41,0x00}; +static const uint8_t font_T[5]={0x01,0x01,0x7F,0x01,0x01}; +static const uint8_t font_R[5]={0x7F,0x09,0x19,0x29,0x46}; +static const uint8_t font_U[5]={0x3F,0x40,0x40,0x40,0x3F}; +static const uint8_t font_N[5]={0x7F,0x06,0x18,0x60,0x7F}; +static const uint8_t font_W[5]={0x7F,0x20,0x18,0x20,0x7F}; +static const uint8_t font_H[5]={0x7F,0x08,0x08,0x08,0x7F}; +static const uint8_t font_M[5]={0x7F,0x02,0x04,0x02,0x7F}; +static const uint8_t font_S[5]={0x46,0x49,0x49,0x49,0x31}; +static const uint8_t font_G[5]={0x3E,0x41,0x41,0x51,0x32}; +static const uint8_t font_P[5]={0x7F,0x09,0x09,0x09,0x06}; +static const uint8_t font_A[5]={0x7E,0x09,0x09,0x09,0x7E}; +static const uint8_t font_B[5]={0x7F,0x49,0x49,0x49,0x36}; + + + + +static const uint8_t font_a[5]={0x20,0x54,0x54,0x54,0x78}; +static const uint8_t font_c[5]={0x38,0x44,0x44,0x44,0x20}; +static const uint8_t font_d[5]={0x38,0x44,0x44,0x48,0x7F}; +static const uint8_t font_e[5]={0x38,0x54,0x54,0x54,0x18}; +static const uint8_t font_g[5]={0x18,0xA4,0xA4,0xA4,0x7C}; +static const uint8_t font_h[5]={0x7F,0x08,0x04,0x04,0x78}; +static const uint8_t font_i[5]={0x00,0x44,0x7D,0x40,0x00}; +static const uint8_t font_l[5]={0x00,0x41,0x7F,0x40,0x00}; +static const uint8_t font_m[5]={0x7C,0x04,0x18,0x04,0x78}; +static const uint8_t font_n[5]={0x7C,0x08,0x04,0x04,0x78}; +static const uint8_t font_o[5]={0x38,0x44,0x44,0x44,0x38}; +static const uint8_t font_p[5]={0x7C,0x14,0x14,0x14,0x08}; +static const uint8_t font_r[5]={0x7C,0x08,0x04,0x04,0x08}; +static const uint8_t font_s[5]={0x48,0x54,0x54,0x54,0x20}; +static const uint8_t font_t[5]={0x04,0x3F,0x44,0x40,0x20}; +static const uint8_t font_u[5]={0x3C,0x40,0x40,0x20,0x7C}; +static const uint8_t font_w[5]={0x3C,0x40,0x30,0x40,0x3C}; +static const uint8_t font_y[5]={0x0C,0x50,0x50,0x50,0x3C}; +static const uint8_t font_space[5] = {0,0,0,0,0}; + +static const uint8_t font_percent[5] = {0x23, 0x13, 0x08, 0x64, 0x62}; +static const uint8_t font_deg[5] = {0x06, 0x09, 0x09, 0x06, 0x00}; +static const uint8_t font_dot[5] = {0x00,0x60,0x60,0x00,0x00}; + + +static const uint8_t icon_good[8] = { 0x3C,0x42,0xA5,0x81,0xA5,0x99,0x42,0x3C }; +static const uint8_t icon_Mild[8] = { 0x3C,0x42,0xA5,0x81,0xBD,0x81,0x42,0x3C }; +static const uint8_t icon_bad[8] = { 0x3C,0x42,0xA5,0x81,0x99,0xA5,0x42,0x3C }; +static const uint8_t icon_hot[8] = { 0x3C,0x42,0xA5,0x81,0x9D,0xA1,0x42,0x3C }; + +static const uint8_t* glyph_for_char(char c) { + if (c>='0' && c<='9') return font5x7_digits[c-'0']; + if (c==':') return font_colon; + + if (c=='E') return font_E; if (c=='D') return font_D; if (c=='I') return font_I; + if (c=='T') return font_T; if (c=='R') return font_R; if (c=='U') return font_U; + if (c=='N') return font_N; if (c=='W') return font_W; if (c=='H') return font_H; + if (c=='M') return font_M; if (c=='S') return font_S; if (c=='G') return font_G; + if (c=='P') return font_P; if (c=='A') return font_A; if (c=='B') return font_B; + + + if (c=='a') return font_a; if (c=='c') return font_c; if (c=='d') return font_d; + if (c=='e') return font_e; if (c=='g') return font_g; if (c=='h') return font_h; + if (c=='i') return font_i; if (c=='l') return font_l; if (c=='m') return font_m; + if (c=='n') return font_n; if (c=='o') return font_o; if (c=='p') return font_p; + if (c=='r') return font_r; if (c=='s') return font_s; if (c=='t') return font_t; + if (c=='u') return font_u; if (c=='w') return font_w; if (c=='y') return font_y; + if (c=='%') return font_percent; if (c==0xB0) return font_deg; if (c=='.') return font_dot; + if (c==' ') return font_space; + return NULL; +} + +static void fb_draw_char5x7(int x, int y, char c, int scale) { + const uint8_t *g = glyph_for_char(c); + if (!g) return; + for (int i=0;i<5;i++) { + for (int j=0;j<7;j++) { + if ((g[i] >> j) & 1) { + for (int sx=0;sx 80) return 8; + + return 1 + (di - 65) * 7 / 15; +} + +static void set_led_level(int level) +{ + char buf[32]; + snprintf(buf, sizeof(buf), "LED %d", level); + + int fd = open(CLOCK_DEV, O_WRONLY); + if (fd >= 0) { + write(fd, buf, strlen(buf)); + close(fd); + } +} + +static void fb_draw_icon8(int x, int y, const uint8_t icon[8], int scale) { + for (int r = 0; r < 8; r++) { + for (int c = 0; c < 8; c++) { + if (icon[r] & (1 << c)) { + for (int sx = 0; sx < scale; sx++) { + for (int sy = 0; sy < scale; sy++) { + fb_set_px(x + c*scale + sx, y + r*scale + sy, 1); + } + } + } + } + } +} + +static void fb_draw_circle(int cx,int cy,int r,int filled){ + for(int y=-r;y<=r;y++){ + for(int x=-r;x<=r;x++){ + int d=x*x+y*y; + int rr=r*r; + if(filled){ if(d<=rr) fb_set_px(cx+x,cy+y,1); } + else{ if(d>=rr-r && d<=rr+r) fb_set_px(cx+x,cy+y,1); } + } + } +} +static void draw_page_dots(int page){ + int cy=60,r=3; + int cx1=OLED_W/2-15; + int cx2=OLED_W/2; + int cx3=OLED_W/2+15; + fb_draw_circle(cx1,cy,r,0); + fb_draw_circle(cx2,cy,r,0); + fb_draw_circle(cx3,cy,r,0); + if(page==0) fb_draw_circle(cx1,cy,r-1,1); + else if(page==1) fb_draw_circle(cx2,cy,r-1,1); + else fb_draw_circle(cx3,cy,r-1,1); +} + +static int read_clock_line(char *out, size_t outsz) { + int fd = open(CLOCK_DEV, O_RDONLY); + if (fd < 0) return -1; + int n = read(fd, out, outsz-1); + close(fd); + if (n <= 0) return -1; + out[n] = 0; + return 0; +} + +int main(void) { + if (i2c_open_oled() != 0) return 1; + oled_init(); + + int blink = 0; + int prev_page = 0; + + long long last_dht_ms = 0; + int cur_temp = -1; + int cur_hum = -1; + + while (1) { + char line[200]; + char mode[8]="RUN"; + char field[8]="SEC"; + int hh=0,mm=0,ss=0,page=0; + int temp=-1,hum=-1; + + if (read_clock_line(line,sizeof(line))==0) { + sscanf(line,"%d:%d:%d MODE=%7s FIELD=%7s PAGE=%d TEMP=%d HUM=%d", + &hh,&mm,&ss,mode,field,&page,&temp,&hum); + } + + long long now = now_ms(); + + + if ((page == 1 || page == 2) && (now - last_dht_ms >= 2000)) { + if (temp >= 0 && hum >= 0) { + cur_temp = temp; + cur_hum = hum; + } + last_dht_ms = now; + } + + fb_clear(); + draw_page_dots(page); + + + if (page==0) { + if (strcmp(mode,"EDIT")==0) + fb_draw_text(0,0,"EDIT",1,1); + else + fb_draw_text(0,0,"RUN",1,1); + + if (strcmp(mode,"EDIT")==0) { + char hs[3], ms[3], ss_s[3]; + sprintf(hs,"%02d",hh); + sprintf(ms,"%02d",mm); + sprintf(ss_s,"%02d",ss); + + if (!(strcmp(field,"HOUR")==0 && blink)) fb_draw_text(10,18,hs,2,2); + fb_draw_text(34,18,":",2,2); + if (!(strcmp(field,"MIN")==0 && blink)) fb_draw_text(46,18,ms,2,2); + fb_draw_text(70,18,":",2,2); + if (!(strcmp(field,"SEC")==0 && blink)) fb_draw_text(82,18,ss_s,2,2); + } else { + char ts[16]; + sprintf(ts,"%02d:%02d:%02d",hh,mm,ss); + fb_draw_text(10,18,ts,2,2); + } + } + + + else if (page==1) { + const int num_x=92; + + fb_draw_text(0,0,"Today Weather",1,1); + fb_draw_text(0,20,"Temp:",1,1); + fb_draw_text(0,38,"Humidity:",1,1); + + char tstr[8], hstr[8]; + + if (cur_temp >= 0) sprintf(tstr,"%02d",cur_temp); + else strcpy(tstr,"--"); + + if (cur_hum >= 0) sprintf(hstr,"%02d",cur_hum); + else strcpy(hstr,"--"); + + fb_draw_text(num_x,16,tstr,2,2); + fb_draw_text(num_x+22,16,"\xB0",1,1); + fb_draw_text(num_x+30,16,"C",1,1); + + fb_draw_text(num_x,34,hstr,2,2); + fb_draw_text(num_x+24,34,"%",1,1); + } + + + else { + int di=-1; + if (cur_temp >= 0 && cur_hum >= 0) + di = (int)(0.81*cur_temp + 0.01*cur_hum*(0.99*cur_temp-14.3) + 46.3); + + fb_draw_text(0,0,"DI PAGE",1,1); + + + char di_str[8]; + if (di>=0) sprintf(di_str,"%02d",di); + else strcpy(di_str,"--"); + + fb_draw_text(0,20,"DI:",2,2); + fb_draw_text(40,20,di_str,2,2); + + if (di >= 0) { + int led = di_to_led(di); + set_led_level(led); + } + + if (di < 0) { + fb_draw_text(40,14,"--",2,2); + fb_draw_text(0,36,"No Data",2,2); + } + else if (di < 68) { + fb_draw_icon8(90,20,icon_good,2); + fb_draw_text(0,36,"Good",2,2); + } + else if (di < 75) { + fb_draw_icon8(90,20,icon_Mild,2); + fb_draw_text(0,36,"Mild",2,2); + } + else if (di < 80) { + fb_draw_icon8(90,20,icon_bad,2); + fb_draw_text(0,36,"Bad",2,2); + } + else { + fb_draw_icon8(90,20,icon_hot,2); + fb_draw_text(0,36,"Hot",2,2); + } + + } + + oled_flush(); + blink = !blink; + prev_page = page; + usleep(200000); + } + return 0; +} diff --git a/driver.c b/driver.c new file mode 100644 index 0000000..947f720 --- /dev/null +++ b/driver.c @@ -0,0 +1,595 @@ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "clock_drv" +#define CLASS_NAME "clock_drv_class" + + +#define DS_RST 17 +#define DS_DAT 4 +#define DS_SCLK 22 + +#define ENC_S1 24 +#define ENC_S2 25 +#define ENC_SW 26 + +#define DHT_GPIO 23 + + +#define LED0 5 +#define LED1 6 +#define LED2 12 +#define LED3 13 +#define LED4 16 +#define LED5 19 +#define LED6 20 +#define LED7 21 + +static int leds[8] = { + LED0, LED1, LED2, LED3, + LED4, LED5, LED6, LED7 +}; + +#define DEBOUNCE_MS 6 +#define LONGPRESS_MS 1000 +#define PAGE_DEBOUNCE_MS 200 + +#define DHT_CACHE_MS 2000 + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("kkk"); +MODULE_DESCRIPTION("DS1302 + rotary/button + DHT11 via /dev/clock_drv"); + +struct rtc_simple { + int hh, mm, ss; + int ch; +}; + +static DEFINE_MUTEX(lock0); + +static struct rtc_simple cur; +static struct rtc_simple edit; +static bool edit_mode = false; +static int edit_field = 2; + +static int ui_page = 0; +static unsigned long last_page_switch_j = 0; + +static int irq_s1, irq_sw; +static unsigned long last_irq_s1, last_irq_sw; +static unsigned long sw_pressed_jiffies; + +static dev_t devno; +static struct cdev cdev0; +static struct class *cls; + +static int dht_temp = -1; +static int dht_hum = -1; +static unsigned long last_dht_j = 0; + + +static inline void ds_clk_pulse(void) +{ + gpio_set_value(DS_SCLK, 1); udelay(2); + gpio_set_value(DS_SCLK, 0); udelay(2); +} + +static void ds_write_byte(u8 b) +{ + int i; + gpio_direction_output(DS_DAT, 0); + for (i = 0; i < 8; i++) { + gpio_set_value(DS_DAT, (b >> i) & 1); + ds_clk_pulse(); + } +} + +static u8 ds_read_byte(void) +{ + int i; + u8 v = 0; + + gpio_direction_input(DS_DAT); + for (i = 0; i < 8; i++) { + if (gpio_get_value(DS_DAT)) v |= (1 << i); + ds_clk_pulse(); + } + return v; +} + +static inline int bcd2int(u8 b) { return (b & 0x0F) + ((b >> 4) & 0x0F) * 10; } +static inline u8 int2bcd(int v){ return (u8)(((v/10) << 4) | (v%10)); } + +static void ds1302_read_time(struct rtc_simple *t) +{ + u8 sec, min, hour; + + gpio_set_value(DS_SCLK, 0); + gpio_set_value(DS_RST, 1); + udelay(4); + + ds_write_byte(0xBF); + sec = ds_read_byte(); + min = ds_read_byte(); + hour = ds_read_byte(); + + ds_read_byte(); ds_read_byte(); ds_read_byte(); ds_read_byte(); ds_read_byte(); + + gpio_set_value(DS_RST, 0); + + t->ch = (sec >> 7) & 1; + t->ss = bcd2int(sec & 0x7F); + t->mm = bcd2int(min); + t->hh = bcd2int(hour & 0x3F); +} + +static void ds1302_write_reg(u8 cmd, u8 data) +{ + gpio_set_value(DS_SCLK, 0); + gpio_set_value(DS_RST, 1); + udelay(4); + + ds_write_byte(cmd); + ds_write_byte(data); + + gpio_set_value(DS_RST, 0); +} + +static void ds1302_set_time(const struct rtc_simple *t) +{ + ds1302_write_reg(0x8E, 0x00); + ds1302_write_reg(0x80, int2bcd(t->ss) & 0x7F); + ds1302_write_reg(0x82, int2bcd(t->mm)); + ds1302_write_reg(0x84, int2bcd(t->hh)); + ds1302_write_reg(0x8E, 0x80); +} + +static void clamp_time(struct rtc_simple *t) +{ + if (t->ss < 0) t->ss = 59; + if (t->ss > 59) t->ss = 0; + + if (t->mm < 0) t->mm = 59; + if (t->mm > 59) t->mm = 0; + + if (t->hh < 0) t->hh = 23; + if (t->hh > 23) t->hh = 0; +} + +static const char *field_name(int f) +{ + if (f == 2) return "HOUR"; + if (f == 1) return "MIN"; + return "SEC"; +} + + +static int dht_wait_level(int level, int timeout_us) +{ + int i; + for (i = 0; i < timeout_us; i++) { + if (gpio_get_value(DHT_GPIO) == level) + return i; + udelay(1); + } + return -ETIMEDOUT; +} + +static int dht_measure_high_us(int timeout_us) +{ + int i = 0; + while (gpio_get_value(DHT_GPIO) == 1) { + if (i++ >= timeout_us) return timeout_us; + udelay(1); + } + return i; +} + +static int dht11_read_once(int *out_temp, int *out_hum) +{ + u8 data[5] = {0}; + int bit, byte; + unsigned long flags; + + gpio_direction_output(DHT_GPIO, 0); + msleep(20); + gpio_set_value(DHT_GPIO, 1); + udelay(40); + gpio_direction_input(DHT_GPIO); + + local_irq_save(flags); + + if (dht_wait_level(0, 100) < 0) goto err; + if (dht_wait_level(1, 100) < 0) goto err; + if (dht_wait_level(0, 100) < 0) goto err; + + for (bit = 0; bit < 40; bit++) { + int high_us; + + if (dht_wait_level(1, 70) < 0) goto err; + + high_us = dht_measure_high_us(100); + + byte = bit / 8; + data[byte] <<= 1; + data[byte] |= (high_us > 40) ? 1 : 0; + + (void)dht_wait_level(0, 70); + } + + local_irq_restore(flags); + + if (((data[0] + data[1] + data[2] + data[3]) & 0xFF) != data[4]) + return -EIO; + + *out_hum = data[0]; + *out_temp = data[2]; + return 0; + +err: + local_irq_restore(flags); + return -ETIMEDOUT; +} + +static void dht11_get_cached(int *out_temp, int *out_hum) +{ + unsigned long now = jiffies; + + if (time_after(now, last_dht_j + msecs_to_jiffies(DHT_CACHE_MS)) || last_dht_j == 0) { + int t, h; + int ret = dht11_read_once(&t, &h); + if (ret == 0) { + dht_temp = t; + dht_hum = h; + } + last_dht_j = now; + } + + *out_temp = dht_temp; + *out_hum = dht_hum; +} + +static void apply_delta_locked(int delta) +{ + if (!edit_mode) return; + if (ui_page != 0) return; + + if (edit_field == 0) edit.ss += delta; + else if (edit_field == 1) edit.mm += delta; + else edit.hh += delta; + + clamp_time(&edit); +} + +static void short_press_locked(void) +{ + if (!edit_mode) return; + if (ui_page != 0) return; + + edit_field = (edit_field == 0) ? 2 : (edit_field - 1); +} + +static void long_press_action(void) +{ + struct rtc_simple t; + + mutex_lock(&lock0); + + if (ui_page != 0) { + mutex_unlock(&lock0); + return; + } + + if (!edit_mode) { + ds1302_read_time(&cur); + edit = cur; + edit_mode = true; + edit_field = 2; + mutex_unlock(&lock0); + return; + } + + t = edit; + edit_mode = false; + mutex_unlock(&lock0); + + ds1302_set_time(&t); +} + +static void page_toggle_if_cw_locked(void) +{ + unsigned long now = jiffies; + + if (edit_mode) return; + + if (time_before(now, last_page_switch_j + msecs_to_jiffies(PAGE_DEBOUNCE_MS))) + return; + + if (gpio_get_value(ENC_S2)) { + + ui_page = (ui_page + 1) % 3; + } else { + ui_page = (ui_page + 3 - 1) % 3; + } + + last_page_switch_j = now; +} + + + +static irqreturn_t s1_irq_handler(int irq, void *dev_id) +{ + unsigned long now = jiffies; + + if (time_before(now, last_irq_s1 + msecs_to_jiffies(DEBOUNCE_MS))) + return IRQ_HANDLED; + last_irq_s1 = now; + + mutex_lock(&lock0); + + if (!edit_mode) { + page_toggle_if_cw_locked(); + } else { + if (gpio_get_value(ENC_S2)) + apply_delta_locked(+1); + else + apply_delta_locked(-1); + } + + mutex_unlock(&lock0); + return IRQ_HANDLED; +} + +static irqreturn_t sw_irq_handler(int irq, void *dev_id) +{ + unsigned long now = jiffies; + + if (time_before(now, last_irq_sw + msecs_to_jiffies(DEBOUNCE_MS))) + return IRQ_HANDLED; + last_irq_sw = now; + + if (gpio_get_value(ENC_SW) == 0) { + sw_pressed_jiffies = now; + } else { + unsigned long held_ms = jiffies_to_msecs(now - sw_pressed_jiffies); + if (held_ms >= LONGPRESS_MS) { + long_press_action(); + } else { + mutex_lock(&lock0); + short_press_locked(); + mutex_unlock(&lock0); + } + } + + return IRQ_HANDLED; +} + +static void set_led_level(int level) +{ + int i; + + if (level < 0) level = 0; + if (level > 8) level = 8; + + for (i = 0; i < 8; i++) { + gpio_set_value(leds[i], (i < level) ? 1 : 0); + } +} + + +static ssize_t dev_read(struct file *f, char __user *ubuf, size_t cnt, loff_t *ppos) +{ + char kbuf[160]; + int len; + struct rtc_simple t; + bool mode; + int field, page; + int temp, hum; + + if (*ppos > 0) return 0; + + ds1302_read_time(&cur); + + mutex_lock(&lock0); + mode = edit_mode; + field = edit_field; + page = ui_page; + t = (mode && page==0) ? edit : cur; + mutex_unlock(&lock0); + + dht11_get_cached(&temp, &hum); + + len = snprintf(kbuf, sizeof(kbuf), + "%02d:%02d:%02d MODE=%s FIELD=%s PAGE=%d TEMP=%d HUM=%d\n", + t.hh, t.mm, t.ss, + (mode && page==0) ? "EDIT" : "RUN", + field_name(field), + page, + temp, hum); + + if (len > cnt) len = cnt; + if (copy_to_user(ubuf, kbuf, len)) return -EFAULT; + + *ppos += len; + return len; +} + +static ssize_t dev_write(struct file *f, const char __user *ubuf, + size_t cnt, loff_t *ppos) +{ + char kbuf[64]; + int hh, mm, ss; + int level; + + if (cnt >= sizeof(kbuf)) + return -EINVAL; + + if (copy_from_user(kbuf, ubuf, cnt)) + return -EFAULT; + + kbuf[cnt] = '\0'; + + + if (sscanf(kbuf, "LED %d", &level) == 1) { + set_led_level(level); + return cnt; + } + + + if (sscanf(kbuf, "SET %d:%d:%d", &hh, &mm, &ss) == 3) { + struct rtc_simple t = { + .hh = hh, + .mm = mm, + .ss = ss, + .ch = 0 + }; + + clamp_time(&t); + ds1302_set_time(&t); + + mutex_lock(&lock0); + edit_mode = false; + mutex_unlock(&lock0); + + return cnt; + } + + return -EINVAL; +} + +static const struct file_operations fops = { + .owner = THIS_MODULE, + .read = dev_read, + .write = dev_write, +}; + +static int __init mod_init(void) +{ + int ret; + struct timespec64 ts; + struct rtc_time tm_val; + + printk(KERN_INFO "==== %s init ====\n", DRIVER_NAME); + + ret = alloc_chrdev_region(&devno, 0, 1, DRIVER_NAME); + if (ret < 0) return ret; + + for (int i = 0; i < 8; i++) { + gpio_request(leds[i], "led"); + gpio_direction_output(leds[i], 0); + } + + cdev_init(&cdev0, &fops); + ret = cdev_add(&cdev0, devno, 1); + if (ret < 0) goto err_chr; + + cls = class_create(THIS_MODULE, CLASS_NAME); + if (IS_ERR(cls)) { ret = PTR_ERR(cls); goto err_cdev; } + + device_create(cls, NULL, devno, NULL, DRIVER_NAME); + + if (gpio_request(DS_RST,"ds_rst")|| + gpio_request(DS_DAT,"ds_dat")|| + gpio_request(DS_SCLK,"ds_sclk")|| + gpio_request(ENC_S1,"enc_s1")|| + gpio_request(ENC_S2,"enc_s2")|| + gpio_request(ENC_SW,"enc_sw")|| + gpio_request(DHT_GPIO,"dht11")) { + ret = -EBUSY; + goto err_dev; + } + + gpio_direction_output(DS_RST,0); + gpio_direction_output(DS_SCLK,0); + gpio_direction_output(DS_DAT,0); + + gpio_direction_input(ENC_S1); + gpio_direction_input(ENC_S2); + gpio_direction_input(ENC_SW); + gpio_direction_input(DHT_GPIO); + + irq_s1 = gpio_to_irq(ENC_S1); + irq_sw = gpio_to_irq(ENC_SW); + + ret = request_irq(irq_s1,s1_irq_handler,IRQF_TRIGGER_FALLING,"enc_s1_irq",NULL); + if (ret) goto err_gpio; + + ret = request_irq(irq_sw,sw_irq_handler, + IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, + "enc_sw_irq",NULL); + if (ret) { free_irq(irq_s1,NULL); goto err_gpio; } + + ds1302_read_time(&cur); + if (cur.ch==1) { + ds1302_set_time(&cur); + ds1302_read_time(&cur); + } + + ktime_get_real_ts64(&ts); + rtc_time64_to_tm(ts.tv_sec,&tm_val); + + mutex_lock(&lock0); + cur.hh = (tm_val.tm_hour+24-3)%24; + cur.mm = tm_val.tm_min; + cur.ss = tm_val.tm_sec; + cur.ch = 0; + + ds1302_set_time(&cur); + edit = cur; + edit_mode = false; + edit_field = 2; + ui_page = 0; + mutex_unlock(&lock0); + + printk(KERN_INFO "OK: DHT cached every %d ms\n", DHT_CACHE_MS); + return 0; + +err_gpio: + gpio_free(DHT_GPIO); + gpio_free(ENC_SW); gpio_free(ENC_S2); gpio_free(ENC_S1); + gpio_free(DS_SCLK); gpio_free(DS_DAT); gpio_free(DS_RST); +err_dev: + device_destroy(cls,devno); + class_destroy(cls); +err_cdev: + cdev_del(&cdev0); +err_chr: + unregister_chrdev_region(devno,1); + return ret; +} + +static void __exit mod_exit(void) +{ + free_irq(irq_s1,NULL); + free_irq(irq_sw,NULL); + + gpio_free(DHT_GPIO); + gpio_free(ENC_SW); gpio_free(ENC_S2); gpio_free(ENC_S1); + gpio_free(DS_SCLK); gpio_free(DS_DAT); gpio_free(DS_RST); + + for (int i = 0; i < 8; i++) + gpio_free(leds[i]); + + device_destroy(cls,devno); + class_destroy(cls); + cdev_del(&cdev0); + unregister_chrdev_region(devno,1); + + printk(KERN_INFO "==== %s exit ====\n", DRIVER_NAME); +} + +module_init(mod_init); +module_exit(mod_exit);