| /* |
| * Copyright (c) 2024 Felipe Neves |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT galaxycore_gc2145 |
| #include <zephyr/kernel.h> |
| #include <zephyr/device.h> |
| |
| #include <zephyr/drivers/video.h> |
| #include <zephyr/drivers/video-controls.h> |
| #include <zephyr/drivers/i2c.h> |
| #include <zephyr/drivers/gpio.h> |
| |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(video_gc2145, CONFIG_VIDEO_LOG_LEVEL); |
| |
| #define GC2145_REG_AMODE1 0x17 |
| #define GC2145_AMODE1_WINDOW_MASK 0xFC |
| #define GC2145_REG_AMODE1_DEF 0x14 |
| #define GC2145_REG_OUTPUT_FMT 0x84 |
| #define GC2145_REG_OUTPUT_FMT_RGB565 0x06 |
| #define GC2145_REG_SYNC_MODE 0x86 |
| #define GC2145_REG_SYNC_MODE_DEF 0x23 |
| #define GC2145_REG_SYNC_MODE_COL_SWITCH 0x10 |
| #define GC2145_REG_SYNC_MODE_ROW_SWITCH 0x20 |
| #define GC2145_REG_RESET 0xFE |
| #define GC2145_REG_SW_RESET 0x80 |
| #define GC2145_PID_VAL 0x21 |
| #define GC2145_REV_VAL 0x55 |
| #define GC2145_SET_P0_REGS 0x00 |
| #define GC2145_REG_CROP_ENABLE 0x90 |
| #define GC2145_CROP_SET_ENABLE 0x01 |
| #define GC2145_REG_BLANK_WINDOW_BASE 0x09 |
| #define GC2145_REG_WINDOW_BASE 0x91 |
| #define GC2145_REG_SUBSAMPLE 0x99 |
| #define GC2145_REG_SUBSAMPLE_MODE 0x9A |
| #define GC2145_SUBSAMPLE_MODE_SMOOTH 0x0E |
| |
| #define UXGA_HSIZE 1600 |
| #define UXGA_VSIZE 1200 |
| |
| struct gc2145_reg { |
| uint8_t addr; |
| uint8_t value; |
| }; |
| |
| static const struct gc2145_reg default_regs[] = { |
| {0xfe, 0xf0}, |
| {0xfe, 0xf0}, |
| {0xfe, 0xf0}, |
| {0xfc, 0x06}, |
| {0xf6, 0x00}, |
| {0xf7, 0x1d}, |
| {0xf8, 0x85}, |
| {0xfa, 0x00}, |
| {0xf9, 0xfe}, |
| {0xf2, 0x00}, |
| |
| /* ISP settings */ |
| {0xfe, 0x00}, |
| {0x03, 0x04}, |
| {0x04, 0xe2}, |
| |
| {0x09, 0x00}, |
| {0x0a, 0x00}, |
| |
| {0x0b, 0x00}, |
| {0x0c, 0x00}, |
| |
| {0x0d, 0x04}, /* Window height */ |
| {0x0e, 0xc0}, |
| |
| {0x0f, 0x06}, /* Window width */ |
| {0x10, 0x52}, |
| |
| {0x99, 0x11}, /* Subsample */ |
| {0x9a, 0x0E}, /* Subsample mode */ |
| |
| {0x12, 0x2e}, |
| {GC2145_REG_OUTPUT_FMT, 0x14}, /* Analog Mode 1 (vflip/mirror[1:0]) */ |
| {0x18, 0x22}, /* Analog Mode 2 */ |
| {0x19, 0x0e}, |
| {0x1a, 0x01}, |
| {0x1b, 0x4b}, |
| {0x1c, 0x07}, |
| {0x1d, 0x10}, |
| {0x1e, 0x88}, |
| {0x1f, 0x78}, |
| {0x20, 0x03}, |
| {0x21, 0x40}, |
| {0x22, 0xa0}, |
| {0x24, 0x16}, |
| {0x25, 0x01}, |
| {0x26, 0x10}, |
| {0x2d, 0x60}, |
| {0x30, 0x01}, |
| {0x31, 0x90}, |
| {0x33, 0x06}, |
| {0x34, 0x01}, |
| {0x80, 0x7f}, |
| {0x81, 0x26}, |
| {0x82, 0xfa}, |
| {0x83, 0x00}, |
| {GC2145_REG_OUTPUT_FMT, 0x06}, |
| {GC2145_REG_SYNC_MODE, 0x23}, |
| {0x88, 0x03}, |
| {0x89, 0x03}, |
| {0x85, 0x08}, |
| {0x8a, 0x00}, |
| {0x8b, 0x00}, |
| {0xb0, 0x55}, |
| {0xc3, 0x00}, |
| {0xc4, 0x80}, |
| {0xc5, 0x90}, |
| {0xc6, 0x3b}, |
| {0xc7, 0x46}, |
| {0xec, 0x06}, |
| {0xed, 0x04}, |
| {0xee, 0x60}, |
| {0xef, 0x90}, |
| {0xb6, 0x01}, |
| |
| {0x90, 0x01}, |
| {0x91, 0x00}, |
| {0x92, 0x00}, |
| {0x93, 0x00}, |
| {0x94, 0x00}, |
| {0x95, 0x02}, |
| {0x96, 0x58}, |
| {0x97, 0x03}, |
| {0x98, 0x20}, |
| {0x99, 0x22}, |
| {0x9a, 0x0E}, |
| |
| {0x9b, 0x00}, |
| {0x9c, 0x00}, |
| {0x9d, 0x00}, |
| {0x9e, 0x00}, |
| {0x9f, 0x00}, |
| {0xa0, 0x00}, |
| {0xa1, 0x00}, |
| {0xa2, 0x00}, |
| |
| /* BLK Settings */ |
| {0xfe, 0x00}, |
| {0x40, 0x42}, |
| {0x41, 0x00}, |
| {0x43, 0x5b}, |
| {0x5e, 0x00}, |
| {0x5f, 0x00}, |
| {0x60, 0x00}, |
| {0x61, 0x00}, |
| {0x62, 0x00}, |
| {0x63, 0x00}, |
| {0x64, 0x00}, |
| {0x65, 0x00}, |
| {0x66, 0x20}, |
| {0x67, 0x20}, |
| {0x68, 0x20}, |
| {0x69, 0x20}, |
| {0x76, 0x00}, |
| {0x6a, 0x08}, |
| {0x6b, 0x08}, |
| {0x6c, 0x08}, |
| {0x6d, 0x08}, |
| {0x6e, 0x08}, |
| {0x6f, 0x08}, |
| {0x70, 0x08}, |
| {0x71, 0x08}, |
| {0x76, 0x00}, |
| {0x72, 0xf0}, |
| {0x7e, 0x3c}, |
| {0x7f, 0x00}, |
| {0xfe, 0x02}, |
| {0x48, 0x15}, |
| {0x49, 0x00}, |
| {0x4b, 0x0b}, |
| {0xfe, 0x00}, |
| |
| /* AEC Settings */ |
| {0xfe, 0x01}, |
| {0x01, 0x04}, |
| {0x02, 0xc0}, |
| {0x03, 0x04}, |
| {0x04, 0x90}, |
| {0x05, 0x30}, |
| {0x06, 0x90}, |
| {0x07, 0x30}, |
| {0x08, 0x80}, |
| {0x09, 0x00}, |
| {0x0a, 0x82}, |
| {0x0b, 0x11}, |
| {0x0c, 0x10}, |
| {0x11, 0x10}, |
| {0x13, 0x68}, |
| {GC2145_REG_OUTPUT_FMT, 0x00}, |
| {0x1c, 0x11}, |
| {0x1e, 0x61}, |
| {0x1f, 0x35}, |
| {0x20, 0x40}, |
| {0x22, 0x40}, |
| {0x23, 0x20}, |
| {0xfe, 0x02}, |
| {0x0f, 0x04}, |
| {0xfe, 0x01}, |
| {0x12, 0x30}, |
| {0x15, 0xb0}, |
| {0x10, 0x31}, |
| {0x3e, 0x28}, |
| {0x3f, 0xb0}, |
| {0x40, 0x90}, |
| {0x41, 0x0f}, |
| {0xfe, 0x02}, |
| {0x90, 0x6c}, |
| {0x91, 0x03}, |
| {0x92, 0xcb}, |
| {0x94, 0x33}, |
| {0x95, 0x84}, |
| {0x97, 0x65}, |
| {0xa2, 0x11}, |
| {0xfe, 0x00}, |
| {0xfe, 0x02}, |
| {0x80, 0xc1}, |
| {0x81, 0x08}, |
| {0x82, 0x05}, |
| {0x83, 0x08}, |
| {GC2145_REG_OUTPUT_FMT, 0x0a}, |
| {GC2145_REG_SYNC_MODE, 0xf0}, |
| {0x87, 0x50}, |
| {0x88, 0x15}, |
| {0x89, 0xb0}, |
| {0x8a, 0x30}, |
| {0x8b, 0x10}, |
| {0xfe, 0x01}, |
| {0x21, 0x04}, |
| {0xfe, 0x02}, |
| {0xa3, 0x50}, |
| {0xa4, 0x20}, |
| {0xa5, 0x40}, |
| {0xa6, 0x80}, |
| {0xab, 0x40}, |
| {0xae, 0x0c}, |
| {0xb3, 0x46}, |
| {0xb4, 0x64}, |
| {0xb6, 0x38}, |
| {0xb7, 0x01}, |
| {0xb9, 0x2b}, |
| {0x3c, 0x04}, |
| {0x3d, 0x15}, |
| {0x4b, 0x06}, |
| {0x4c, 0x20}, |
| {0xfe, 0x00}, |
| |
| /* Gamma Control */ |
| {0xfe, 0x02}, |
| {0x10, 0x09}, |
| {0x11, 0x0d}, |
| {0x12, 0x13}, |
| {0x13, 0x19}, |
| {0x14, 0x27}, |
| {0x15, 0x37}, |
| {0x16, 0x45}, |
| {GC2145_REG_OUTPUT_FMT, 0x53}, |
| {0x18, 0x69}, |
| {0x19, 0x7d}, |
| {0x1a, 0x8f}, |
| {0x1b, 0x9d}, |
| {0x1c, 0xa9}, |
| {0x1d, 0xbd}, |
| {0x1e, 0xcd}, |
| {0x1f, 0xd9}, |
| {0x20, 0xe3}, |
| {0x21, 0xea}, |
| {0x22, 0xef}, |
| {0x23, 0xf5}, |
| {0x24, 0xf9}, |
| {0x25, 0xff}, |
| {0xfe, 0x00}, |
| {0xc6, 0x20}, |
| {0xc7, 0x2b}, |
| {0xfe, 0x02}, |
| {0x26, 0x0f}, |
| {0x27, 0x14}, |
| {0x28, 0x19}, |
| {0x29, 0x1e}, |
| {0x2a, 0x27}, |
| {0x2b, 0x33}, |
| {0x2c, 0x3b}, |
| {0x2d, 0x45}, |
| {0x2e, 0x59}, |
| {0x2f, 0x69}, |
| {0x30, 0x7c}, |
| {0x31, 0x89}, |
| {0x32, 0x98}, |
| {0x33, 0xae}, |
| {0x34, 0xc0}, |
| {0x35, 0xcf}, |
| {0x36, 0xda}, |
| {0x37, 0xe2}, |
| {0x38, 0xe9}, |
| {0x39, 0xf3}, |
| {0x3a, 0xf9}, |
| {0x3b, 0xff}, |
| {0xfe, 0x02}, |
| {0xd1, 0x32}, |
| {0xd2, 0x32}, |
| {0xd3, 0x40}, |
| {0xd6, 0xf0}, |
| {0xd7, 0x10}, |
| {0xd8, 0xda}, |
| {0xdd, 0x14}, |
| {0xde, 0x86}, |
| {0xed, 0x80}, |
| {0xee, 0x00}, |
| {0xef, 0x3f}, |
| {0xd8, 0xd8}, |
| {0xfe, 0x01}, |
| {0x9f, 0x40}, |
| {0xfe, 0x01}, |
| {0xc2, 0x14}, |
| {0xc3, 0x0d}, |
| {0xc4, 0x0c}, |
| {0xc8, 0x15}, |
| {0xc9, 0x0d}, |
| {0xca, 0x0a}, |
| {0xbc, 0x24}, |
| {0xbd, 0x10}, |
| {0xbe, 0x0b}, |
| {0xb6, 0x25}, |
| {0xb7, 0x16}, |
| {0xb8, 0x15}, |
| {0xc5, 0x00}, |
| {0xc6, 0x00}, |
| {0xc7, 0x00}, |
| {0xcb, 0x00}, |
| {0xcc, 0x00}, |
| {0xcd, 0x00}, |
| {0xbf, 0x07}, |
| {0xc0, 0x00}, |
| {0xc1, 0x00}, |
| {0xb9, 0x00}, |
| {0xba, 0x00}, |
| {0xbb, 0x00}, |
| {0xaa, 0x01}, |
| {0xab, 0x01}, |
| {0xac, 0x00}, |
| {0xad, 0x05}, |
| {0xae, 0x06}, |
| {0xaf, 0x0e}, |
| {0xb0, 0x0b}, |
| {0xb1, 0x07}, |
| {0xb2, 0x06}, |
| {0xb3, 0x17}, |
| {0xb4, 0x0e}, |
| {0xb5, 0x0e}, |
| {0xd0, 0x09}, |
| {0xd1, 0x00}, |
| {0xd2, 0x00}, |
| {0xd6, 0x08}, |
| {0xd7, 0x00}, |
| {0xd8, 0x00}, |
| {0xd9, 0x00}, |
| {0xda, 0x00}, |
| {0xdb, 0x00}, |
| {0xd3, 0x0a}, |
| {0xd4, 0x00}, |
| {0xd5, 0x00}, |
| {0xa4, 0x00}, |
| {0xa5, 0x00}, |
| {0xa6, 0x77}, |
| {0xa7, 0x77}, |
| {0xa8, 0x77}, |
| {0xa9, 0x77}, |
| {0xa1, 0x80}, |
| {0xa2, 0x80}, |
| |
| {0xfe, 0x01}, |
| {0xdf, 0x0d}, |
| {0xdc, 0x25}, |
| {0xdd, 0x30}, |
| {0xe0, 0x77}, |
| {0xe1, 0x80}, |
| {0xe2, 0x77}, |
| {0xe3, 0x90}, |
| {0xe6, 0x90}, |
| {0xe7, 0xa0}, |
| {0xe8, 0x90}, |
| {0xe9, 0xa0}, |
| {0xfe, 0x00}, |
| {0xfe, 0x01}, |
| {0x4f, 0x00}, |
| {0x4f, 0x00}, |
| {0x4b, 0x01}, |
| {0x4f, 0x00}, |
| |
| {0x4c, 0x01}, |
| {0x4d, 0x71}, |
| {0x4e, 0x01}, |
| {0x4c, 0x01}, |
| {0x4d, 0x91}, |
| {0x4e, 0x01}, |
| {0x4c, 0x01}, |
| {0x4d, 0x70}, |
| {0x4e, 0x01}, |
| {0x4c, 0x01}, |
| {0x4d, 0x90}, |
| {0x4e, 0x02}, |
| {0x4c, 0x01}, |
| {0x4d, 0xb0}, |
| {0x4e, 0x02}, |
| {0x4c, 0x01}, |
| {0x4d, 0x8f}, |
| {0x4e, 0x02}, |
| {0x4c, 0x01}, |
| {0x4d, 0x6f}, |
| {0x4e, 0x02}, |
| {0x4c, 0x01}, |
| {0x4d, 0xaf}, |
| {0x4e, 0x02}, |
| {0x4c, 0x01}, |
| {0x4d, 0xd0}, |
| {0x4e, 0x02}, |
| {0x4c, 0x01}, |
| {0x4d, 0xf0}, |
| {0x4e, 0x02}, |
| {0x4c, 0x01}, |
| {0x4d, 0xcf}, |
| {0x4e, 0x02}, |
| {0x4c, 0x01}, |
| {0x4d, 0xef}, |
| {0x4e, 0x02}, |
| {0x4c, 0x01}, |
| {0x4d, 0x6e}, |
| {0x4e, 0x03}, |
| {0x4c, 0x01}, |
| {0x4d, 0x8e}, |
| {0x4e, 0x03}, |
| {0x4c, 0x01}, |
| {0x4d, 0xae}, |
| {0x4e, 0x03}, |
| {0x4c, 0x01}, |
| {0x4d, 0xce}, |
| {0x4e, 0x03}, |
| {0x4c, 0x01}, |
| {0x4d, 0x4d}, |
| {0x4e, 0x03}, |
| {0x4c, 0x01}, |
| {0x4d, 0x6d}, |
| {0x4e, 0x03}, |
| {0x4c, 0x01}, |
| {0x4d, 0x8d}, |
| {0x4e, 0x03}, |
| {0x4c, 0x01}, |
| {0x4d, 0xad}, |
| {0x4e, 0x03}, |
| {0x4c, 0x01}, |
| {0x4d, 0xcd}, |
| {0x4e, 0x03}, |
| {0x4c, 0x01}, |
| {0x4d, 0x4c}, |
| {0x4e, 0x03}, |
| {0x4c, 0x01}, |
| {0x4d, 0x6c}, |
| {0x4e, 0x03}, |
| {0x4c, 0x01}, |
| {0x4d, 0x8c}, |
| {0x4e, 0x03}, |
| {0x4c, 0x01}, |
| {0x4d, 0xac}, |
| {0x4e, 0x03}, |
| {0x4c, 0x01}, |
| {0x4d, 0xcc}, |
| {0x4e, 0x03}, |
| {0x4c, 0x01}, |
| {0x4d, 0xcb}, |
| {0x4e, 0x03}, |
| {0x4c, 0x01}, |
| {0x4d, 0x4b}, |
| {0x4e, 0x03}, |
| {0x4c, 0x01}, |
| {0x4d, 0x6b}, |
| {0x4e, 0x03}, |
| {0x4c, 0x01}, |
| {0x4d, 0x8b}, |
| {0x4e, 0x03}, |
| {0x4c, 0x01}, |
| {0x4d, 0xab}, |
| {0x4e, 0x03}, |
| {0x4c, 0x01}, |
| {0x4d, 0x8a}, |
| {0x4e, 0x04}, |
| {0x4c, 0x01}, |
| {0x4d, 0xaa}, |
| {0x4e, 0x04}, |
| {0x4c, 0x01}, |
| {0x4d, 0xca}, |
| {0x4e, 0x04}, |
| {0x4c, 0x01}, |
| {0x4d, 0xca}, |
| {0x4e, 0x04}, |
| {0x4c, 0x01}, |
| {0x4d, 0xc9}, |
| {0x4e, 0x04}, |
| {0x4c, 0x01}, |
| {0x4d, 0x8a}, |
| {0x4e, 0x04}, |
| {0x4c, 0x01}, |
| {0x4d, 0x89}, |
| {0x4e, 0x04}, |
| {0x4c, 0x01}, |
| {0x4d, 0xa9}, |
| {0x4e, 0x04}, |
| {0x4c, 0x02}, |
| {0x4d, 0x0b}, |
| {0x4e, 0x05}, |
| {0x4c, 0x02}, |
| {0x4d, 0x0a}, |
| {0x4e, 0x05}, |
| {0x4c, 0x01}, |
| {0x4d, 0xeb}, |
| {0x4e, 0x05}, |
| {0x4c, 0x01}, |
| {0x4d, 0xea}, |
| {0x4e, 0x05}, |
| {0x4c, 0x02}, |
| {0x4d, 0x09}, |
| {0x4e, 0x05}, |
| {0x4c, 0x02}, |
| {0x4d, 0x29}, |
| {0x4e, 0x05}, |
| {0x4c, 0x02}, |
| {0x4d, 0x2a}, |
| {0x4e, 0x05}, |
| {0x4c, 0x02}, |
| {0x4d, 0x4a}, |
| {0x4e, 0x05}, |
| {0x4c, 0x02}, |
| {0x4d, 0x8a}, |
| {0x4e, 0x06}, |
| {0x4c, 0x02}, |
| {0x4d, 0x49}, |
| {0x4e, 0x06}, |
| {0x4c, 0x02}, |
| {0x4d, 0x69}, |
| {0x4e, 0x06}, |
| {0x4c, 0x02}, |
| {0x4d, 0x89}, |
| {0x4e, 0x06}, |
| {0x4c, 0x02}, |
| {0x4d, 0xa9}, |
| {0x4e, 0x06}, |
| {0x4c, 0x02}, |
| {0x4d, 0x48}, |
| {0x4e, 0x06}, |
| {0x4c, 0x02}, |
| {0x4d, 0x68}, |
| {0x4e, 0x06}, |
| {0x4c, 0x02}, |
| {0x4d, 0x69}, |
| {0x4e, 0x06}, |
| {0x4c, 0x02}, |
| {0x4d, 0xca}, |
| {0x4e, 0x07}, |
| {0x4c, 0x02}, |
| {0x4d, 0xc9}, |
| {0x4e, 0x07}, |
| {0x4c, 0x02}, |
| {0x4d, 0xe9}, |
| {0x4e, 0x07}, |
| {0x4c, 0x03}, |
| {0x4d, 0x09}, |
| {0x4e, 0x07}, |
| {0x4c, 0x02}, |
| {0x4d, 0xc8}, |
| {0x4e, 0x07}, |
| {0x4c, 0x02}, |
| {0x4d, 0xe8}, |
| {0x4e, 0x07}, |
| {0x4c, 0x02}, |
| {0x4d, 0xa7}, |
| {0x4e, 0x07}, |
| {0x4c, 0x02}, |
| {0x4d, 0xc7}, |
| {0x4e, 0x07}, |
| {0x4c, 0x02}, |
| {0x4d, 0xe7}, |
| {0x4e, 0x07}, |
| {0x4c, 0x03}, |
| {0x4d, 0x07}, |
| {0x4e, 0x07}, |
| |
| {0x4f, 0x01}, |
| {0x50, 0x80}, |
| {0x51, 0xa8}, |
| {0x52, 0x47}, |
| {0x53, 0x38}, |
| {0x54, 0xc7}, |
| {0x56, 0x0e}, |
| {0x58, 0x08}, |
| {0x5b, 0x00}, |
| {0x5c, 0x74}, |
| {0x5d, 0x8b}, |
| {0x61, 0xdb}, |
| {0x62, 0xb8}, |
| {0x63, 0x86}, |
| {0x64, 0xc0}, |
| {0x65, 0x04}, |
| {0x67, 0xa8}, |
| {0x68, 0xb0}, |
| {0x69, 0x00}, |
| {0x6a, 0xa8}, |
| {0x6b, 0xb0}, |
| {0x6c, 0xaf}, |
| {0x6d, 0x8b}, |
| {0x6e, 0x50}, |
| {0x6f, 0x18}, |
| {0x73, 0xf0}, |
| {0x70, 0x0d}, |
| {0x71, 0x60}, |
| {0x72, 0x80}, |
| {0x74, 0x01}, |
| {0x75, 0x01}, |
| {0x7f, 0x0c}, |
| {0x76, 0x70}, |
| {0x77, 0x58}, |
| {0x78, 0xa0}, |
| {0x79, 0x5e}, |
| {0x7a, 0x54}, |
| {0x7b, 0x58}, |
| {0xfe, 0x00}, |
| {0xfe, 0x02}, |
| {0xc0, 0x01}, |
| {0xc1, 0x44}, |
| {0xc2, 0xfd}, |
| {0xc3, 0x04}, |
| {0xc4, 0xF0}, |
| {0xc5, 0x48}, |
| {0xc6, 0xfd}, |
| {0xc7, 0x46}, |
| {0xc8, 0xfd}, |
| {0xc9, 0x02}, |
| {0xca, 0xe0}, |
| {0xcb, 0x45}, |
| {0xcc, 0xec}, |
| {0xcd, 0x48}, |
| {0xce, 0xf0}, |
| {0xcf, 0xf0}, |
| {0xe3, 0x0c}, |
| {0xe4, 0x4b}, |
| {0xe5, 0xe0}, |
| {0xfe, 0x01}, |
| {0x9f, 0x40}, |
| {0xfe, 0x00}, |
| |
| /* Output Control */ |
| {0xfe, 0x00}, |
| {0xf2, 0x0f}, |
| {0xfe, 0x02}, |
| {0x40, 0xbf}, |
| {0x46, 0xcf}, |
| {0xfe, 0x00}, |
| |
| {0xfe, 0x00}, |
| {0x05, 0x01}, |
| {0x06, 0x1C}, |
| {0x07, 0x00}, |
| {0x08, 0x32}, |
| {0x11, 0x00}, |
| {0x12, 0x1D}, |
| {0x13, 0x00}, |
| {0x14, 0x00}, |
| |
| {0xfe, 0x01}, |
| {0x3c, 0x00}, |
| {0x3d, 0x04}, |
| {0xfe, 0x00}, |
| {0x00, 0x00}, |
| }; |
| |
| struct gc2145_config { |
| struct i2c_dt_spec i2c; |
| #if DT_INST_NODE_HAS_PROP(0, reset_gpios) |
| struct gpio_dt_spec reset_gpio; |
| #endif |
| }; |
| |
| struct gc2145_data { |
| struct video_format fmt; |
| }; |
| |
| #define GC2145_VIDEO_FORMAT_CAP(width, height, format) \ |
| { \ |
| .pixelformat = format, .width_min = width, .width_max = width, \ |
| .height_min = height, .height_max = height, .width_step = 0, .height_step = 0, \ |
| } |
| |
| enum resolutions { |
| QVGA_RESOLUTION = 0, |
| VGA_RESOLUTION, |
| UXGA_RESOLUTION, |
| RESOLUTIONS_MAX, |
| }; |
| |
| static const struct video_format_cap fmts[] = { |
| [QVGA_RESOLUTION] = GC2145_VIDEO_FORMAT_CAP(320, 240, VIDEO_PIX_FMT_RGB565), /* QVGA */ |
| [VGA_RESOLUTION] = GC2145_VIDEO_FORMAT_CAP(640, 480, VIDEO_PIX_FMT_RGB565), /* VGA */ |
| [UXGA_RESOLUTION] = GC2145_VIDEO_FORMAT_CAP(1600, 1200, VIDEO_PIX_FMT_RGB565), /* UXGA */ |
| [RESOLUTIONS_MAX] = {0}, |
| }; |
| |
| static int gc2145_write_reg(const struct i2c_dt_spec *spec, uint8_t reg_addr, uint8_t value) |
| { |
| int ret; |
| uint8_t tries = 3; |
| |
| /* |
| * It rarely happens that the camera does not respond with ACK signal. |
| * In that case it usually responds on 2nd try but there is a 3rd one |
| * just to be sure that the connection error is not caused by driver |
| * itself. |
| */ |
| do { |
| ret = i2c_reg_write_byte_dt(spec, reg_addr, value); |
| if (!ret) { |
| return 0; |
| } |
| /* If writing failed wait 5ms before next attempt */ |
| k_msleep(5); |
| |
| } while (tries--); |
| |
| LOG_ERR("failed to write 0x%x to 0x%x,", value, reg_addr); |
| return ret; |
| } |
| |
| static int gc2145_read_reg(const struct i2c_dt_spec *spec, uint8_t reg_addr, uint8_t *value) |
| { |
| int ret; |
| uint8_t tries = 3; |
| |
| /* |
| * It rarely happens that the camera does not respond with ACK signal. |
| * In that case it usually responds on 2nd try but there is a 3rd one |
| * just to be sure that the connection error is not caused by driver |
| * itself. |
| */ |
| do { |
| ret = i2c_reg_read_byte_dt(spec, reg_addr, value); |
| if (!ret) { |
| return 0; |
| } |
| /* If writing failed wait 5ms before next attempt */ |
| k_msleep(5); |
| |
| } while (tries--); |
| |
| LOG_ERR("failed to read 0x%x register", reg_addr); |
| return ret; |
| } |
| |
| static int gc2145_write_all(const struct device *dev, const struct gc2145_reg *regs, |
| uint16_t reg_num) |
| { |
| const struct gc2145_config *cfg = dev->config; |
| |
| for (uint16_t i = 0; i < reg_num; i++) { |
| int ret; |
| |
| ret = gc2145_write_reg(&cfg->i2c, regs[i].addr, regs[i].value); |
| if (ret < 0) { |
| return ret; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int gc2145_soft_reset(const struct device *dev) |
| { |
| int ret; |
| const struct gc2145_config *cfg = dev->config; |
| |
| /* Initiate system reset */ |
| ret = gc2145_write_reg(&cfg->i2c, GC2145_REG_RESET, GC2145_REG_SW_RESET); |
| |
| k_msleep(300); |
| |
| return ret; |
| } |
| |
| static int gc2145_set_ctrl_vflip(const struct device *dev, bool enable) |
| { |
| int ret; |
| const struct gc2145_config *cfg = dev->config; |
| uint8_t old_value; |
| |
| ret = gc2145_read_reg(&cfg->i2c, GC2145_REG_AMODE1, &old_value); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| /* Set the vertical flip state */ |
| return gc2145_write_reg(&cfg->i2c, GC2145_REG_AMODE1, |
| (old_value & GC2145_AMODE1_WINDOW_MASK) | (enable << 1)); |
| } |
| |
| static int gc2145_set_ctrl_hmirror(const struct device *dev, bool enable) |
| { |
| int ret; |
| const struct gc2145_config *cfg = dev->config; |
| uint8_t old_value; |
| |
| ret = gc2145_read_reg(&cfg->i2c, GC2145_REG_AMODE1, &old_value); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| /* Set the horizontal mirror state */ |
| return gc2145_write_reg(&cfg->i2c, GC2145_REG_AMODE1, |
| (old_value & GC2145_AMODE1_WINDOW_MASK) | enable); |
| } |
| |
| static int gc2145_set_window(const struct device *dev, uint16_t reg, uint16_t x, uint16_t y, |
| uint16_t w, uint16_t h) |
| { |
| int ret; |
| const struct gc2145_config *cfg = dev->config; |
| |
| ret = gc2145_write_reg(&cfg->i2c, GC2145_REG_RESET, GC2145_SET_P0_REGS); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| /* Y/row offset */ |
| ret = gc2145_write_reg(&cfg->i2c, reg++, y >> 8); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| ret = gc2145_write_reg(&cfg->i2c, reg++, y & 0xff); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| /* X/col offset */ |
| ret = gc2145_write_reg(&cfg->i2c, reg++, x >> 8); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| ret = gc2145_write_reg(&cfg->i2c, reg++, x & 0xff); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| /* Window height */ |
| ret = gc2145_write_reg(&cfg->i2c, reg++, h >> 8); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| ret = gc2145_write_reg(&cfg->i2c, reg++, h & 0xff); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| /* Window width */ |
| ret = gc2145_write_reg(&cfg->i2c, reg++, w >> 8); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| ret = gc2145_write_reg(&cfg->i2c, reg++, w & 0xff); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int gc2145_set_output_format(const struct device *dev, int output_format) |
| { |
| int ret; |
| const struct gc2145_config *cfg = dev->config; |
| |
| ret = gc2145_write_reg(&cfg->i2c, GC2145_REG_RESET, GC2145_SET_P0_REGS); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| if (output_format != VIDEO_PIX_FMT_RGB565) { |
| LOG_ERR("Image format not supported"); |
| return -ENOTSUP; |
| } |
| |
| /* Disable JPEG compression and set output to RGB565 */ |
| ret = gc2145_write_reg(&cfg->i2c, GC2145_REG_OUTPUT_FMT, GC2145_REG_OUTPUT_FMT_RGB565); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| k_sleep(K_MSEC(30)); |
| |
| return 0; |
| } |
| |
| static int gc2145_set_resolution(const struct device *dev, enum resolutions res) |
| { |
| int ret; |
| const struct gc2145_config *cfg = dev->config; |
| |
| uint16_t w; |
| uint16_t h; |
| uint16_t win_w; |
| uint16_t win_h; |
| uint16_t c_ratio; |
| uint16_t r_ratio; |
| uint16_t x; |
| uint16_t y; |
| uint16_t win_x; |
| uint16_t win_y; |
| |
| if (res >= RESOLUTIONS_MAX) { |
| return -EIO; |
| } |
| |
| w = fmts[res].width_min; |
| h = fmts[res].height_min; |
| |
| /* Add the subsampling factor depending on resolution */ |
| switch (res) { |
| case QVGA_RESOLUTION: |
| c_ratio = 3; |
| r_ratio = 3; |
| break; |
| case VGA_RESOLUTION: |
| c_ratio = 2; |
| r_ratio = 2; |
| break; |
| case UXGA_RESOLUTION: |
| c_ratio = 1; |
| r_ratio = 1; |
| break; |
| default: |
| LOG_ERR("Unsupported resolution %d", res); |
| return -EIO; |
| }; |
| |
| /* Calculates the window boundaries to obtain the desired resolution */ |
| win_w = w * c_ratio; |
| win_h = h * r_ratio; |
| x = (((win_w / c_ratio) - w) / 2); |
| y = (((win_h / r_ratio) - h) / 2); |
| win_x = ((UXGA_HSIZE - win_w) / 2); |
| win_y = ((UXGA_VSIZE - win_h) / 2); |
| |
| /* Set readout window first. */ |
| ret = gc2145_set_window(dev, GC2145_REG_BLANK_WINDOW_BASE, win_x, win_y, win_w + 16, |
| win_h + 8); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| /* Set cropping window next. */ |
| ret = gc2145_set_window(dev, GC2145_REG_WINDOW_BASE, x, y, w, h); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| /* Enable crop */ |
| ret = gc2145_write_reg(&cfg->i2c, GC2145_REG_CROP_ENABLE, GC2145_CROP_SET_ENABLE); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| /* Set Sub-sampling ratio and mode */ |
| ret = gc2145_write_reg(&cfg->i2c, GC2145_REG_SUBSAMPLE, ((r_ratio << 4) | c_ratio)); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| ret = gc2145_write_reg(&cfg->i2c, GC2145_REG_SUBSAMPLE_MODE, GC2145_SUBSAMPLE_MODE_SMOOTH); |
| if (ret < 0) { |
| return ret; |
| } |
| /* |
| * Galaxy Core datasheet does not document the reason behind of this |
| * and other short delay requirements, but the reason exposed by them |
| * is to give enough time for the sensor DSP to handle the I2C transaction |
| * give some time time to apply the changes before the next instruction. |
| */ |
| k_sleep(K_MSEC(30)); |
| |
| return 0; |
| } |
| |
| static uint8_t gc2145_check_connection(const struct device *dev) |
| { |
| int ret; |
| const struct gc2145_config *cfg = dev->config; |
| uint8_t reg_pid_val; |
| uint8_t reg_ver_val; |
| |
| ret = gc2145_read_reg(&cfg->i2c, 0xf0, ®_pid_val); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| ret = gc2145_read_reg(&cfg->i2c, 0xf1, ®_ver_val); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| if ((reg_ver_val != GC2145_REV_VAL) || (reg_pid_val != GC2145_PID_VAL)) { |
| LOG_WRN("Unexpected GC2145 pid: 0x%x or rev: 0x%x", reg_pid_val, reg_ver_val); |
| } |
| |
| return 0; |
| } |
| |
| static int gc2145_set_fmt(const struct device *dev, enum video_endpoint_id ep, |
| struct video_format *fmt) |
| { |
| struct gc2145_data *drv_data = dev->data; |
| enum resolutions res = RESOLUTIONS_MAX; |
| int ret; |
| |
| /* We only support RGB565 formats */ |
| if (fmt->pixelformat != VIDEO_PIX_FMT_RGB565) { |
| LOG_ERR("gc2145 camera supports only RGB565"); |
| return -ENOTSUP; |
| } |
| |
| if (memcmp(&drv_data->fmt, fmt, sizeof(drv_data->fmt)) == 0) { |
| /* nothing to do */ |
| return 0; |
| } |
| |
| /* Check if camera is capable of handling given format */ |
| for (int i = 0; i == ARRAY_SIZE(fmts); i++) { |
| if (fmts[i].width_min == fmt->width && fmts[i].height_min == fmt->height && |
| fmts[i].pixelformat == fmt->pixelformat) { |
| res = (enum resolutions)i; |
| break; |
| } |
| } |
| if (res == RESOLUTIONS_MAX) { |
| LOG_ERR("Image format not supported"); |
| return -ENOTSUP; |
| } |
| |
| drv_data->fmt = *fmt; |
| |
| /* Set output format */ |
| ret = gc2145_set_output_format(dev, fmt->pixelformat); |
| if (ret < 0) { |
| LOG_ERR("Failed to set the output format"); |
| return ret; |
| } |
| |
| /* Set window size */ |
| ret = gc2145_set_resolution(dev, res); |
| if (ret < 0) { |
| LOG_ERR("Failed to set the resolution"); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int gc2145_get_fmt(const struct device *dev, enum video_endpoint_id ep, |
| struct video_format *fmt) |
| { |
| struct gc2145_data *drv_data = dev->data; |
| |
| *fmt = drv_data->fmt; |
| |
| return 0; |
| } |
| |
| static int gc2145_stream_start(const struct device *dev) |
| { |
| const struct gc2145_config *cfg = dev->config; |
| |
| return gc2145_write_reg(&cfg->i2c, 0xf2, 0x0f); |
| } |
| |
| static int gc2145_stream_stop(const struct device *dev) |
| { |
| const struct gc2145_config *cfg = dev->config; |
| |
| return gc2145_write_reg(&cfg->i2c, 0xf2, 0x00); |
| } |
| |
| static int gc2145_get_caps(const struct device *dev, enum video_endpoint_id ep, |
| struct video_caps *caps) |
| { |
| caps->format_caps = fmts; |
| return 0; |
| } |
| |
| static int gc2145_set_ctrl(const struct device *dev, unsigned int cid, void *value) |
| { |
| switch (cid) { |
| case VIDEO_CID_HFLIP: |
| return gc2145_set_ctrl_hmirror(dev, (int)value); |
| case VIDEO_CID_VFLIP: |
| return gc2145_set_ctrl_vflip(dev, (int)value); |
| default: |
| return -ENOTSUP; |
| } |
| } |
| |
| static const struct video_driver_api gc2145_driver_api = { |
| .set_format = gc2145_set_fmt, |
| .get_format = gc2145_get_fmt, |
| .get_caps = gc2145_get_caps, |
| .stream_start = gc2145_stream_start, |
| .stream_stop = gc2145_stream_stop, |
| .set_ctrl = gc2145_set_ctrl, |
| }; |
| |
| static int gc2145_init(const struct device *dev) |
| { |
| struct video_format fmt; |
| int ret; |
| |
| #if DT_INST_NODE_HAS_PROP(0, reset_gpios) |
| const struct gc2145_config *cfg = dev->config; |
| |
| ret = gpio_pin_configure_dt(&cfg->reset_gpio, GPIO_OUTPUT_ACTIVE); |
| if (ret) { |
| return ret; |
| } |
| |
| k_sleep(K_MSEC(1)); |
| gpio_pin_set_dt(&cfg->reset_gpio, 0); |
| k_sleep(K_MSEC(1)); |
| #endif |
| |
| ret = gc2145_check_connection(dev); |
| if (ret) { |
| return ret; |
| } |
| |
| gc2145_soft_reset(dev); |
| gc2145_write_all(dev, default_regs, ARRAY_SIZE(default_regs)); |
| |
| /* set default/init format QVGA RGB565 */ |
| fmt.pixelformat = VIDEO_PIX_FMT_RGB565; |
| fmt.width = fmts[QVGA_RESOLUTION].width_min; |
| fmt.height = fmts[QVGA_RESOLUTION].height_min; |
| fmt.pitch = fmts[QVGA_RESOLUTION].width_min * 2; |
| |
| ret = gc2145_set_fmt(dev, VIDEO_EP_OUT, &fmt); |
| if (ret) { |
| LOG_ERR("Unable to configure default format"); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| /* Unique Instance */ |
| static const struct gc2145_config gc2145_cfg_0 = { |
| .i2c = I2C_DT_SPEC_INST_GET(0), |
| #if DT_INST_NODE_HAS_PROP(0, reset_gpios) |
| .reset_gpio = GPIO_DT_SPEC_INST_GET(0, reset_gpios), |
| #endif |
| }; |
| static struct gc2145_data gc2145_data_0; |
| |
| static int gc2145_init_0(const struct device *dev) |
| { |
| const struct gc2145_config *cfg = dev->config; |
| |
| if (!i2c_is_ready_dt(&cfg->i2c)) { |
| LOG_ERR("Bus device is not ready"); |
| return -ENODEV; |
| } |
| |
| #if DT_INST_NODE_HAS_PROP(0, reset_gpios) |
| if (!gpio_is_ready_dt(&cfg->reset_gpio)) { |
| LOG_ERR("%s: device %s is not ready", dev->name, cfg->reset_gpio.port->name); |
| return -ENODEV; |
| } |
| #endif |
| |
| return gc2145_init(dev); |
| } |
| |
| DEVICE_DT_INST_DEFINE(0, &gc2145_init_0, NULL, &gc2145_data_0, &gc2145_cfg_0, POST_KERNEL, |
| CONFIG_VIDEO_INIT_PRIORITY, &gc2145_driver_api); |