diff --git a/ssd1306/Makefile b/ssd1306/Makefile new file mode 100644 index 0000000..6c2a3e9 --- /dev/null +++ b/ssd1306/Makefile @@ -0,0 +1,36 @@ +# +# ssd1306 +# +ifneq ($(KERNELRELEASE),) + +obj-m := i2c_ssd1306.o + +else + + +ifeq ($(KERNELDIR),) +ifeq ($(BBB_KERNEL),) + $(error Path to kernel tree - KERNELDIR or BBB_KERNEL variable is not defined!) +endif +endif + +KERNELDIR ?= $(BBB_KERNEL) + +export ARCH = arm +export CROSS_COMPILE ?= arm-linux-gnueabihf- + + +.PHONY: all clean dtb + +all: + $(MAKE) -C $(KERNELDIR) M=$(CURDIR) modules + +clean: + $(MAKE) -C $(KERNELDIR) M=$(CURDIR) clean + +dtb: + cp am335x-bone-common.dtsi $(KERNELDIR)/arch/arm/boot/dts/ + $(MAKE) -C $(KERNELDIR) M=$(CURDIR) dtbs + + +endif diff --git a/ssd1306/am335x-bone-common.dtsi b/ssd1306/am335x-bone-common.dtsi new file mode 100644 index 0000000..be9c26b --- /dev/null +++ b/ssd1306/am335x-bone-common.dtsi @@ -0,0 +1,471 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include + +/ { + cpus { + cpu@0 { + cpu0-supply = <&dcdc2_reg>; + }; + }; + + memory@80000000 { + device_type = "memory"; + reg = <0x80000000 0x10000000>; /* 256 MB */ + }; + + chosen { + stdout-path = &uart0; + }; + + leds { + pinctrl-names = "default"; + pinctrl-0 = <&user_leds_s0>; + + compatible = "gpio-leds"; + + led2 { + label = "beaglebone:green:usr0"; + gpios = <&gpio1 21 GPIO_ACTIVE_HIGH>; + linux,default-trigger = "heartbeat"; + default-state = "off"; + }; + + led3 { + label = "beaglebone:green:usr1"; + gpios = <&gpio1 22 GPIO_ACTIVE_HIGH>; + linux,default-trigger = "mmc0"; + default-state = "off"; + }; + + led4 { + label = "beaglebone:green:usr2"; + gpios = <&gpio1 23 GPIO_ACTIVE_HIGH>; + linux,default-trigger = "cpu0"; + default-state = "off"; + }; + + led5 { + label = "beaglebone:green:usr3"; + gpios = <&gpio1 24 GPIO_ACTIVE_HIGH>; + linux,default-trigger = "mmc1"; + default-state = "off"; + }; + }; + + vmmcsd_fixed: fixedregulator0 { + compatible = "regulator-fixed"; + regulator-name = "vmmcsd_fixed"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + }; +}; + +&am33xx_pinmux { + user_leds_s0: user_leds_s0 { + pinctrl-single,pins = < + AM33XX_IOPAD(0x854, PIN_OUTPUT_PULLDOWN | MUX_MODE7) /* gpmc_a5.gpio1_21 */ + AM33XX_IOPAD(0x858, PIN_OUTPUT_PULLUP | MUX_MODE7) /* gpmc_a6.gpio1_22 */ + AM33XX_IOPAD(0x85c, PIN_OUTPUT_PULLDOWN | MUX_MODE7) /* gpmc_a7.gpio1_23 */ + AM33XX_IOPAD(0x860, PIN_OUTPUT_PULLUP | MUX_MODE7) /* gpmc_a8.gpio1_24 */ + >; + }; + + i2c0_pins: pinmux_i2c0_pins { + pinctrl-single,pins = < + AM33XX_IOPAD(0x988, PIN_INPUT_PULLUP | MUX_MODE0) /* i2c0_sda.i2c0_sda */ + AM33XX_IOPAD(0x98c, PIN_INPUT_PULLUP | MUX_MODE0) /* i2c0_scl.i2c0_scl */ + >; + }; + + i2c1_pins: pinmux_i2c1_pins { + pinctrl-single,pins = < + AM33XX_IOPAD(0x958, PIN_INPUT_PULLUP | MUX_MODE2) /* i2c1_scl.i2c1_scl */ + AM33XX_IOPAD(0x95c, PIN_INPUT_PULLUP | MUX_MODE2) /* i2c1_sda.i2c1_sda */ + >; + }; + + i2c2_pins: pinmux_i2c2_pins { + pinctrl-single,pins = < + AM33XX_IOPAD(0x978, PIN_INPUT_PULLUP | MUX_MODE3) /* uart1_ctsn.i2c2_sda */ + AM33XX_IOPAD(0x97c, PIN_INPUT_PULLUP | MUX_MODE3) /* uart1_rtsn.i2c2_scl */ + >; + }; + + uart0_pins: pinmux_uart0_pins { + pinctrl-single,pins = < + AM33XX_IOPAD(0x970, PIN_INPUT_PULLUP | MUX_MODE0) /* uart0_rxd.uart0_rxd */ + AM33XX_IOPAD(0x974, PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* uart0_txd.uart0_txd */ + >; + }; + + cpsw_default: cpsw_default { + pinctrl-single,pins = < + /* Slave 1 */ + 0x108 (PIN_INPUT | MUX_MODE0) /* mii1_col.mii1_col */ + 0x10c (PIN_INPUT | MUX_MODE0) /* mii1_crs.mii1_crs */ + AM33XX_IOPAD(0x910, PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_rxerr.mii1_rxerr */ + AM33XX_IOPAD(0x914, PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* mii1_txen.mii1_txen */ + AM33XX_IOPAD(0x918, PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_rxdv.mii1_rxdv */ + AM33XX_IOPAD(0x91c, PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* mii1_txd3.mii1_txd3 */ + AM33XX_IOPAD(0x920, PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* mii1_txd2.mii1_txd2 */ + AM33XX_IOPAD(0x924, PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* mii1_txd1.mii1_txd1 */ + AM33XX_IOPAD(0x928, PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* mii1_txd0.mii1_txd0 */ + AM33XX_IOPAD(0x92c, PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_txclk.mii1_txclk */ + AM33XX_IOPAD(0x930, PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_rxclk.mii1_rxclk */ + AM33XX_IOPAD(0x934, PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_rxd3.mii1_rxd3 */ + AM33XX_IOPAD(0x938, PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_rxd2.mii1_rxd2 */ + AM33XX_IOPAD(0x93c, PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_rxd1.mii1_rxd1 */ + AM33XX_IOPAD(0x940, PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_rxd0.mii1_rxd0 */ + >; + }; + + cpsw_sleep: cpsw_sleep { + pinctrl-single,pins = < + /* Slave 1 reset value */ + 0x108 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x10c (PIN_INPUT_PULLDOWN | MUX_MODE7) + AM33XX_IOPAD(0x910, PIN_INPUT_PULLDOWN | MUX_MODE7) + AM33XX_IOPAD(0x914, PIN_INPUT_PULLDOWN | MUX_MODE7) + AM33XX_IOPAD(0x918, PIN_INPUT_PULLDOWN | MUX_MODE7) + AM33XX_IOPAD(0x91c, PIN_INPUT_PULLDOWN | MUX_MODE7) + AM33XX_IOPAD(0x920, PIN_INPUT_PULLDOWN | MUX_MODE7) + AM33XX_IOPAD(0x924, PIN_INPUT_PULLDOWN | MUX_MODE7) + AM33XX_IOPAD(0x928, PIN_INPUT_PULLDOWN | MUX_MODE7) + AM33XX_IOPAD(0x92c, PIN_INPUT_PULLDOWN | MUX_MODE7) + AM33XX_IOPAD(0x930, PIN_INPUT_PULLDOWN | MUX_MODE7) + AM33XX_IOPAD(0x934, PIN_INPUT_PULLDOWN | MUX_MODE7) + AM33XX_IOPAD(0x938, PIN_INPUT_PULLDOWN | MUX_MODE7) + AM33XX_IOPAD(0x93c, PIN_INPUT_PULLDOWN | MUX_MODE7) + AM33XX_IOPAD(0x940, PIN_INPUT_PULLDOWN | MUX_MODE7) + >; + }; + + davinci_mdio_default: davinci_mdio_default { + pinctrl-single,pins = < + /* MDIO */ + AM33XX_IOPAD(0x948, PIN_INPUT_PULLUP | SLEWCTRL_FAST | MUX_MODE0) /* mdio_data.mdio_data */ + AM33XX_IOPAD(0x94c, PIN_OUTPUT_PULLUP | MUX_MODE0) /* mdio_clk.mdio_clk */ + >; + }; + + davinci_mdio_sleep: davinci_mdio_sleep { + pinctrl-single,pins = < + /* MDIO reset value */ + AM33XX_IOPAD(0x948, PIN_INPUT_PULLDOWN | MUX_MODE7) + AM33XX_IOPAD(0x94c, PIN_INPUT_PULLDOWN | MUX_MODE7) + >; + }; + + mmc1_pins: pinmux_mmc1_pins { + pinctrl-single,pins = < + AM33XX_IOPAD(0x960, PIN_INPUT | MUX_MODE7) /* GPIO0_6 */ + >; + }; + + emmc_pins: pinmux_emmc_pins { + pinctrl-single,pins = < + AM33XX_IOPAD(0x880, PIN_INPUT_PULLUP | MUX_MODE2) /* gpmc_csn1.mmc1_clk */ + AM33XX_IOPAD(0x884, PIN_INPUT_PULLUP | MUX_MODE2) /* gpmc_csn2.mmc1_cmd */ + AM33XX_IOPAD(0x800, PIN_INPUT_PULLUP | MUX_MODE1) /* gpmc_ad0.mmc1_dat0 */ + AM33XX_IOPAD(0x804, PIN_INPUT_PULLUP | MUX_MODE1) /* gpmc_ad1.mmc1_dat1 */ + AM33XX_IOPAD(0x808, PIN_INPUT_PULLUP | MUX_MODE1) /* gpmc_ad2.mmc1_dat2 */ + AM33XX_IOPAD(0x80c, PIN_INPUT_PULLUP | MUX_MODE1) /* gpmc_ad3.mmc1_dat3 */ + AM33XX_IOPAD(0x810, PIN_INPUT_PULLUP | MUX_MODE1) /* gpmc_ad4.mmc1_dat4 */ + AM33XX_IOPAD(0x814, PIN_INPUT_PULLUP | MUX_MODE1) /* gpmc_ad5.mmc1_dat5 */ + AM33XX_IOPAD(0x818, PIN_INPUT_PULLUP | MUX_MODE1) /* gpmc_ad6.mmc1_dat6 */ + AM33XX_IOPAD(0x81c, PIN_INPUT_PULLUP | MUX_MODE1) /* gpmc_ad7.mmc1_dat7 */ + >; + }; +}; + +&uart0 { + pinctrl-names = "default"; + pinctrl-0 = <&uart0_pins>; + + status = "okay"; +}; + +&usb { + status = "okay"; +}; + +&usb_ctrl_mod { + status = "okay"; +}; + +&usb0_phy { + status = "okay"; +}; + +&usb1_phy { + status = "okay"; +}; + +&usb0 { + status = "okay"; + dr_mode = "peripheral"; +}; + +&usb1 { + status = "okay"; + dr_mode = "host"; +}; + +&cppi41dma { + status = "okay"; +}; + +&i2c0 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c0_pins>; + + status = "okay"; + clock-frequency = <400000>; + + tps: tps@24 { + reg = <0x24>; + }; + + baseboard_eeprom: baseboard_eeprom@50 { + compatible = "at,24c256"; + reg = <0x50>; + + #address-cells = <1>; + #size-cells = <1>; + baseboard_data: baseboard_data@0 { + reg = <0 0x100>; + }; + }; +}; + +&i2c2 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c2_pins>; + + status = "okay"; + clock-frequency = <100000>; + + i2c_ssd1306: i2c_ssd1306@3c { + compatible = "gl,i2c_ssd1306"; + reg = <0x3c>; + status = "okay"; + }; + + cape_eeprom0: cape_eeprom0@54 { + compatible = "at,24c256"; + reg = <0x54>; + #address-cells = <1>; + #size-cells = <1>; + cape0_data: cape_data@0 { + reg = <0 0x100>; + }; + }; + + cape_eeprom1: cape_eeprom1@55 { + compatible = "at,24c256"; + reg = <0x55>; + #address-cells = <1>; + #size-cells = <1>; + cape1_data: cape_data@0 { + reg = <0 0x100>; + }; + }; + + cape_eeprom2: cape_eeprom2@56 { + compatible = "at,24c256"; + reg = <0x56>; + #address-cells = <1>; + #size-cells = <1>; + cape2_data: cape_data@0 { + reg = <0 0x100>; + }; + }; + + cape_eeprom3: cape_eeprom3@57 { + compatible = "at,24c256"; + reg = <0x57>; + #address-cells = <1>; + #size-cells = <1>; + cape3_data: cape_data@0 { + reg = <0 0x100>; + }; + }; +}; + + +/include/ "tps65217.dtsi" + +&tps { + /* + * Configure pmic to enter OFF-state instead of SLEEP-state ("RTC-only + * mode") at poweroff. Most BeagleBone versions do not support RTC-only + * mode and risk hardware damage if this mode is entered. + * + * For details, see linux-omap mailing list May 2015 thread + * [PATCH] ARM: dts: am335x-bone* enable pmic-shutdown-controller + * In particular, messages: + * http://www.spinics.net/lists/linux-omap/msg118585.html + * http://www.spinics.net/lists/linux-omap/msg118615.html + * + * You can override this later with + * &tps { /delete-property/ ti,pmic-shutdown-controller; } + * if you want to use RTC-only mode and made sure you are not affected + * by the hardware problems. (Tip: double-check by performing a current + * measurement after shutdown: it should be less than 1 mA.) + */ + + interrupts = <7>; /* NMI */ + interrupt-parent = <&intc>; + + ti,pmic-shutdown-controller; + + charger { + interrupts = , ; + interrupts-names = "AC", "USB"; + status = "okay"; + }; + + pwrbutton { + interrupts = ; + status = "okay"; + }; + + regulators { + dcdc1_reg: regulator@0 { + regulator-name = "vdds_dpr"; + regulator-always-on; + }; + + dcdc2_reg: regulator@1 { + /* VDD_MPU voltage limits 0.95V - 1.26V with +/-4% tolerance */ + regulator-name = "vdd_mpu"; + regulator-min-microvolt = <925000>; + regulator-max-microvolt = <1351500>; + regulator-boot-on; + regulator-always-on; + }; + + dcdc3_reg: regulator@2 { + /* VDD_CORE voltage limits 0.95V - 1.1V with +/-4% tolerance */ + regulator-name = "vdd_core"; + regulator-min-microvolt = <925000>; + regulator-max-microvolt = <1150000>; + regulator-boot-on; + regulator-always-on; + }; + + ldo1_reg: regulator@3 { + regulator-name = "vio,vrtc,vdds"; + regulator-always-on; + }; + + ldo2_reg: regulator@4 { + regulator-name = "vdd_3v3aux"; + regulator-always-on; + }; + + ldo3_reg: regulator@5 { + regulator-name = "vdd_1v8"; + regulator-always-on; + }; + + ldo4_reg: regulator@6 { + regulator-name = "vdd_3v3a"; + regulator-always-on; + }; + }; +}; + +&cpsw_emac0 { + phy_id = <&davinci_mdio>, <0>; + phy-mode = "mii"; +}; + +&mac { + slaves = <1>; + pinctrl-names = "default", "sleep"; + pinctrl-0 = <&cpsw_default>; + pinctrl-1 = <&cpsw_sleep>; + status = "okay"; +}; + +&davinci_mdio { + pinctrl-names = "default", "sleep"; + pinctrl-0 = <&davinci_mdio_default>; + pinctrl-1 = <&davinci_mdio_sleep>; + status = "okay"; +}; + +&mmc1 { + status = "okay"; + bus-width = <0x4>; + pinctrl-names = "default"; + pinctrl-0 = <&mmc1_pins>; + cd-gpios = <&gpio0 6 GPIO_ACTIVE_LOW>; +}; + +&aes { + status = "okay"; +}; + +&sham { + status = "okay"; +}; + +&rtc { + clocks = <&clk_32768_ck>, <&clkdiv32k_ick>; + clock-names = "ext-clk", "int-clk"; + system-power-controller; +}; + +&wkup_m3_ipc { + ti,scale-data-fw = "am335x-bone-scale-data.bin"; +}; + +&pruss_soc_bus { + status = "okay"; + + pruss: pruss@4a300000 { + status = "okay"; + + pru0: pru@4a334000 { + status = "okay"; + }; + + pru1: pru@4a338000 { + status = "okay"; + }; + }; +}; + +/* the cape manager */ +/ { + bone_capemgr { + compatible = "ti,bone-capemgr"; + status = "okay"; + + nvmem-cells = <&baseboard_data &cape0_data &cape1_data &cape2_data &cape3_data>; + nvmem-cell-names = "baseboard", "slot0", "slot1", "slot2", "slot3"; + #slots = <4>; + + /* map board revisions to compatible definitions */ + baseboardmaps { + baseboard_beaglebone: board@0 { + board-name = "A335BONE"; + compatible-name = "ti,beaglebone"; + }; + + baseboard_beaglebone_black: board@1 { + board-name = "A335BNLT"; + compatible-name = "ti,beaglebone-black"; + }; + }; + }; +}; diff --git a/ssd1306/i2c_ssd1306.c b/ssd1306/i2c_ssd1306.c new file mode 100644 index 0000000..fe6d61d --- /dev/null +++ b/ssd1306/i2c_ssd1306.c @@ -0,0 +1,369 @@ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#define CMD_ADDR 0 + +#define SET_LOW_COLUMN 0x00 +#define SET_HIGH_COLUMN 0x10 +#define COLUMN_ADDR 0x21 +#define PAGE_ADDR 0x22 +#define SET_START_PAGE 0xB0 +#define CHARGE_PUMP 0x8D +#define DISPLAY_OFF 0xAE +#define DISPLAY_ON 0xAF + +#define MEMORY_MODE 0x20 +#define SET_CONTRAST 0x81 +#define SET_NORMAL_DISPLAY 0xA6 +#define SET_INVERT_DISPLAY 0xA7 +#define COM_SCAN_INC 0xC0 +#define COM_SCAN_DEC 0xC8 +#define SET_DISPLAY_OFFSET 0xD3 +#define SET_DISPLAY_CLOCK_DIV 0xD5 +#define SET_PRECHARGE 0xD9 +#define SET_COM_PINS 0xDA +#define SET_VCOM_DETECT 0xDB +#define SET_START_LINE 0x40 + +#define CHECK_STATUS_REG_VAL 0x43 + +#define MYDEVNAME "i2c_ssd1306" +#define LCD_WIDTH 128 +#define LCD_HEIGHT 64 +#define LCD_BPP 8 +#define SYNC_RATE_MS 50 + +struct my_device { + struct i2c_client *client; + struct fb_info *fb_info; + struct task_struct *th; + u8 *vmem; + size_t vmsize; + atomic_t dirty; +}; + +static const struct i2c_device_id i2c_ssd1306_idtable[] = { + { MYDEVNAME, 0 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, i2c_ssd1306_idtable); + +static int i2c_ssd1306_init(struct i2c_client *drv_client) +{ + s32 val; + + i2c_smbus_write_byte_data(drv_client, CMD_ADDR, DISPLAY_OFF); + val = i2c_smbus_read_byte_data(drv_client, CMD_ADDR); + + dev_info(&drv_client->dev, "%s: status reg: %d\n", __func__, val); + if (val != CHECK_STATUS_REG_VAL) + return -1; + + i2c_smbus_write_byte_data(drv_client, CMD_ADDR, MEMORY_MODE); + i2c_smbus_write_byte_data(drv_client, CMD_ADDR, 0); + /* Set column start / end */ + i2c_smbus_write_byte_data(drv_client, CMD_ADDR, COLUMN_ADDR); + i2c_smbus_write_byte_data(drv_client, CMD_ADDR, 0); + i2c_smbus_write_byte_data(drv_client, CMD_ADDR, (LCD_WIDTH - 1)); + + /* Set page start / end */ + i2c_smbus_write_byte_data(drv_client, CMD_ADDR, PAGE_ADDR); + i2c_smbus_write_byte_data(drv_client, CMD_ADDR, 0); + i2c_smbus_write_byte_data(drv_client, CMD_ADDR, (LCD_HEIGHT / 8 - 1)); + + i2c_smbus_write_byte_data(drv_client, CMD_ADDR, COM_SCAN_DEC); + + i2c_smbus_write_byte_data(drv_client, CMD_ADDR, SET_CONTRAST); + /* Max contrast */ + i2c_smbus_write_byte_data(drv_client, CMD_ADDR, 0xFF); + + /* set segment re-map 0 to 127 */ + i2c_smbus_write_byte_data(drv_client, CMD_ADDR, 0xA1); + i2c_smbus_write_byte_data(drv_client, CMD_ADDR, SET_NORMAL_DISPLAY); + /* set multiplex ratio(1 to 64) */ + i2c_smbus_write_byte_data(drv_client, CMD_ADDR, 0xA8); + i2c_smbus_write_byte_data(drv_client, CMD_ADDR, (LCD_HEIGHT - 1)); + /* 0xA4 => follows RAM content */ + i2c_smbus_write_byte_data(drv_client, CMD_ADDR, 0xA4); + i2c_smbus_write_byte_data(drv_client, CMD_ADDR, SET_DISPLAY_OFFSET); + i2c_smbus_write_byte_data(drv_client, CMD_ADDR, 0x00); + + i2c_smbus_write_byte_data(drv_client, CMD_ADDR, SET_DISPLAY_CLOCK_DIV); + i2c_smbus_write_byte_data(drv_client, CMD_ADDR, 0x80); + + i2c_smbus_write_byte_data(drv_client, CMD_ADDR, SET_PRECHARGE); + i2c_smbus_write_byte_data(drv_client, CMD_ADDR, 0x22); + + i2c_smbus_write_byte_data(drv_client, CMD_ADDR, SET_COM_PINS); + i2c_smbus_write_byte_data(drv_client, CMD_ADDR, 0x12); + + i2c_smbus_write_byte_data(drv_client, CMD_ADDR, SET_VCOM_DETECT); + i2c_smbus_write_byte_data(drv_client, CMD_ADDR, 0x20); + + + i2c_smbus_write_byte_data(drv_client, CMD_ADDR, CHARGE_PUMP); + i2c_smbus_write_byte_data(drv_client, CMD_ADDR, 0x14); + + i2c_smbus_write_byte_data(drv_client, CMD_ADDR, DISPLAY_ON); + + return 0; +} + +static inline void set_dirty(struct my_device *md) +{ + atomic_inc(&(md->dirty)); +} + +static ssize_t i2c_ssd1306_write(struct fb_info *info, + const char __user *buf, size_t count, loff_t *ppos) +{ + struct i2c_client *drv_client; + struct my_device *md = info->par; + int cnt; + + if (!md) + return -EINVAL; + + drv_client = md->client; + dev_info(&drv_client->dev, "%s: enter\n", __func__); + + cnt = fb_sys_write(info, buf, count, ppos); + + if (cnt < 0) + return cnt; + + set_dirty(md); + + return cnt; +} + +static int i2c_ssd1306_blank(int blank_mode, struct fb_info *info) +{ + struct i2c_client *drv_client; + struct my_device *md = info->par; + + if (!md) + return -EINVAL; + + drv_client = md->client; + dev_info(&drv_client->dev, "%s: enter\n", __func__); + return -1; +} + +static void i2c_ssd1306_fillrect(struct fb_info *info, + const struct fb_fillrect *rect) +{ + struct i2c_client *drv_client; + struct my_device *md = info->par; + + if (!md) + return; + + drv_client = md->client; + dev_info(&drv_client->dev, "%s: enter\n", __func__); +} + +static void i2c_ssd1306_copyarea(struct fb_info *info, + const struct fb_copyarea *area) +{ + struct i2c_client *drv_client; + struct my_device *md = info->par; + + if (!md) + return; + + drv_client = md->client; + dev_info(&drv_client->dev, "%s: enter\n", __func__); +} + +static void i2c_ssd1306_imageblit(struct fb_info *info, + const struct fb_image *image) +{ + struct i2c_client *drv_client; + struct my_device *md = info->par; + + if (!md) + return; + + drv_client = md->client; + dev_info(&drv_client->dev, "%s: enter\n", __func__); +} + +static void copy_byte_to_bit_data(u8 *dst, u8 *src, int size) +{ + int i, j, k; + u8 buf; + + for (i = 0; i < LCD_HEIGHT / 8; ++i) { + for (j = 0; j < LCD_WIDTH; ++j) { + for (buf = 0, k = 0; k < 8; ++k) + buf |= src[ + j + + k * LCD_WIDTH + + i * LCD_WIDTH * 8] > 0x7f ? 1 << k : 0; + *dst++ = buf; + } + } +} + +static int update_display_thread(void *data) +{ + static u8 out[LCD_WIDTH * LCD_HEIGHT / 8 + 1]; + struct my_device *md = (struct my_device *) data; + + pr_info("thread started\n"); + do { + if (atomic_xchg(&md->dirty, 0)) { + if (sizeof(out) - 1 != md->vmsize / 8) { + pr_err("error in code %d != %d\n", + sizeof(out) - 1, md->vmsize / 8); + break; + } + out[0] = SET_START_LINE + 0; + copy_byte_to_bit_data( + &out[1], md->vmem, md->vmsize); + if (i2c_master_send(md->client, out, sizeof(out)) < 0) { + pr_err("error send to device\n"); + break; + } + } + msleep(SYNC_RATE_MS); + } while (!kthread_should_stop()); + pr_info("exit from thread\n"); + return 0; +} + +static struct fb_ops fb_ops = { + .owner = THIS_MODULE, + .fb_read = fb_sys_read, + .fb_write = i2c_ssd1306_write, + .fb_blank = i2c_ssd1306_blank, + .fb_fillrect = i2c_ssd1306_fillrect, + .fb_copyarea = i2c_ssd1306_copyarea, + .fb_imageblit = i2c_ssd1306_imageblit, +}; + +static int i2c_ssd1306_probe(struct i2c_client *drv_client, + const struct i2c_device_id *id) +{ + struct fb_info *fb_info; + struct my_device *md; + u8 *vmem; + size_t vmem_size; + int err; + + dev_info(&drv_client->dev, "init I2C driver client address %d\n", + drv_client->addr); + + if (i2c_ssd1306_init(drv_client)) { + dev_info(&drv_client->dev, "I2C device not found\n"); + return -1; + } + + vmem_size = LCD_WIDTH * LCD_HEIGHT * LCD_BPP / 8; + vmem = vzalloc(vmem_size); + if (!vmem) { + dev_info(&drv_client->dev, + "vzalloc error size: %d\n", vmem_size); + return -ENOMEM; + } + + fb_info = framebuffer_alloc(sizeof(struct my_device), &drv_client->dev); + if (!fb_info) { + dev_info(&drv_client->dev, "framebuffer_alloc error\n"); + err = -ENOMEM; + goto error; + } + + fb_info->fbops = &fb_ops; + fb_info->screen_base = (u8 __force __iomem *)vmem; + fb_info->fix.smem_start = __pa(vmem); + fb_info->fix.smem_len = vmem_size; + + i2c_set_clientdata(drv_client, fb_info->par); + + md = fb_info->par; + md->fb_info = fb_info; + md->client = drv_client; + md->vmsize = vmem_size; + md->vmem = vmem; + + strncpy(fb_info->fix.id, MYDEVNAME, 16); + fb_info->fix.type = FB_TYPE_PACKED_PIXELS; + fb_info->fix.visual = FB_VISUAL_MONO10; + fb_info->fix.accel = FB_ACCEL_NONE; + fb_info->fix.line_length = LCD_WIDTH; + + fb_info->var.bits_per_pixel = LCD_BPP; + fb_info->var.xres = LCD_WIDTH; + fb_info->var.xres_virtual = LCD_WIDTH; + fb_info->var.yres = LCD_HEIGHT; + fb_info->var.yres_virtual = LCD_HEIGHT; + + fb_info->var.red.length = 1; + fb_info->var.red.offset = 0; + fb_info->var.green.length = 1; + fb_info->var.green.offset = 0; + fb_info->var.blue.length = 1; + fb_info->var.blue.offset = 0; + + err = register_framebuffer(fb_info); + if (err) { + dev_err(&drv_client->dev, "Couldn't register the framebuffer\n"); + goto error; + } + + md->th = kthread_run(update_display_thread, md, "i2c_ssd1306_thread"); + dev_info(&drv_client->dev, "%s: thread %p started\n", __func__, md->th); + + dev_info(&drv_client->dev, "ssd1306 driver successfully loaded\n"); + return 0; + +error: + vfree(vmem); + return err; +} + +static int i2c_ssd1306_remove(struct i2c_client *drv_client) +{ + struct my_device *md = i2c_get_clientdata(drv_client); + + kthread_stop(md->th); + unregister_framebuffer(md->fb_info); + + dev_info(&drv_client->dev, "ssd1306 driver successfully removed\n"); + return 0; +} + +static struct i2c_driver i2c_ssd1306_driver = { + .driver = { + .name = MYDEVNAME, + }, + + .probe = i2c_ssd1306_probe, + .remove = i2c_ssd1306_remove, + .id_table = i2c_ssd1306_idtable, +}; + +module_i2c_driver(i2c_ssd1306_driver); + +MODULE_AUTHOR("Dmytro Kirtoka "); +MODULE_DESCRIPTION("tft lcd b&w screen driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("0.1");