diff --git a/adafruit-pitft.py b/adafruit-pitft.py index a7dc605..7ab5f5c 100755 --- a/adafruit-pitft.py +++ b/adafruit-pitft.py @@ -197,7 +197,7 @@ def softwareinstall(): if not shell.run_command("apt-get install -y libts0", True): if not shell.run_command("apt-get install -y tslib"): warn_exit("Apt failed to install TSLIB!") - if not shell.run_command("apt-get install -y bc fbi git python-dev python-pip python-smbus python-spidev evtest libts-bin device-tree-compiler"): + if not shell.run_command("apt-get install -y bc fbi git python3-dev python3-pip python3-smbus python3-spidev evtest libts-bin device-tree-compiler"): warn_exit("Apt failed to install software!") if not shell.run_command("pip3 install evdev"): warn_exit("Pip failed to install software!") @@ -250,11 +250,11 @@ def update_configtxt(rotation_override=None): if not shell.isdir("/lib/modules/{}/build".format(shell.release())): warn_exit("Kernel was updated, please reboot now and re-run script!") shell.pushd("st7789_module") - if not shell.run_command("make -C /lib/modules/$(uname -r)/build M=$(pwd) modules"): + if not shell.run_command("make"): warn_exit("Apt failed to compile ST7789V drivers!") - shell.run_command("mv /lib/modules/{rel}/kernel/drivers/gpu/drm/tiny/mi0283qt.ko /lib/modules/{rel}/kernel/drivers/gpu/drm/tiny/mi0283qt.BACK".format(rel=shell.release())) - shell.run_command("mv /lib/modules/{rel}/kernel/drivers/staging/fbtft/fb_st7789v.ko /lib/modules/{rel}/kernel/drivers/gpu/drm/tiny/mi0283qt.BACK".format(rel=shell.release())) - shell.run_command("mv st7789v_ada.ko /lib/modules/{rel}/kernel/drivers/gpu/drm/tiny/mi0283qt.ko".format(rel=shell.release())) + # Only back up if a copy doesn't already exist, so if script is ran more than once we don't lose the original + if not shell.exists("/lib/modules/{rel}/kernel/drivers/staging/fbtft/fb_st7789v.BACK".format(rel=shell.release())): + shell.run_command("mv /lib/modules/{rel}/kernel/drivers/staging/fbtft/fb_st7789v.ko /lib/modules/{rel}/kernel/drivers/staging/fbtft/fb_st7789v.BACK".format(rel=shell.release())) shell.run_command("mv fb_st7789v.ko /lib/modules/{rel}/kernel/drivers/staging/fbtft/fb_st7789v.ko".format(rel=shell.release())) shell.popd() diff --git a/overlays/minipitft114-overlay.dts b/overlays/minipitft114-overlay.dts index 49c49a3..b1c6504 100644 --- a/overlays/minipitft114-overlay.dts +++ b/overlays/minipitft114-overlay.dts @@ -1,5 +1,5 @@ /* - * Device Tree overlay for Adafruit Mini PiTFT 1.14" Display + * Device Tree overlay for Adafruit Mini PiTFT 1.14" 240x240 Display * */ @@ -7,72 +7,65 @@ /plugin/; / { - compatible = "brcm,bcm2835", "brcm,bcm2708", "brcm,bcm2709"; + compatible = "brcm,bcm2835", "brcm,bcm2708", "brcm,bcm2709"; - fragment@0 { - target = <&spi0>; - __overlay__ { - status = "okay"; + fragment@0 { + target = <&spi0>; + __overlay__ { + status = "okay"; - spidev@0{ - status = "disabled"; - }; + spidev@0{ + status = "disabled"; + }; - }; - }; + spidev@1{ + status = "disabled"; + }; + }; + }; - fragment@1 { - target = <&gpio>; - __overlay__ { - pitft_pins: pitft_pins { - brcm,pins = <25>; /* dc pin */ - brcm,function = <1>; /* out */ - brcm,pull = <0>; /* no pull */ - }; - }; - }; + fragment@1 { + target = <&gpio>; + __overlay__ { + pitft_pins: pitft_pins { + brcm,pins = <25>; + brcm,function = <1>; /* out */ + brcm,pull = <0>; /* none */ + }; + }; + }; - fragment@2 { - target = <&spi0>; - __overlay__ { - /* needed to avoid dtc warning */ - #address-cells = <1>; - #size-cells = <0>; + fragment@2 { + target = <&spi0>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; - pitft: pitft@0{ - compatible = "multi-inno,mi0283qt"; - reg = <0>; - pinctrl-names = "default"; - pinctrl-0 = <&pitft_pins>; + pitft: pitft@0{ + compatible = "sitronix,st7789v"; + reg = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&pitft_pins>; + spi-max-frequency = <32000000>; + rotate = <270>; + width = <135>; + height = <240>; + buswidth = <8>; + dc-gpios = <&gpio 25 0>; + led-gpios = <&gpio 26 0>; + debug = <0>; + }; + }; + }; - spi-max-frequency = <32000000>; - rotation = <90>; - width = <136>; - height = <240>; - col_offset = <53>; - row_offset = <40>; - dc-gpios = <&gpio 25 0>; - backlight = <&backlight>; - }; - }; - }; - fragment@3 { - target-path = "/soc"; - __overlay__ { - backlight: backlight { - compatible = "gpio-backlight"; - gpios = <&gpio 22 0>; - }; - }; - }; - - __overrides__ { - speed = <&pitft>,"spi-max-frequency:0"; - rotation = <&pitft>,"rotation:0"; - width = <&pitft>,"width:0"; - height = <&pitft>,"height:0"; - col_offset = <&pitft>,"col_offset:0"; - row_offset = <&pitft>,"row_offset:0"; - }; + __overrides__ { + speed = <&pitft>,"spi-max-frequency:0"; + rotate = <&pitft>,"rotate:0"; + width = <&pitft>,"width:0"; + height = <&pitft>,"height:0"; + fps = <&pitft>,"fps:0"; + debug = <&pitft>,"debug:0"; + }; }; diff --git a/overlays/minipitft13-overlay.dts b/overlays/minipitft13-overlay.dts index 00b278b..0820151 100644 --- a/overlays/minipitft13-overlay.dts +++ b/overlays/minipitft13-overlay.dts @@ -43,7 +43,7 @@ #size-cells = <0>; pitft: pitft@0{ - compatible = "sitronix,st7789v"; + compatible = "sitronix,st7789v"; reg = <0>; pinctrl-names = "default"; pinctrl-0 = <&pitft_pins>; diff --git a/st7789_module/Makefile b/st7789_module/Makefile index cc8e020..0f7575d 100644 --- a/st7789_module/Makefile +++ b/st7789_module/Makefile @@ -1,15 +1,13 @@ -obj-m += st7789v_ada.o obj-m += fb_st7789v.o KDIR ?= /lib/modules/`uname -r`/build default: - $(MAKE) -C $(KDIR) M=$(pwd) modules + $(MAKE) -C $(KDIR) M=$(shell pwd) modules install: - $(MAKE) -C $(KDIR) M=$(PWD) modules_install + $(MAKE) -C $(KDIR) M=$(shell pwd) modules_install $(DEPMOD) clean: - $(MAKE) -C $(KDIR) M=$(PWD) clean - + $(MAKE) -C $(KDIR) M=$(shell pwd) clean diff --git a/st7789_module/fb_st7789v.c b/st7789_module/fb_st7789v.c index 5ac7f7e..9156067 100644 --- a/st7789_module/fb_st7789v.c +++ b/st7789_module/fb_st7789v.c @@ -16,31 +16,16 @@ #define DRVNAME "fb_st7789v" -static unsigned int width; -module_param(width, uint, 0000); -MODULE_PARM_DESC(width, "Display width"); - -static unsigned int height; -module_param(height, uint, 0000); -MODULE_PARM_DESC(height, "Display height"); - -static u32 col_offset = 0; -static u32 row_offset = 0; -static u8 col_hack_fix_offset = 0; -static short x_offset = 0; -static short y_offset = 0; - -#define ST77XX_MADCTL_MY 0x80 -#define ST77XX_MADCTL_MX 0x40 -#define ST77XX_MADCTL_MV 0x20 -#define ST77XX_MADCTL_ML 0x10 -#define ST77XX_MADCTL_BGR 0x08 -#define ST77XX_MADCTL_RGB 0x00 - #define DEFAULT_GAMMA \ "70 2C 2E 15 10 09 48 33 53 0B 19 18 20 25\n" \ "70 2C 2E 15 10 09 48 33 53 0B 19 18 20 25" +#define HSD20_IPS_GAMMA \ + "D0 05 0A 09 08 05 2E 44 45 0F 17 16 2B 33\n" \ + "D0 05 0A 09 08 05 2E 43 45 0F 16 16 2B 33" + +#define HSD20_IPS 1 + /** * enum st7789v_command - ST7789V display controller commands * @@ -76,6 +61,18 @@ enum st7789v_command { NVGAMCTRL = 0xE1, }; +#define MADCTL_BGR BIT(3) /* bitmask for RGB/BGR order */ +#define MADCTL_MV BIT(5) /* bitmask for page/column order */ +#define MADCTL_MX BIT(6) /* bitmask for column address order */ +#define MADCTL_MY BIT(7) /* bitmask for page address order */ + +static u32 col1_offset = 0; +static u32 col2_offset = 0; +static u32 row1_offset = 0; +static u32 row2_offset = 0; +static short x_offset = 0; +static short y_offset = 0; + /** * init_display() - initialize the display controller * @@ -92,44 +89,85 @@ enum st7789v_command { */ static int init_display(struct fbtft_par *par) { - printk(KERN_INFO "ST7789 adafruit fbtft driver\n"); - - width = par->info->var.xres; - height = par->info->var.yres; - - if ((width == 0) || (width > 240)) { - width = 240; - } - if ((height == 0) || (height > 320)) { - height = 320; - } - printk(KERN_INFO "Size: (%d, %d)\n", width, height); - - // Go to sleep - write_reg(par, MIPI_DCS_SET_DISPLAY_OFF); - // Soft reset - write_reg(par, MIPI_DCS_SOFT_RESET); - mdelay(150); - /* turn off sleep mode */ write_reg(par, MIPI_DCS_EXIT_SLEEP_MODE); - mdelay(10); + mdelay(120); /* set pixel format to RGB-565 */ write_reg(par, MIPI_DCS_SET_PIXEL_FORMAT, MIPI_DCS_PIXEL_FMT_16BIT); + if (HSD20_IPS) + write_reg(par, PORCTRL, 0x05, 0x05, 0x00, 0x33, 0x33); - write_reg(par, MIPI_DCS_SET_ADDRESS_MODE, 0); - write_reg(par, MIPI_DCS_SET_COLUMN_ADDRESS, 0, 0, 0, 240); - write_reg(par, MIPI_DCS_SET_PAGE_ADDRESS, 0, 0, 320>>8, 320&0xFF); - write_reg(par, MIPI_DCS_ENTER_INVERT_MODE); // odd hack, displays are inverted - write_reg(par, MIPI_DCS_ENTER_NORMAL_MODE); - mdelay(10); + else + write_reg(par, PORCTRL, 0x08, 0x08, 0x00, 0x22, 0x22); + /* + * VGH = 13.26V + * VGL = -10.43V + */ + if (HSD20_IPS) + write_reg(par, GCTRL, 0x75); + else + write_reg(par, GCTRL, 0x35); + + /* + * VDV and VRH register values come from command write + * (instead of NVM) + */ + write_reg(par, VDVVRHEN, 0x01, 0xFF); + + /* + * VAP = 4.1V + (VCOM + VCOM offset + 0.5 * VDV) + * VAN = -4.1V + (VCOM + VCOM offset + 0.5 * VDV) + */ + if (HSD20_IPS) + write_reg(par, VRHS, 0x13); + else + write_reg(par, VRHS, 0x0B); + + /* VDV = 0V */ + write_reg(par, VDVS, 0x20); + + /* VCOM = 0.9V */ + if (HSD20_IPS) + write_reg(par, VCOMS, 0x22); + else + write_reg(par, VCOMS, 0x20); + + /* VCOM offset = 0V */ + write_reg(par, VCMOFSET, 0x20); + + /* + * AVDD = 6.8V + * AVCL = -4.8V + * VDS = 2.3V + */ + write_reg(par, PWCTRL1, 0xA4, 0xA1); write_reg(par, MIPI_DCS_SET_DISPLAY_ON); + + if (HSD20_IPS) + write_reg(par, MIPI_DCS_ENTER_INVERT_MODE); + return 0; } +static void minipitft_set_addr_win(struct fbtft_par *par, int xs, int ys, + int xe, int ye) +{ + xs += x_offset; + xe += x_offset; + ys += y_offset; + ye += y_offset; + write_reg(par, MIPI_DCS_SET_COLUMN_ADDRESS, + xs >> 8, xs & 0xFF, xe >> 8, xe & 0xFF); + + write_reg(par, MIPI_DCS_SET_PAGE_ADDRESS, + ys >> 8, ys & 0xFF, ye >> 8, ye & 0xFF); + + write_reg(par, MIPI_DCS_WRITE_MEMORY_START); +} + /** * set_var() - apply LCD properties like rotation and BGR mode * @@ -139,36 +177,104 @@ static int init_display(struct fbtft_par *par) */ static int set_var(struct fbtft_par *par) { - u8 addr_mode = 0; + u8 madctl_par = 0; + struct fbtft_display *display = &par->pdata->display; + u32 width = display->width; + u32 height = display->height; + if (par->bgr) + madctl_par |= MADCTL_BGR; + + if (width < 240) { + // Display is centered + row1_offset = row2_offset = (int)((320 - height + 1) / 2); + col1_offset = (int)((240 - width) / 2); + col2_offset = (int)((240 - width + 1) / 2); + } else { + row1_offset = 0; + row2_offset = (320 - height); + col1_offset = col2_offset = (240 - width); + } switch (par->info->var.rotate) { case 0: - addr_mode = 0; - x_offset = col_offset; - y_offset = row_offset; + x_offset = col1_offset; + y_offset = row1_offset; break; case 90: - addr_mode = ST77XX_MADCTL_MV | ST77XX_MADCTL_MX; - x_offset = row_offset; - y_offset = col_offset; - break; + madctl_par |= (MADCTL_MV | MADCTL_MY); + x_offset = row1_offset; + y_offset = col1_offset; + break; case 180: - addr_mode = ST77XX_MADCTL_MX | ST77XX_MADCTL_MY; - x_offset = (240 - width) - col_offset + col_hack_fix_offset; - // hack tweak to account for extra pixel width to make even - y_offset = (320 - height) - row_offset; + madctl_par |= (MADCTL_MX | MADCTL_MY); + x_offset = col2_offset; + y_offset = row2_offset; break; case 270: - addr_mode = ST77XX_MADCTL_MV | ST77XX_MADCTL_MY; - x_offset = (320 - height) - row_offset; - y_offset = (240 - width) - col_offset; + madctl_par |= (MADCTL_MV | MADCTL_MX); + x_offset = row2_offset; + y_offset = col2_offset; break; default: return -EINVAL; } - printk(KERN_INFO "Rotation %d offsets %d %d\n", par->info->var.rotate, x_offset, y_offset); + write_reg(par, MIPI_DCS_SET_ADDRESS_MODE, madctl_par); + return 0; +} - write_reg(par, MIPI_DCS_SET_ADDRESS_MODE, addr_mode); +/** + * set_gamma() - set gamma curves + * + * @par: FBTFT parameter object + * @curves: gamma curves + * + * Before the gamma curves are applied, they are preprocessed with a bitmask + * to ensure syntactically correct input for the display controller. + * This implies that the curves input parameter might be changed by this + * function and that illegal gamma values are auto-corrected and not + * reported as errors. + * + * Return: 0 on success, < 0 if error occurred. + */ +static int set_gamma(struct fbtft_par *par, u32 *curves) +{ + int i; + int j; + int c; /* curve index offset */ + + /* + * Bitmasks for gamma curve command parameters. + * The masks are the same for both positive and negative voltage + * gamma curves. + */ + static const u8 gamma_par_mask[] = { + 0xFF, /* V63[3:0], V0[3:0]*/ + 0x3F, /* V1[5:0] */ + 0x3F, /* V2[5:0] */ + 0x1F, /* V4[4:0] */ + 0x1F, /* V6[4:0] */ + 0x3F, /* J0[1:0], V13[3:0] */ + 0x7F, /* V20[6:0] */ + 0x77, /* V36[2:0], V27[2:0] */ + 0x7F, /* V43[6:0] */ + 0x3F, /* J1[1:0], V50[3:0] */ + 0x1F, /* V57[4:0] */ + 0x1F, /* V59[4:0] */ + 0x3F, /* V61[5:0] */ + 0x3F, /* V62[5:0] */ + }; + + for (i = 0; i < par->gamma.num_curves; i++) { + c = i * par->gamma.num_values; + for (j = 0; j < par->gamma.num_values; j++) + curves[c + j] &= gamma_par_mask[j]; + write_reg(par, PVGAMCTRL + i, + curves[c + 0], curves[c + 1], curves[c + 2], + curves[c + 3], curves[c + 4], curves[c + 5], + curves[c + 6], curves[c + 7], curves[c + 8], + curves[c + 9], curves[c + 10], curves[c + 11], + curves[c + 12], curves[c + 13]); + } return 0; } @@ -195,11 +301,13 @@ static struct fbtft_display display = { .height = 320, .gamma_num = 2, .gamma_len = 14, - .gamma = DEFAULT_GAMMA, + .gamma = HSD20_IPS_GAMMA, .fbtftops = { .init_display = init_display, .set_var = set_var, + .set_gamma = set_gamma, .blank = blank, + .set_addr_win = minipitft_set_addr_win, }, }; diff --git a/st7789_module/fbtft.h b/st7789_module/fbtft.h index 9b6bdb6..f4f5ff0 100644 --- a/st7789_module/fbtft.h +++ b/st7789_module/fbtft.h @@ -251,7 +251,8 @@ void fbtft_register_backlight(struct fbtft_par *par); void fbtft_unregister_backlight(struct fbtft_par *par); int fbtft_init_display(struct fbtft_par *par); int fbtft_probe_common(struct fbtft_display *display, struct spi_device *sdev, - struct platform_device *pdev); + struct platform_device *pdev, + const struct of_device_id *dt_ids); int fbtft_remove_common(struct device *dev, struct fb_info *info); /* fbtft-io.c */ @@ -272,11 +273,13 @@ void fbtft_write_reg8_bus9(struct fbtft_par *par, int len, ...); void fbtft_write_reg16_bus8(struct fbtft_par *par, int len, ...); void fbtft_write_reg16_bus16(struct fbtft_par *par, int len, ...); -#define FBTFT_REGISTER_DRIVER(_name, _compatible, _display) \ +#define FBTFT_REGISTER_DRIVER_START(_display) \ + \ +static const struct of_device_id dt_ids[]; \ \ static int fbtft_driver_probe_spi(struct spi_device *spi) \ { \ - return fbtft_probe_common(_display, spi, NULL); \ + return fbtft_probe_common(_display, spi, NULL, dt_ids); \ } \ \ static int fbtft_driver_remove_spi(struct spi_device *spi) \ @@ -288,7 +291,7 @@ static int fbtft_driver_remove_spi(struct spi_device *spi) \ \ static int fbtft_driver_probe_pdev(struct platform_device *pdev) \ { \ - return fbtft_probe_common(_display, NULL, pdev); \ + return fbtft_probe_common(_display, NULL, pdev, dt_ids); \ } \ \ static int fbtft_driver_remove_pdev(struct platform_device *pdev) \ @@ -298,8 +301,16 @@ static int fbtft_driver_remove_pdev(struct platform_device *pdev) \ return fbtft_remove_common(&pdev->dev, info); \ } \ \ -static const struct of_device_id dt_ids[] = { \ - { .compatible = _compatible }, \ +static const struct of_device_id dt_ids[] = { + +#define FBTFT_COMPATIBLE(_compatible) \ + { .compatible = _compatible }, + +#define FBTFT_VARIANT_COMPATIBLE(_compatible, _variant) \ + { .compatible = _compatible, .data = _variant }, + +#define FBTFT_REGISTER_DRIVER_END(_name, _display) \ + \ {}, \ }; \ \ @@ -309,7 +320,7 @@ MODULE_DEVICE_TABLE(of, dt_ids); \ static struct spi_driver fbtft_driver_spi_driver = { \ .driver = { \ .name = _name, \ - .of_match_table = of_match_ptr(dt_ids), \ + .of_match_table = dt_ids, \ }, \ .probe = fbtft_driver_probe_spi, \ .remove = fbtft_driver_remove_spi, \ @@ -319,7 +330,7 @@ static struct platform_driver fbtft_driver_platform_driver = { \ .driver = { \ .name = _name, \ .owner = THIS_MODULE, \ - .of_match_table = of_match_ptr(dt_ids), \ + .of_match_table = dt_ids, \ }, \ .probe = fbtft_driver_probe_pdev, \ .remove = fbtft_driver_remove_pdev, \ @@ -344,13 +355,26 @@ static void __exit fbtft_driver_module_exit(void) \ module_init(fbtft_driver_module_init); \ module_exit(fbtft_driver_module_exit); +#define FBTFT_REGISTER_DRIVER(_name, _compatible, _display) \ + FBTFT_REGISTER_DRIVER_START(_display) \ + FBTFT_COMPATIBLE(_compatible) \ + FBTFT_REGISTER_DRIVER_END(_name, _display) + /* Debug macros */ /* shorthand debug levels */ #define DEBUG_LEVEL_1 DEBUG_REQUEST_GPIOS -#define DEBUG_LEVEL_2 (DEBUG_LEVEL_1 | DEBUG_DRIVER_INIT_FUNCTIONS | DEBUG_TIME_FIRST_UPDATE) -#define DEBUG_LEVEL_3 (DEBUG_LEVEL_2 | DEBUG_RESET | DEBUG_INIT_DISPLAY | DEBUG_BLANK | DEBUG_REQUEST_GPIOS | DEBUG_FREE_GPIOS | DEBUG_VERIFY_GPIOS | DEBUG_BACKLIGHT | DEBUG_SYSFS) -#define DEBUG_LEVEL_4 (DEBUG_LEVEL_2 | DEBUG_FB_READ | DEBUG_FB_WRITE | DEBUG_FB_FILLRECT | DEBUG_FB_COPYAREA | DEBUG_FB_IMAGEBLIT | DEBUG_FB_BLANK) +#define DEBUG_LEVEL_2 (DEBUG_LEVEL_1 | DEBUG_DRIVER_INIT_FUNCTIONS \ + | DEBUG_TIME_FIRST_UPDATE) +#define DEBUG_LEVEL_3 (DEBUG_LEVEL_2 | DEBUG_RESET | DEBUG_INIT_DISPLAY \ + | DEBUG_BLANK | DEBUG_REQUEST_GPIOS \ + | DEBUG_FREE_GPIOS \ + | DEBUG_VERIFY_GPIOS \ + | DEBUG_BACKLIGHT | DEBUG_SYSFS) +#define DEBUG_LEVEL_4 (DEBUG_LEVEL_2 | DEBUG_FB_READ | DEBUG_FB_WRITE \ + | DEBUG_FB_FILLRECT \ + | DEBUG_FB_COPYAREA \ + | DEBUG_FB_IMAGEBLIT | DEBUG_FB_BLANK) #define DEBUG_LEVEL_5 (DEBUG_LEVEL_3 | DEBUG_UPDATE_DISPLAY) #define DEBUG_LEVEL_6 (DEBUG_LEVEL_4 | DEBUG_LEVEL_5) #define DEBUG_LEVEL_7 0xFFFFFFFF @@ -398,8 +422,8 @@ do { \ #define fbtft_par_dbg(level, par, format, arg...) \ do { \ - if (unlikely(par->debug & level)) \ - dev_info(par->info->device, format, ##arg); \ + if (unlikely((par)->debug & (level))) \ + dev_info((par)->info->device, format, ##arg); \ } while (0) #define fbtft_par_dbg_hex(level, par, dev, type, buf, num, format, arg...) \ diff --git a/st7789_module/st7789v_ada.c b/st7789_module/st7789v_ada.c deleted file mode 100644 index 51c79d2..0000000 --- a/st7789_module/st7789v_ada.c +++ /dev/null @@ -1,432 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * DRM driver for ST7789V panels with flexible config - * - * Copyright 2021 Melissa LeBlanc-Williams - * Copyright 2019 Limor Fried - * Copyright 2016 Noralf Trønnes - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include