/* * Copyright (c) 2024 Felipe Neves * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT galaxycore_gc2145 #include #include #include #include #include #include #include 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 < RESOLUTIONS_MAX; 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 DEVICE_API(video, 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);