├── .codespellrc ├── .github ├── dependabot.yml └── workflows │ ├── check-arduino.yml │ ├── compile-examples.yml │ ├── report-size-deltas.yml │ ├── spell-check.yml │ └── sync-labels.yml ├── LICENSE ├── README.adoc ├── examples ├── CameraCapture │ └── CameraCapture.ino ├── CameraCaptureRawBytes │ └── CameraCaptureRawBytes.ino ├── CameraTestPattern │ └── CameraTestPattern.ino └── ConnectionTest │ └── ConnectionTest.ino ├── extras └── CameraVisualizerRawBytes │ └── CameraVisualizerRawBytes.pde ├── keywords.txt ├── library.properties └── src ├── Arduino_OV767X.h ├── OV767X.cpp ├── OV767X.h └── utility ├── ov7670.c ├── ov7670.h └── ov7670_arduino_shim.cpp /.codespellrc: -------------------------------------------------------------------------------- 1 | # See: https://github.com/codespell-project/codespell#using-a-config-file 2 | [codespell] 3 | # In the event of a false positive, add the problematic word, in all lowercase, to a comma-separated list here: 4 | ignore-words-list = , 5 | check-filenames = 6 | check-hidden = 7 | skip = ./.git,./src/utility 8 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # See: https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates#about-the-dependabotyml-file 2 | version: 2 3 | 4 | updates: 5 | # Configure check for outdated GitHub Actions actions in workflows. 6 | # See: https://docs.github.com/en/github/administering-a-repository/keeping-your-actions-up-to-date-with-dependabot 7 | - package-ecosystem: github-actions 8 | directory: / # Check the repository's workflows under /.github/workflows/ 9 | schedule: 10 | interval: daily 11 | labels: 12 | - "topic: infrastructure" 13 | -------------------------------------------------------------------------------- /.github/workflows/check-arduino.yml: -------------------------------------------------------------------------------- 1 | name: Check Arduino 2 | 3 | # See: https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows 4 | on: 5 | push: 6 | pull_request: 7 | schedule: 8 | # Run every Tuesday at 8 AM UTC to catch breakage caused by new rules added to Arduino Lint. 9 | - cron: "0 8 * * TUE" 10 | workflow_dispatch: 11 | repository_dispatch: 12 | 13 | jobs: 14 | lint: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Checkout repository 19 | uses: actions/checkout@v4 20 | 21 | - name: Arduino Lint 22 | uses: arduino/arduino-lint-action@v2 23 | with: 24 | compliance: strict 25 | library-manager: update 26 | # Always use this setting for official repositories. Remove for 3rd party projects. 27 | official: true 28 | project-type: library 29 | -------------------------------------------------------------------------------- /.github/workflows/compile-examples.yml: -------------------------------------------------------------------------------- 1 | name: Compile Examples 2 | 3 | # See: https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows 4 | on: 5 | push: 6 | paths: 7 | - ".github/workflows/compile-examples.yml" 8 | - "examples/**" 9 | - "src/**" 10 | pull_request: 11 | paths: 12 | - ".github/workflows/compile-examples.yml" 13 | - "examples/**" 14 | - "src/**" 15 | schedule: 16 | # Run every Tuesday at 8 AM UTC to catch breakage caused by changes to external resources (libraries, platforms). 17 | - cron: "0 8 * * TUE" 18 | workflow_dispatch: 19 | repository_dispatch: 20 | 21 | jobs: 22 | build: 23 | name: ${{ matrix.board.fqbn }} 24 | runs-on: ubuntu-latest 25 | 26 | env: 27 | SKETCHES_REPORTS_PATH: sketches-reports 28 | 29 | strategy: 30 | fail-fast: false 31 | 32 | matrix: 33 | board: 34 | - fqbn: arduino:mbed_nano:nano33ble 35 | platforms: | 36 | - name: arduino:mbed_nano 37 | artifact-name-suffix: arduino-mbed_nano-nano33ble 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v4 42 | 43 | - name: Compile examples 44 | uses: arduino/compile-sketches@v1 45 | with: 46 | github-token: ${{ secrets.GITHUB_TOKEN }} 47 | fqbn: ${{ matrix.board.fqbn }} 48 | platforms: ${{ matrix.board.platforms }} 49 | libraries: | 50 | # Install the library from the local path. 51 | - source-path: ./ 52 | - name: Arduino_CRC32 53 | sketch-paths: | 54 | - examples 55 | enable-deltas-report: true 56 | sketches-report-path: ${{ env.SKETCHES_REPORTS_PATH }} 57 | 58 | - name: Save sketches report as workflow artifact 59 | uses: actions/upload-artifact@v4 60 | with: 61 | if-no-files-found: error 62 | path: ${{ env.SKETCHES_REPORTS_PATH }} 63 | name: sketches-report-${{ matrix.board.artifact-name-suffix }} 64 | -------------------------------------------------------------------------------- /.github/workflows/report-size-deltas.yml: -------------------------------------------------------------------------------- 1 | name: Report Size Deltas 2 | 3 | # See: https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows 4 | on: 5 | push: 6 | paths: 7 | - ".github/workflows/report-size-deltas.yml" 8 | schedule: 9 | # Run at the minimum interval allowed by GitHub Actions. 10 | # Note: GitHub Actions periodically has outages which result in workflow failures. 11 | # In this event, the workflows will start passing again once the service recovers. 12 | - cron: "*/5 * * * *" 13 | workflow_dispatch: 14 | repository_dispatch: 15 | 16 | jobs: 17 | report: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Comment size deltas reports to PRs 21 | uses: arduino/report-size-deltas@v1 22 | with: 23 | # Regex matching the names of the workflow artifacts created by the "Compile Examples" workflow 24 | sketches-reports-source: ^sketches-report-.+ 25 | -------------------------------------------------------------------------------- /.github/workflows/spell-check.yml: -------------------------------------------------------------------------------- 1 | name: Spell Check 2 | 3 | # See: https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows 4 | on: 5 | push: 6 | pull_request: 7 | schedule: 8 | # Run every Tuesday at 8 AM UTC to catch new misspelling detections resulting from dictionary updates. 9 | - cron: "0 8 * * TUE" 10 | workflow_dispatch: 11 | repository_dispatch: 12 | 13 | jobs: 14 | spellcheck: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Checkout repository 19 | uses: actions/checkout@v4 20 | 21 | - name: Spell check 22 | uses: codespell-project/actions-codespell@master 23 | -------------------------------------------------------------------------------- /.github/workflows/sync-labels.yml: -------------------------------------------------------------------------------- 1 | # Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/sync-labels.md 2 | name: Sync Labels 3 | 4 | # See: https://docs.github.com/en/actions/reference/events-that-trigger-workflows 5 | on: 6 | push: 7 | paths: 8 | - ".github/workflows/sync-labels.ya?ml" 9 | - ".github/label-configuration-files/*.ya?ml" 10 | pull_request: 11 | paths: 12 | - ".github/workflows/sync-labels.ya?ml" 13 | - ".github/label-configuration-files/*.ya?ml" 14 | schedule: 15 | # Run daily at 8 AM UTC to sync with changes to shared label configurations. 16 | - cron: "0 8 * * *" 17 | workflow_dispatch: 18 | repository_dispatch: 19 | 20 | env: 21 | CONFIGURATIONS_FOLDER: .github/label-configuration-files 22 | CONFIGURATIONS_ARTIFACT: label-configuration-files 23 | 24 | jobs: 25 | check: 26 | runs-on: ubuntu-latest 27 | 28 | steps: 29 | - name: Checkout repository 30 | uses: actions/checkout@v4 31 | 32 | - name: Download JSON schema for labels configuration file 33 | id: download-schema 34 | uses: carlosperate/download-file-action@v2 35 | with: 36 | file-url: https://raw.githubusercontent.com/arduino/tooling-project-assets/main/workflow-templates/assets/sync-labels/arduino-tooling-gh-label-configuration-schema.json 37 | location: ${{ runner.temp }}/label-configuration-schema 38 | 39 | - name: Install JSON schema validator 40 | run: | 41 | sudo npm install \ 42 | --global \ 43 | ajv-cli \ 44 | ajv-formats 45 | 46 | - name: Validate local labels configuration 47 | run: | 48 | # See: https://github.com/ajv-validator/ajv-cli#readme 49 | ajv validate \ 50 | --all-errors \ 51 | -c ajv-formats \ 52 | -s "${{ steps.download-schema.outputs.file-path }}" \ 53 | -d "${{ env.CONFIGURATIONS_FOLDER }}/*.{yml,yaml}" 54 | 55 | download: 56 | needs: check 57 | runs-on: ubuntu-latest 58 | 59 | strategy: 60 | matrix: 61 | filename: 62 | # Filenames of the shared configurations to apply to the repository in addition to the local configuration. 63 | # https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/sync-labels 64 | - universal.yml 65 | 66 | steps: 67 | - name: Download 68 | uses: carlosperate/download-file-action@v2 69 | with: 70 | file-url: https://raw.githubusercontent.com/arduino/tooling-project-assets/main/workflow-templates/assets/sync-labels/${{ matrix.filename }} 71 | 72 | - name: Pass configuration files to next job via workflow artifact 73 | uses: actions/upload-artifact@v4 74 | with: 75 | path: | 76 | *.yaml 77 | *.yml 78 | if-no-files-found: error 79 | name: ${{ env.CONFIGURATIONS_ARTIFACT }} 80 | 81 | sync: 82 | needs: download 83 | runs-on: ubuntu-latest 84 | 85 | steps: 86 | - name: Set environment variables 87 | run: | 88 | # See: https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable 89 | echo "MERGED_CONFIGURATION_PATH=${{ runner.temp }}/labels.yml" >> "$GITHUB_ENV" 90 | 91 | - name: Determine whether to dry run 92 | id: dry-run 93 | if: > 94 | github.event_name == 'pull_request' || 95 | ( 96 | ( 97 | github.event_name == 'push' || 98 | github.event_name == 'workflow_dispatch' 99 | ) && 100 | github.ref != format('refs/heads/{0}', github.event.repository.default_branch) 101 | ) 102 | run: | 103 | # Use of this flag in the github-label-sync command will cause it to only check the validity of the 104 | # configuration. 105 | echo "::set-output name=flag::--dry-run" 106 | 107 | - name: Checkout repository 108 | uses: actions/checkout@v4 109 | 110 | - name: Download configuration files artifact 111 | uses: actions/download-artifact@v4 112 | with: 113 | name: ${{ env.CONFIGURATIONS_ARTIFACT }} 114 | path: ${{ env.CONFIGURATIONS_FOLDER }} 115 | 116 | - name: Remove unneeded artifact 117 | uses: geekyeggo/delete-artifact@v5 118 | with: 119 | name: ${{ env.CONFIGURATIONS_ARTIFACT }} 120 | 121 | - name: Merge label configuration files 122 | run: | 123 | # Merge all configuration files 124 | shopt -s extglob 125 | cat "${{ env.CONFIGURATIONS_FOLDER }}"/*.@(yml|yaml) > "${{ env.MERGED_CONFIGURATION_PATH }}" 126 | 127 | - name: Install github-label-sync 128 | run: sudo npm install --global github-label-sync 129 | 130 | - name: Sync labels 131 | env: 132 | GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} 133 | run: | 134 | # See: https://github.com/Financial-Times/github-label-sync 135 | github-label-sync \ 136 | --labels "${{ env.MERGED_CONFIGURATION_PATH }}" \ 137 | ${{ steps.dry-run.outputs.flag }} \ 138 | ${{ github.repository }} 139 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | :repository-owner: arduino-libraries 2 | :repository-name: Arduino_OV767X 3 | 4 | = {repository-name} Library for Arduino = 5 | 6 | image:https://github.com/{repository-owner}/{repository-name}/actions/workflows/check-arduino.yml/badge.svg["Check Arduino status", link="https://github.com/{repository-owner}/{repository-name}/actions/workflows/check-arduino.yml"] 7 | image:https://github.com/{repository-owner}/{repository-name}/actions/workflows/compile-examples.yml/badge.svg["Compile Examples status", link="https://github.com/{repository-owner}/{repository-name}/actions/workflows/compile-examples.yml"] 8 | image:https://github.com/{repository-owner}/{repository-name}/actions/workflows/spell-check.yml/badge.svg["Spell Check status", link="https://github.com/{repository-owner}/{repository-name}/actions/workflows/spell-check.yml"] 9 | 10 | Capture images from your OmniVision OV7670 camera in your Arduino sketches. 11 | 12 | This library is based on https://www.kernel.org[Linux Kernel's] V4L2 driver for OmniVision OV7670 cameras - which was created by Jonathan Corbet. 13 | 14 | == License == 15 | 16 | Copyright (c) 2021 Arduino SA. All rights reserved. 17 | 18 | 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; version 2. 19 | 20 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 21 | 22 | You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 23 | -------------------------------------------------------------------------------- /examples/CameraCapture/CameraCapture.ino: -------------------------------------------------------------------------------- 1 | /* 2 | OV767X - Camera Test Pattern 3 | 4 | This sketch waits for the letter 'c' on the Serial Monitor, 5 | it then reads a frame from the OmniVision OV7670 camera and 6 | prints the data to the Serial Monitor as a hex string. 7 | 8 | The website https://rawpixels.net - can be used the visualize the data: 9 | width: 176 10 | height: 144 11 | RGB565 12 | Little Endian 13 | 14 | Circuit: 15 | - Arduino Nano 33 BLE board 16 | - OV7670 camera module: 17 | - 3.3 connected to 3.3 18 | - GND connected GND 19 | - SIOC connected to A5 20 | - SIOD connected to A4 21 | - VSYNC connected to 8 22 | - HREF connected to A1 23 | - PCLK connected to A0 24 | - XCLK connected to 9 25 | - D7 connected to 4 26 | - D6 connected to 6 27 | - D5 connected to 5 28 | - D4 connected to 3 29 | - D3 connected to 2 30 | - D2 connected to 0 / RX 31 | - D1 connected to 1 / TX 32 | - D0 connected to 10 33 | 34 | This example code is in the public domain. 35 | */ 36 | 37 | #include 38 | 39 | unsigned short pixels[176 * 144]; // QCIF: 176x144 X 2 bytes per pixel (RGB565) 40 | 41 | void setup() { 42 | Serial.begin(9600); 43 | while (!Serial); 44 | 45 | Serial.println("OV767X Camera Capture"); 46 | Serial.println(); 47 | 48 | if (!Camera.begin(QCIF, RGB565, 1)) { 49 | Serial.println("Failed to initialize camera!"); 50 | while (1); 51 | } 52 | 53 | Serial.println("Camera settings:"); 54 | Serial.print("\twidth = "); 55 | Serial.println(Camera.width()); 56 | Serial.print("\theight = "); 57 | Serial.println(Camera.height()); 58 | Serial.print("\tbits per pixel = "); 59 | Serial.println(Camera.bitsPerPixel()); 60 | Serial.println(); 61 | 62 | Serial.println("Send the 'c' character to read a frame ..."); 63 | Serial.println(); 64 | } 65 | 66 | void loop() { 67 | if (Serial.read() == 'c') { 68 | Serial.println("Reading frame"); 69 | Serial.println(); 70 | Camera.readFrame(pixels); 71 | 72 | int numPixels = Camera.width() * Camera.height(); 73 | 74 | for (int i = 0; i < numPixels; i++) { 75 | unsigned short p = pixels[i]; 76 | 77 | if (p < 0x1000) { 78 | Serial.print('0'); 79 | } 80 | 81 | if (p < 0x0100) { 82 | Serial.print('0'); 83 | } 84 | 85 | if (p < 0x0010) { 86 | Serial.print('0'); 87 | } 88 | 89 | Serial.print(p, HEX); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /examples/CameraCaptureRawBytes/CameraCaptureRawBytes.ino: -------------------------------------------------------------------------------- 1 | /* 2 | OV767X - Camera Capture Raw Bytes 3 | 4 | This sketch reads a frame from the OmniVision OV7670 camera 5 | and writes the bytes to the Serial port. Use the Processing 6 | sketch in the extras folder to visualize the camera output. 7 | 8 | Circuit: 9 | - Arduino Nano 33 BLE board 10 | - OV7670 camera module: 11 | - 3.3 connected to 3.3 12 | - GND connected GND 13 | - SIOC connected to A5 14 | - SIOD connected to A4 15 | - VSYNC connected to 8 16 | - HREF connected to A1 17 | - PCLK connected to A0 18 | - XCLK connected to 9 19 | - D7 connected to 4 20 | - D6 connected to 6 21 | - D5 connected to 5 22 | - D4 connected to 3 23 | - D3 connected to 2 24 | - D2 connected to 0 / RX 25 | - D1 connected to 1 / TX 26 | - D0 connected to 10 27 | 28 | This example code is in the public domain. 29 | */ 30 | 31 | #include 32 | 33 | int bytesPerFrame; 34 | 35 | byte data[320 * 240 * 2]; // QVGA: 320x240 X 2 bytes per pixel (RGB565) 36 | 37 | void setup() { 38 | Serial.begin(9600); 39 | while (!Serial); 40 | 41 | if (!Camera.begin(QVGA, RGB565, 1)) { 42 | Serial.println("Failed to initialize camera!"); 43 | while (1); 44 | } 45 | 46 | bytesPerFrame = Camera.width() * Camera.height() * Camera.bytesPerPixel(); 47 | 48 | // Optionally, enable the test pattern for testing 49 | // Camera.testPattern(); 50 | } 51 | 52 | void loop() { 53 | Camera.readFrame(data); 54 | 55 | Serial.write(data, bytesPerFrame); 56 | } 57 | -------------------------------------------------------------------------------- /examples/CameraTestPattern/CameraTestPattern.ino: -------------------------------------------------------------------------------- 1 | /* 2 | OV767X - Camera Test Pattern 3 | 4 | This sketch enables the test pattern mode, then reads a frame from 5 | the OmniVision OV7670 camera and prints the data to the 6 | Serial Monitor as a hex string. 7 | 8 | The website https://rawpixels.net - can be used the visualize the data: 9 | width: 176 10 | height: 144 11 | RGB565 12 | Little Endian 13 | 14 | Circuit: 15 | - Arduino Nano 33 BLE board 16 | - OV7670 camera module: 17 | - 3.3 connected to 3.3 18 | - GND connected GND 19 | - SIOC connected to A5 20 | - SIOD connected to A4 21 | - VSYNC connected to 8 22 | - HREF connected to A1 23 | - PCLK connected to A0 24 | - XCLK connected to 9 25 | - D7 connected to 4 26 | - D6 connected to 6 27 | - D5 connected to 5 28 | - D4 connected to 3 29 | - D3 connected to 2 30 | - D2 connected to 0 / RX 31 | - D1 connected to 1 / TX 32 | - D0 connected to 10 33 | 34 | This example code is in the public domain. 35 | */ 36 | 37 | #include 38 | 39 | unsigned short pixels[176 * 144]; // QCIF: 176x144 X 2 bytes per pixel (RGB565) 40 | 41 | void setup() { 42 | Serial.begin(9600); 43 | while (!Serial); 44 | 45 | Serial.println("OV767X Test Pattern"); 46 | Serial.println(); 47 | 48 | if (!Camera.begin(QCIF, RGB565, 1)) { 49 | Serial.println("Failed to initialize camera!"); 50 | while (1); 51 | } 52 | 53 | Serial.println("Camera settings:"); 54 | Serial.print("\twidth = "); 55 | Serial.println(Camera.width()); 56 | Serial.print("\theight = "); 57 | Serial.println(Camera.height()); 58 | Serial.print("\tbits per pixel = "); 59 | Serial.println(Camera.bitsPerPixel()); 60 | Serial.println(); 61 | 62 | Serial.println("Enabling test pattern mode"); 63 | Serial.println(); 64 | Camera.testPattern(); 65 | 66 | Serial.println("Reading frame"); 67 | Serial.println(); 68 | Camera.readFrame(pixels); 69 | 70 | int numPixels = Camera.width() * Camera.height(); 71 | 72 | for (int i = 0; i < numPixels; i++) { 73 | unsigned short p = pixels[i]; 74 | 75 | if (p < 0x1000) { 76 | Serial.print('0'); 77 | } 78 | 79 | if (p < 0x0100) { 80 | Serial.print('0'); 81 | } 82 | 83 | if (p < 0x0010) { 84 | Serial.print('0'); 85 | } 86 | 87 | Serial.print(p, HEX); 88 | } 89 | 90 | Serial.println(); 91 | } 92 | 93 | void loop() { 94 | // do nothing 95 | } 96 | -------------------------------------------------------------------------------- /examples/ConnectionTest/ConnectionTest.ino: -------------------------------------------------------------------------------- 1 | /* 2 | OV767X - ConnectionTest.ino 3 | 4 | Test that the connection between your Arduino and Camera is able to transfer data correctly at the given speed 5 | 6 | Circuit: 7 | - Arduino Nano 33 BLE board 8 | - OV7670 camera module: 9 | - 3.3 connected to 3.3 10 | - GND connected GND 11 | - SIOC connected to A5 12 | - SIOD connected to A4 13 | - VSYNC connected to 8 14 | - HREF connected to A1 15 | - PCLK connected to A0 16 | - XCLK connected to 9 17 | - D7 connected to 4 18 | - D6 connected to 6 19 | - D5 connected to 5 20 | - D4 connected to 3 21 | - D3 connected to 2 22 | - D2 connected to 0 / RX 23 | - D1 connected to 1 / TX 24 | - D0 connected to 10 25 | 26 | This example code is in the public domain. 27 | 28 | */ 29 | 30 | #include 31 | #include 32 | 33 | int bytesPerFrame; 34 | int errors = 0; 35 | int count = 0; 36 | int bestTime = 100000; 37 | int worstTime = 0; 38 | int delayTime = 300; 39 | 40 | 41 | long timer = 0; 42 | Arduino_CRC32 crc32; 43 | 44 | const bool error_checking = true; 45 | 46 | byte data[176 * 144 * 2]; // QCIF at 2 bytes per pixel 47 | 48 | void setup() { 49 | Serial.begin(9600); 50 | while (!Serial); 51 | 52 | if (!Camera.begin(QCIF, RGB565, 5)) { 53 | Serial.println("Failed to initialize camera!"); 54 | while (1); 55 | } 56 | 57 | bytesPerFrame = Camera.width() * Camera.height() * Camera.bytesPerPixel(); 58 | 59 | // Enable the test pattern so we have a fixed image to run a checksum against 60 | Camera.testPattern(); 61 | } 62 | 63 | void loop() { 64 | 65 | // sliding delay window to try different start times wrt camera VSYNC 66 | if (delayTime>0) {delayTime=delayTime-10;} 67 | delay(delayTime); 68 | 69 | // benchmarking 70 | timer = millis(); 71 | Camera.readFrame(data); 72 | timer = millis() - timer; 73 | // Check if it is a best case or worse case time 74 | bestTime = min(timer, bestTime); 75 | worstTime = max(timer, worstTime); 76 | 77 | // Test against known checksum values (minor pixel variations at the start but were visually confirmed to be a good test pattern) 78 | uint32_t const crc32_res = crc32.calc(data, bytesPerFrame); 79 | if (crc32_res != 0x15AB2939 && crc32_res != 0xD3EC95E && crc32_res != 0xB9C43ED9) { 80 | errors++; 81 | }; 82 | 83 | count++; 84 | 85 | Serial.print(" errors:"); 86 | Serial.print(errors); 87 | Serial.print("/"); 88 | Serial.print(count); 89 | Serial.print(" best:"); 90 | Serial.print(bestTime); 91 | Serial.print("ms worst:"); 92 | Serial.print(worstTime); 93 | Serial.println("ms"); 94 | 95 | } 96 | -------------------------------------------------------------------------------- /extras/CameraVisualizerRawBytes/CameraVisualizerRawBytes.pde: -------------------------------------------------------------------------------- 1 | /* 2 | This sketch reads a raw Stream of RGB565 pixels 3 | from the Serial port and displays the frame on 4 | the window. 5 | 6 | Use with the Examples -> CameraCaptureRawBytes Arduino sketch. 7 | 8 | This example code is in the public domain. 9 | */ 10 | 11 | import processing.serial.*; 12 | import java.nio.ByteBuffer; 13 | import java.nio.ByteOrder; 14 | 15 | Serial myPort; 16 | 17 | // must match resolution used in the sketch 18 | final int cameraWidth = 320; 19 | final int cameraHeight = 240; 20 | final int cameraBytesPerPixel = 2; 21 | final int bytesPerFrame = cameraWidth * cameraHeight * cameraBytesPerPixel; 22 | 23 | PImage myImage; 24 | byte[] frameBuffer = new byte[bytesPerFrame]; 25 | 26 | void setup() 27 | { 28 | size(320, 240); 29 | 30 | // if you have only ONE serial port active 31 | //myPort = new Serial(this, Serial.list()[0], 9600); // if you have only ONE serial port active 32 | 33 | // if you know the serial port name 34 | //myPort = new Serial(this, "COM5", 9600); // Windows 35 | //myPort = new Serial(this, "/dev/ttyACM0", 9600); // Linux 36 | myPort = new Serial(this, "/dev/cu.usbmodem14401", 9600); // Mac 37 | 38 | // wait for full frame of bytes 39 | myPort.buffer(bytesPerFrame); 40 | 41 | myImage = createImage(cameraWidth, cameraHeight, RGB); 42 | } 43 | 44 | void draw() 45 | { 46 | image(myImage, 0, 0); 47 | } 48 | 49 | void serialEvent(Serial myPort) { 50 | // read the saw bytes in 51 | myPort.readBytes(frameBuffer); 52 | 53 | // access raw bytes via byte buffer 54 | ByteBuffer bb = ByteBuffer.wrap(frameBuffer); 55 | bb.order(ByteOrder.BIG_ENDIAN); 56 | 57 | int i = 0; 58 | 59 | while (bb.hasRemaining()) { 60 | // read 16-bit pixel 61 | short p = bb.getShort(); 62 | 63 | // convert RGB565 to RGB 24-bit 64 | int r = ((p >> 11) & 0x1f) << 3; 65 | int g = ((p >> 5) & 0x3f) << 2; 66 | int b = ((p >> 0) & 0x1f) << 3; 67 | 68 | // set pixel color 69 | myImage .pixels[i++] = color(r, g, b); 70 | } 71 | myImage .updatePixels(); 72 | 73 | } 74 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ########################################## 2 | # Syntax Coloring Map For Arduino_OV767X 3 | ########################################## 4 | # Class 5 | ####################################### 6 | 7 | Arduino_OV767X KEYWORD1 8 | 9 | Camera KEYWORD1 10 | 11 | ####################################### 12 | # Methods and Functions 13 | ####################################### 14 | 15 | begin KEYWORD2 16 | end KEYWORD2 17 | 18 | width KEYWORD2 19 | height KEYWORD2 20 | bitsPerPixel KEYWORD2 21 | bytesPerPixel KEYWORD2 22 | 23 | readFrame KEYWORD2 24 | 25 | testPattern KEYWORD2 26 | noTestPattern KEYWORD2 27 | 28 | setSaturation KEYWORD2 29 | setHue KEYWORD2 30 | setBrightness KEYWORD2 31 | setContrast KEYWORD2 32 | horizontalFlip KEYWORD2 33 | noHorizontalFlip KEYWORD2 34 | verticalFlip KEYWORD2 35 | noVerticalFlip KEYWORD2 36 | setGain KEYWORD2 37 | autoGain KEYWORD2 38 | setExposure KEYWORD2 39 | autoExposure KEYWORD2 40 | 41 | setPins KEYWORD2 42 | 43 | ####################################### 44 | # Constants 45 | ####################################### 46 | 47 | YUV422 LITERAL1 48 | RGB444 LITERAL1 49 | RGB565 LITERAL1 50 | SBGGR8 LITERAL1 51 | GRAYSCALE LITERAL 1 52 | 53 | VGA LITERAL1 54 | CIF LITERAL1 55 | QVGA LITERAL1 56 | QQVGA LITERAL1 57 | QCIF LITERAL1 58 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=Arduino_OV767X 2 | version=0.0.2 3 | author=Arduino 4 | maintainer=Arduino 5 | sentence=Capture images from your OmniVision OV7670 camera in your Arduino sketches. 6 | paragraph= 7 | category=Sensors 8 | url=http://github.com/arduino-libraries/Arduino_OV767X 9 | architectures=mbed 10 | includes=Arduino_OV767X.h 11 | -------------------------------------------------------------------------------- /src/Arduino_OV767X.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | /* 3 | * This file is part of the Arduino_OX767X library. 4 | * Copyright (c) 2020 Arduino SA. All rights reserved. 5 | */ 6 | 7 | #ifndef _ARUDINO_OV767X_H_ 8 | #define _ARUDINO_OV767X_H_ 9 | 10 | #include "OV767X.h" 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /src/OV767X.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | /* 3 | * This file is part of the Arduino_OX767X library. 4 | * Copyright (c) 2020 Arduino SA. All rights reserved. 5 | */ 6 | 7 | #include 8 | #include 9 | 10 | #include "OV767X.h" 11 | 12 | // if not defined in the variant 13 | #ifndef digitalPinToBitMask 14 | #define digitalPinToBitMask(P) (1 << (digitalPinToPinName(P) % 32)) 15 | #endif 16 | 17 | #ifndef portInputRegister 18 | #define portInputRegister(P) ((P == 0) ? &NRF_P0->IN : &NRF_P1->IN) 19 | #endif 20 | 21 | extern "C" { 22 | // defined in utility/ov7670.c: 23 | struct ov7670_fract { 24 | uint32_t numerator; 25 | uint32_t denominator; 26 | }; 27 | 28 | void* ov7670_alloc(); 29 | void ov7670_free(void*); 30 | 31 | int ov7670_reset(void*, uint32_t val); 32 | int ov7670_detect(void*); 33 | void ov7670_configure(void*, int devtype, int format, int wsize, int clock_speed, int pll_bypass, int pclk_hb_disable); 34 | int ov7670_s_power(void*, int on); 35 | int ov7675_set_framerate(void*, struct ov7670_fract *tpf); 36 | 37 | int ov7670_s_sat_hue(void*, int sat, int hue); 38 | int ov7670_s_brightness(void*, int value); 39 | int ov7670_s_contrast(void*, int value); 40 | int ov7670_s_hflip(void*, int value); 41 | int ov7670_s_vflip(void*, int value); 42 | int ov7670_s_gain(void*, int value); 43 | int ov7670_s_autogain(void*, int value); 44 | int ov7670_s_exp(void*, int value); 45 | int ov7670_s_autoexp(void*, int value); 46 | int ov7670_s_test_pattern(void*, int value); 47 | }; 48 | 49 | const int OV760_D[8] = { 50 | OV7670_D0, OV7670_D1, OV7670_D2, OV7670_D3, OV7670_D4, OV7670_D5, OV7670_D6, OV7670_D7 51 | }; 52 | 53 | OV767X::OV767X() : 54 | _ov7670(NULL), 55 | _saturation(128), 56 | _hue(0) 57 | { 58 | setPins(OV7670_VSYNC, OV7670_HREF, OV7670_PLK, OV7670_XCLK, OV760_D); 59 | } 60 | 61 | OV767X::~OV767X() 62 | { 63 | if (_ov7670) { 64 | ov7670_free(_ov7670); 65 | } 66 | } 67 | 68 | int OV767X::begin(int resolution, int format, int fps, int camera_name) 69 | { 70 | switch (resolution) { 71 | case VGA: 72 | _width = 640; 73 | _height = 480; 74 | break; 75 | 76 | case CIF: 77 | _width = 352; 78 | _height = 240; 79 | break; 80 | 81 | case QVGA: 82 | _width = 320; 83 | _height = 240; 84 | break; 85 | 86 | case QCIF: 87 | _width = 176; 88 | _height = 144; 89 | break; 90 | 91 | case QQVGA: 92 | _width = 160; 93 | _height = 120; 94 | break; 95 | 96 | default: 97 | return 0; 98 | } 99 | 100 | _grayscale = false; 101 | switch (format) { 102 | case YUV422: 103 | case RGB444: 104 | case RGB565: 105 | _bytesPerPixel = 2; 106 | break; 107 | 108 | case GRAYSCALE: 109 | format = YUV422; // We use YUV422 but discard U and V bytes 110 | _bytesPerPixel = 2; // 2 input bytes per pixel of which 1 is discarded 111 | _grayscale = true; 112 | break; 113 | 114 | default: 115 | return 0; 116 | } 117 | 118 | // The only frame rates which work on the Nano 33 BLE are 1 and 5 FPS 119 | if (fps != 1 && fps != 5) 120 | return 0; 121 | 122 | _ov7670 = ov7670_alloc(); 123 | if (!_ov7670) { 124 | end(); 125 | 126 | return 0; 127 | } 128 | 129 | pinMode(_vsyncPin, INPUT); 130 | pinMode(_hrefPin, INPUT); 131 | pinMode(_pclkPin, INPUT); 132 | pinMode(_xclkPin, OUTPUT); 133 | 134 | for (int i = 0; i < 8; i++) { 135 | pinMode(_dPins[i], INPUT); 136 | } 137 | 138 | _vsyncPort = portInputRegister(digitalPinToPort(_vsyncPin)); 139 | _vsyncMask = digitalPinToBitMask(_vsyncPin); 140 | _hrefPort = portInputRegister(digitalPinToPort(_hrefPin)); 141 | _hrefMask = digitalPinToBitMask(_hrefPin); 142 | _pclkPort = portInputRegister(digitalPinToPort(_pclkPin)); 143 | _pclkMask = digitalPinToBitMask(_pclkPin); 144 | 145 | beginXClk(); 146 | 147 | Wire.begin(); 148 | 149 | delay(1000); 150 | 151 | if (ov7670_detect(_ov7670)) { 152 | end(); 153 | 154 | return 0; 155 | } 156 | 157 | ov7670_configure(_ov7670, camera_name /*OV7670 = 0, OV7675 = 1*/, format, resolution, 158 | 16 /* MHz */, 0 /*pll bypass*/, 1 /* pclk_hb_disable */); 159 | 160 | if (ov7670_s_power(_ov7670, 1)) { 161 | end(); 162 | 163 | return 0; 164 | } 165 | 166 | struct ov7670_fract tpf; 167 | 168 | tpf.numerator = 1; 169 | tpf.denominator = fps; 170 | 171 | ov7675_set_framerate(_ov7670, &tpf); 172 | 173 | return 1; 174 | } 175 | 176 | void OV767X::end() 177 | { 178 | endXClk(); 179 | 180 | pinMode(_xclkPin, INPUT); 181 | 182 | Wire.end(); 183 | 184 | if (_ov7670) { 185 | ov7670_free(_ov7670); 186 | } 187 | } 188 | 189 | int OV767X::width() const 190 | { 191 | return _width; 192 | } 193 | 194 | int OV767X::height() const 195 | { 196 | return _height; 197 | } 198 | 199 | int OV767X::bitsPerPixel() const 200 | { 201 | if (_grayscale) { 202 | return 8; 203 | } else { 204 | return _bytesPerPixel * 8; 205 | } 206 | } 207 | 208 | int OV767X::bytesPerPixel() const 209 | { 210 | if (_grayscale) { 211 | return 1; 212 | } else { 213 | return _bytesPerPixel; 214 | } 215 | } 216 | 217 | // 218 | // Optimized Data Reading Explanation: 219 | // 220 | // In order to keep up with the data rate of 5 FPS, the inner loop that reads 221 | // data from the camera board needs to be as quick as possible. The 64 Mhz ARM 222 | // Cortex-M4 in the Nano 33 would not be able to keep up if we read each bit 223 | // one at a time from the various GPIO pins and combined them into a byte. 224 | // Instead, we chose specific GPIO pins which all occupy a single GPIO "PORT" 225 | // In this case, P1 (The Nano 33 exposes some bits of P0 and some of P1). 226 | // The bits on P1 are not connected to sequential GPIO pins, so the order 227 | // chosen may look a bit odd. Below is a map showing the GPIO pin numbers 228 | // and the bit position they correspond with on P1 (bit 0 is on the right) 229 | // 230 | // 20-19-18-17-16-15-14-13-12-11-10-09-08-07-06-05-04-03-02-01-00 (bit) 231 | // ~ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 232 | // ~ |xx|xx|xx|xx|xx|04|06|05|03|02|01|xx|12|xx|xx|xx|xx|00|10|11|xx| (pin) 233 | // ~ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 234 | // 235 | // The most efficient way to read 8-bits of data with the arrangement above 236 | // is to wire the pins for P1 bits 2,3,10,11,12,13,14,15. This allows other 237 | // features such as SPI to still work and gives us 2 groups of contiguous 238 | // bits (0-1, 10-15). With 2 groups of bits, we can read, mask, shift and 239 | // OR them together to form an 8-bit byte with the minimum number of operations. 240 | // 241 | void OV767X::readFrame(void* buffer) 242 | { 243 | uint32_t ulPin = 33; // P1.xx set of GPIO is in 'pin' 32 and above 244 | NRF_GPIO_Type * port; 245 | 246 | port = nrf_gpio_pin_port_decode(&ulPin); 247 | 248 | noInterrupts(); 249 | 250 | uint8_t* b = (uint8_t*)buffer; 251 | int bytesPerRow = _width * _bytesPerPixel; 252 | 253 | // Falling edge indicates start of frame 254 | while ((*_vsyncPort & _vsyncMask) == 0); // wait for HIGH 255 | while ((*_vsyncPort & _vsyncMask) != 0); // wait for LOW 256 | 257 | for (int i = 0; i < _height; i++) { 258 | // rising edge indicates start of line 259 | while ((*_hrefPort & _hrefMask) == 0); // wait for HIGH 260 | 261 | for (int j = 0; j < bytesPerRow; j++) { 262 | // rising edges clock each data byte 263 | while ((*_pclkPort & _pclkMask) != 0); // wait for LOW 264 | 265 | uint32_t in = port->IN; // read all bits in parallel 266 | 267 | in >>= 2; // place bits 0 and 1 at the "bottom" of the register 268 | in &= 0x3f03; // isolate the 8 bits we care about 269 | in |= (in >> 6); // combine the upper 6 and lower 2 bits 270 | 271 | if (!(j & 1) || !_grayscale) { 272 | *b++ = in; 273 | } 274 | while ((*_pclkPort & _pclkMask) == 0); // wait for HIGH 275 | } 276 | while ((*_hrefPort & _hrefMask) != 0); // wait for LOW 277 | } 278 | 279 | interrupts(); 280 | } 281 | 282 | void OV767X::testPattern(int pattern) 283 | { 284 | ov7670_s_test_pattern(_ov7670, pattern); 285 | } 286 | 287 | void OV767X::noTestPattern() 288 | { 289 | ov7670_s_test_pattern(_ov7670, 0); 290 | } 291 | 292 | void OV767X::setSaturation(int saturation) 293 | { 294 | _saturation = saturation; 295 | 296 | ov7670_s_sat_hue(_ov7670, _saturation, _hue); 297 | } 298 | 299 | void OV767X::setHue(int hue) 300 | { 301 | _hue = hue; 302 | 303 | ov7670_s_sat_hue(_ov7670, _saturation, _hue); 304 | } 305 | 306 | void OV767X::setBrightness(int brightness) 307 | { 308 | ov7670_s_brightness(_ov7670, brightness); 309 | } 310 | 311 | void OV767X::setContrast(int contrast) 312 | { 313 | ov7670_s_contrast(_ov7670, contrast); 314 | } 315 | 316 | void OV767X::horizontalFlip() 317 | { 318 | ov7670_s_hflip(_ov7670, 1); 319 | } 320 | 321 | void OV767X::noHorizontalFlip() 322 | { 323 | ov7670_s_hflip(_ov7670, 0); 324 | } 325 | 326 | void OV767X::verticalFlip() 327 | { 328 | ov7670_s_vflip(_ov7670, 1); 329 | } 330 | 331 | void OV767X::noVerticalFlip() 332 | { 333 | ov7670_s_vflip(_ov7670, 0); 334 | } 335 | 336 | void OV767X::setGain(int gain) 337 | { 338 | ov7670_s_gain(_ov7670, gain); 339 | } 340 | 341 | void OV767X::autoGain() 342 | { 343 | ov7670_s_autogain(_ov7670, 1); 344 | } 345 | 346 | void OV767X::setExposure(int exposure) 347 | { 348 | ov7670_s_exp(_ov7670, exposure); 349 | } 350 | 351 | void OV767X::autoExposure() 352 | { 353 | ov7670_s_autoexp(_ov7670, 0 /* V4L2_EXPOSURE_AUTO */); 354 | } 355 | 356 | void OV767X::setPins(int vsync, int href, int pclk, int xclk, const int dpins[8]) 357 | { 358 | _vsyncPin = vsync; 359 | _hrefPin = href; 360 | _pclkPin = pclk; 361 | _xclkPin = xclk; 362 | 363 | memcpy(_dPins, dpins, sizeof(_dPins)); 364 | } 365 | 366 | void OV767X::beginXClk() 367 | { 368 | // Generates 16 MHz signal using I2S peripheral 369 | NRF_I2S->CONFIG.MCKEN = (I2S_CONFIG_MCKEN_MCKEN_ENABLE << I2S_CONFIG_MCKEN_MCKEN_Pos); 370 | NRF_I2S->CONFIG.MCKFREQ = I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV2 << I2S_CONFIG_MCKFREQ_MCKFREQ_Pos; 371 | NRF_I2S->CONFIG.MODE = I2S_CONFIG_MODE_MODE_MASTER << I2S_CONFIG_MODE_MODE_Pos; 372 | 373 | NRF_I2S->PSEL.MCK = (digitalPinToPinName(_xclkPin) << I2S_PSEL_MCK_PIN_Pos); 374 | 375 | NRF_I2S->ENABLE = 1; 376 | NRF_I2S->TASKS_START = 1; 377 | } 378 | 379 | void OV767X::endXClk() 380 | { 381 | NRF_I2S->TASKS_STOP = 1; 382 | } 383 | 384 | OV767X Camera; 385 | -------------------------------------------------------------------------------- /src/OV767X.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | /* 3 | * This file is part of the Arduino_OX767X library. 4 | * Copyright (c) 2020 Arduino SA. All rights reserved. 5 | */ 6 | 7 | #ifndef _OV767X_H_ 8 | #define _OV767X_H_ 9 | 10 | #include 11 | 12 | #define OV7670_VSYNC 8 13 | #define OV7670_HREF A1 14 | #define OV7670_PLK A0 15 | #define OV7670_XCLK 9 16 | #define OV7670_D0 10 17 | #define OV7670_D1 1 18 | #define OV7670_D2 0 19 | #define OV7670_D3 2 20 | #define OV7670_D4 3 21 | #define OV7670_D5 5 22 | #define OV7670_D6 6 23 | #define OV7670_D7 4 24 | 25 | enum 26 | { 27 | YUV422 = 0, 28 | RGB444 = 1, 29 | RGB565 = 2, 30 | // SBGGR8 = 3 31 | GRAYSCALE = 4 32 | }; 33 | 34 | enum 35 | { 36 | OV7670 = 0, 37 | OV7675 = 1 38 | }; 39 | 40 | enum 41 | { 42 | VGA = 0, // 640x480 43 | CIF = 1, // 352x240 44 | QVGA = 2, // 320x240 45 | QCIF = 3, // 176x144 46 | QQVGA = 4, // 160x120 47 | }; 48 | 49 | class OV767X 50 | { 51 | public: 52 | OV767X(); 53 | virtual ~OV767X(); 54 | 55 | // Supported FPS: 1, 5, 10, 15, 30 56 | int begin(int resolution, int format, int fps, int camera_name = OV7670); 57 | void end(); 58 | 59 | // must be called after Camera.begin(): 60 | int width() const; 61 | int height() const; 62 | int bitsPerPixel() const; 63 | int bytesPerPixel() const; 64 | 65 | void readFrame(void* buffer); 66 | 67 | void testPattern(int pattern = 2); 68 | void noTestPattern(); 69 | 70 | void setSaturation(int saturation); // 0 - 255 71 | void setHue(int hue); // -180 - 180 72 | void setBrightness(int brightness); // 0 - 255 73 | void setContrast(int contrast); // 0 - 127 74 | void horizontalFlip(); 75 | void noHorizontalFlip(); 76 | void verticalFlip(); 77 | void noVerticalFlip(); 78 | void setGain(int gain); // 0 - 255 79 | void autoGain(); 80 | void setExposure(int exposure); // 0 - 65535 81 | void autoExposure(); 82 | 83 | // must be called before Camera.begin() 84 | void setPins(int vsync, int href, int pclk, int xclk, const int dpins[8]); 85 | 86 | private: 87 | void beginXClk(); 88 | void endXClk(); 89 | 90 | private: 91 | int _vsyncPin; 92 | int _hrefPin; 93 | int _pclkPin; 94 | int _xclkPin; 95 | int _dPins[8]; 96 | 97 | int _width; 98 | int _height; 99 | int _bytesPerPixel; 100 | bool _grayscale; 101 | 102 | void* _ov7670; 103 | 104 | volatile uint32_t* _vsyncPort; 105 | uint32_t _vsyncMask; 106 | volatile uint32_t* _hrefPort; 107 | uint32_t _hrefMask; 108 | volatile uint32_t* _pclkPort; 109 | uint32_t _pclkMask; 110 | 111 | int _saturation; 112 | int _hue; 113 | }; 114 | 115 | extern OV767X Camera; 116 | 117 | #endif 118 | -------------------------------------------------------------------------------- /src/utility/ov7670.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | /* 3 | * A V4L2 driver for OmniVision OV7670 cameras. 4 | * 5 | * Copyright 2006 One Laptop Per Child Association, Inc. Written 6 | * by Jonathan Corbet with substantial inspiration from Mark 7 | * McClelland's ovcamchip code. 8 | * 9 | * Copyright 2006-7 Jonathan Corbet 10 | */ 11 | #ifdef ARDUINO 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | typedef uint8_t u8; 20 | typedef uint32_t u32; 21 | typedef int32_t __s32; 22 | 23 | struct v4l2_subdev { 24 | }; 25 | 26 | struct v4l2_fract { 27 | uint32_t numerator; 28 | uint32_t denominator; 29 | }; 30 | 31 | #define VGA_WIDTH 640 32 | #define VGA_HEIGHT 480 33 | #define CIF_WIDTH 352 34 | #define CIF_HEIGHT 240 35 | #define QVGA_WIDTH 320 36 | #define QVGA_HEIGHT 240 37 | #define QCIF_WIDTH 176 38 | #define QCIF_HEIGHT 144 39 | #define QQVGA_WIDTH 160 40 | #define QQVGA_HEIGHT 120 41 | 42 | #define V4L2_EXPOSURE_AUTO 0 43 | 44 | #define BIT(n) (1 << n) 45 | #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) 46 | 47 | extern void msleep(unsigned long ms); 48 | extern int arduino_i2c_read(unsigned short address, unsigned char reg, unsigned char *value); 49 | extern int arduino_i2c_write(unsigned short address, unsigned char reg, unsigned char value); 50 | 51 | 52 | #include "ov7670.h" 53 | 54 | #else 55 | #include 56 | #include 57 | #include 58 | #include 59 | #include 60 | #include 61 | #include 62 | #include 63 | #include 64 | #include 65 | #include 66 | #include 67 | #include 68 | #include 69 | #include 70 | #include 71 | 72 | MODULE_AUTHOR("Jonathan Corbet "); 73 | MODULE_DESCRIPTION("A low-level driver for OmniVision ov7670 sensors"); 74 | MODULE_LICENSE("GPL"); 75 | 76 | static bool debug; 77 | module_param(debug, bool, 0644); 78 | MODULE_PARM_DESC(debug, "Debug level (0-1)"); 79 | #endif 80 | 81 | /* 82 | * The 7670 sits on i2c with ID 0x42 83 | */ 84 | #define OV7670_I2C_ADDR 0x42 85 | 86 | #define PLL_FACTOR 4 87 | 88 | /* Registers */ 89 | #define REG_GAIN 0x00 /* Gain lower 8 bits (rest in vref) */ 90 | #define REG_BLUE 0x01 /* blue gain */ 91 | #define REG_RED 0x02 /* red gain */ 92 | #define REG_VREF 0x03 /* Pieces of GAIN, VSTART, VSTOP */ 93 | #define REG_COM1 0x04 /* Control 1 */ 94 | #define COM1_CCIR656 0x40 /* CCIR656 enable */ 95 | #define REG_BAVE 0x05 /* U/B Average level */ 96 | #define REG_GbAVE 0x06 /* Y/Gb Average level */ 97 | #define REG_AECHH 0x07 /* AEC MS 5 bits */ 98 | #define REG_RAVE 0x08 /* V/R Average level */ 99 | #define REG_COM2 0x09 /* Control 2 */ 100 | #define COM2_SSLEEP 0x10 /* Soft sleep mode */ 101 | #define REG_PID 0x0a /* Product ID MSB */ 102 | #define REG_VER 0x0b /* Product ID LSB */ 103 | #define REG_COM3 0x0c /* Control 3 */ 104 | #define COM3_SWAP 0x40 /* Byte swap */ 105 | #define COM3_SCALEEN 0x08 /* Enable scaling */ 106 | #define COM3_DCWEN 0x04 /* Enable downsamp/crop/window */ 107 | #define REG_COM4 0x0d /* Control 4 */ 108 | #define REG_COM5 0x0e /* All "reserved" */ 109 | #define REG_COM6 0x0f /* Control 6 */ 110 | #define REG_AECH 0x10 /* More bits of AEC value */ 111 | #define REG_CLKRC 0x11 /* Clocl control */ 112 | #define CLK_EXT 0x40 /* Use external clock directly */ 113 | #define CLK_SCALE 0x3f /* Mask for internal clock scale */ 114 | #define REG_COM7 0x12 /* Control 7 */ 115 | #define COM7_RESET 0x80 /* Register reset */ 116 | #define COM7_FMT_MASK 0x38 117 | #define COM7_FMT_VGA 0x00 118 | #define COM7_FMT_CIF 0x20 /* CIF format */ 119 | #define COM7_FMT_QVGA 0x10 /* QVGA format */ 120 | #define COM7_FMT_QCIF 0x08 /* QCIF format */ 121 | #define COM7_RGB 0x04 /* bits 0 and 2 - RGB format */ 122 | #define COM7_YUV 0x00 /* YUV */ 123 | #define COM7_BAYER 0x01 /* Bayer format */ 124 | #define COM7_PBAYER 0x05 /* "Processed bayer" */ 125 | #define REG_COM8 0x13 /* Control 8 */ 126 | #define COM8_FASTAEC 0x80 /* Enable fast AGC/AEC */ 127 | #define COM8_AECSTEP 0x40 /* Unlimited AEC step size */ 128 | #define COM8_BFILT 0x20 /* Band filter enable */ 129 | #define COM8_AGC 0x04 /* Auto gain enable */ 130 | #define COM8_AWB 0x02 /* White balance enable */ 131 | #define COM8_AEC 0x01 /* Auto exposure enable */ 132 | #define REG_COM9 0x14 /* Control 9 - gain ceiling */ 133 | #define REG_COM10 0x15 /* Control 10 */ 134 | #define COM10_HSYNC 0x40 /* HSYNC instead of HREF */ 135 | #define COM10_PCLK_HB 0x20 /* Suppress PCLK on horiz blank */ 136 | #define COM10_HREF_REV 0x08 /* Reverse HREF */ 137 | #define COM10_VS_LEAD 0x04 /* VSYNC on clock leading edge */ 138 | #define COM10_VS_NEG 0x02 /* VSYNC negative */ 139 | #define COM10_HS_NEG 0x01 /* HSYNC negative */ 140 | #define REG_HSTART 0x17 /* Horiz start high bits */ 141 | #define REG_HSTOP 0x18 /* Horiz stop high bits */ 142 | #define REG_VSTART 0x19 /* Vert start high bits */ 143 | #define REG_VSTOP 0x1a /* Vert stop high bits */ 144 | #define REG_PSHFT 0x1b /* Pixel delay after HREF */ 145 | #define REG_MIDH 0x1c /* Manuf. ID high */ 146 | #define REG_MIDL 0x1d /* Manuf. ID low */ 147 | #define REG_MVFP 0x1e /* Mirror / vflip */ 148 | #define MVFP_MIRROR 0x20 /* Mirror image */ 149 | #define MVFP_FLIP 0x10 /* Vertical flip */ 150 | 151 | #define REG_AEW 0x24 /* AGC upper limit */ 152 | #define REG_AEB 0x25 /* AGC lower limit */ 153 | #define REG_VPT 0x26 /* AGC/AEC fast mode op region */ 154 | #define REG_HSYST 0x30 /* HSYNC rising edge delay */ 155 | #define REG_HSYEN 0x31 /* HSYNC falling edge delay */ 156 | #define REG_HREF 0x32 /* HREF pieces */ 157 | #define REG_TSLB 0x3a /* lots of stuff */ 158 | #define TSLB_YLAST 0x04 /* UYVY or VYUY - see com13 */ 159 | #define REG_COM11 0x3b /* Control 11 */ 160 | #define COM11_NIGHT 0x80 /* NIght mode enable */ 161 | #define COM11_NMFR 0x60 /* Two bit NM frame rate */ 162 | #define COM11_HZAUTO 0x10 /* Auto detect 50/60 Hz */ 163 | #define COM11_50HZ 0x08 /* Manual 50Hz select */ 164 | #define COM11_EXP 0x02 165 | #define REG_COM12 0x3c /* Control 12 */ 166 | #define COM12_HREF 0x80 /* HREF always */ 167 | #define REG_COM13 0x3d /* Control 13 */ 168 | #define COM13_GAMMA 0x80 /* Gamma enable */ 169 | #define COM13_UVSAT 0x40 /* UV saturation auto adjustment */ 170 | #define COM13_UVSWAP 0x01 /* V before U - w/TSLB */ 171 | #define REG_COM14 0x3e /* Control 14 */ 172 | #define COM14_DCWEN 0x10 /* DCW/PCLK-scale enable */ 173 | #define REG_EDGE 0x3f /* Edge enhancement factor */ 174 | #define REG_COM15 0x40 /* Control 15 */ 175 | #define COM15_R10F0 0x00 /* Data range 10 to F0 */ 176 | #define COM15_R01FE 0x80 /* 01 to FE */ 177 | #define COM15_R00FF 0xc0 /* 00 to FF */ 178 | #define COM15_RGB565 0x10 /* RGB565 output */ 179 | #define COM15_RGB555 0x30 /* RGB555 output */ 180 | #define REG_COM16 0x41 /* Control 16 */ 181 | #define COM16_AWBGAIN 0x08 /* AWB gain enable */ 182 | #define REG_COM17 0x42 /* Control 17 */ 183 | #define COM17_AECWIN 0xc0 /* AEC window - must match COM4 */ 184 | #define COM17_CBAR 0x08 /* DSP Color bar */ 185 | 186 | /* 187 | * This matrix defines how the colors are generated, must be 188 | * tweaked to adjust hue and saturation. 189 | * 190 | * Order: v-red, v-green, v-blue, u-red, u-green, u-blue 191 | * 192 | * They are nine-bit signed quantities, with the sign bit 193 | * stored in 0x58. Sign for v-red is bit 0, and up from there. 194 | */ 195 | #define REG_CMATRIX_BASE 0x4f 196 | #define CMATRIX_LEN 6 197 | #define REG_CMATRIX_SIGN 0x58 198 | 199 | 200 | #define REG_BRIGHT 0x55 /* Brightness */ 201 | #define REG_CONTRAS 0x56 /* Contrast control */ 202 | 203 | #define REG_GFIX 0x69 /* Fix gain control */ 204 | 205 | #define REG_DBLV 0x6b /* PLL control an debugging */ 206 | #define DBLV_BYPASS 0x0a /* Bypass PLL */ 207 | #define DBLV_X4 0x4a /* clock x4 */ 208 | #define DBLV_X6 0x8a /* clock x6 */ 209 | #define DBLV_X8 0xca /* clock x8 */ 210 | 211 | #define REG_SCALING_XSC 0x70 /* Test pattern and horizontal scale factor */ 212 | #define TEST_PATTTERN_0 0x80 213 | #define REG_SCALING_YSC 0x71 /* Test pattern and vertical scale factor */ 214 | #define TEST_PATTTERN_1 0x80 215 | 216 | #define REG_REG76 0x76 /* OV's name */ 217 | #define R76_BLKPCOR 0x80 /* Black pixel correction enable */ 218 | #define R76_WHTPCOR 0x40 /* White pixel correction enable */ 219 | 220 | #define REG_RGB444 0x8c /* RGB 444 control */ 221 | #define R444_ENABLE 0x02 /* Turn on RGB444, overrides 5x5 */ 222 | #define R444_RGBX 0x01 /* Empty nibble at end */ 223 | 224 | #define REG_HAECC1 0x9f /* Hist AEC/AGC control 1 */ 225 | #define REG_HAECC2 0xa0 /* Hist AEC/AGC control 2 */ 226 | 227 | #define REG_BD50MAX 0xa5 /* 50hz banding step limit */ 228 | #define REG_HAECC3 0xa6 /* Hist AEC/AGC control 3 */ 229 | #define REG_HAECC4 0xa7 /* Hist AEC/AGC control 4 */ 230 | #define REG_HAECC5 0xa8 /* Hist AEC/AGC control 5 */ 231 | #define REG_HAECC6 0xa9 /* Hist AEC/AGC control 6 */ 232 | #define REG_HAECC7 0xaa /* Hist AEC/AGC control 7 */ 233 | #define REG_BD60MAX 0xab /* 60hz banding step limit */ 234 | 235 | enum ov7670_model { 236 | MODEL_OV7670 = 0, 237 | MODEL_OV7675, 238 | }; 239 | 240 | struct ov7670_win_size { 241 | int width; 242 | int height; 243 | unsigned char com7_bit; 244 | int hstart; /* Start/stop values for the camera. Note */ 245 | int hstop; /* that they do not always make complete */ 246 | int vstart; /* sense to humans, but evidently the sensor */ 247 | int vstop; /* will do the right thing... */ 248 | struct regval_list *regs; /* Regs to tweak */ 249 | }; 250 | 251 | struct ov7670_devtype { 252 | /* formats supported for each model */ 253 | struct ov7670_win_size *win_sizes; 254 | unsigned int n_win_sizes; 255 | /* callbacks for frame rate control */ 256 | int (*set_framerate)(struct v4l2_subdev *, struct v4l2_fract *); 257 | void (*get_framerate)(struct v4l2_subdev *, struct v4l2_fract *); 258 | }; 259 | 260 | /* 261 | * Information we maintain about a known sensor. 262 | */ 263 | struct ov7670_format_struct; /* coming later */ 264 | struct ov7670_info { 265 | struct v4l2_subdev sd; 266 | #if defined(CONFIG_MEDIA_CONTROLLER) 267 | struct media_pad pad; 268 | #endif 269 | #ifndef ARDUINO 270 | struct v4l2_ctrl_handler hdl; 271 | struct { 272 | /* gain cluster */ 273 | struct v4l2_ctrl *auto_gain; 274 | struct v4l2_ctrl *gain; 275 | }; 276 | struct { 277 | /* exposure cluster */ 278 | struct v4l2_ctrl *auto_exposure; 279 | struct v4l2_ctrl *exposure; 280 | }; 281 | struct { 282 | /* saturation/hue cluster */ 283 | struct v4l2_ctrl *saturation; 284 | struct v4l2_ctrl *hue; 285 | }; 286 | struct v4l2_mbus_framefmt format; 287 | #endif 288 | struct ov7670_format_struct *fmt; /* Current format */ 289 | struct ov7670_win_size *wsize; 290 | struct clk *clk; 291 | int on; 292 | #ifndef ARDUINO 293 | struct gpio_desc *resetb_gpio; 294 | struct gpio_desc *pwdn_gpio; 295 | unsigned int mbus_config; /* Media bus configuration flags */ 296 | #endif 297 | int min_width; /* Filter out smaller sizes */ 298 | int min_height; /* Filter out smaller sizes */ 299 | int clock_speed; /* External clock speed (MHz) */ 300 | u8 clkrc; /* Clock divider value */ 301 | bool use_smbus; /* Use smbus I/O instead of I2C */ 302 | bool pll_bypass; 303 | bool pclk_hb_disable; 304 | const struct ov7670_devtype *devtype; /* Device specifics */ 305 | }; 306 | 307 | static inline struct ov7670_info *to_state(struct v4l2_subdev *sd) 308 | { 309 | #ifdef ARDUINO 310 | return (struct ov7670_info*)sd; 311 | #else 312 | return container_of(sd, struct ov7670_info, sd); 313 | #endif 314 | } 315 | 316 | #ifndef ARDUINO 317 | static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) 318 | { 319 | return &container_of(ctrl->handler, struct ov7670_info, hdl)->sd; 320 | } 321 | #endif 322 | 323 | 324 | 325 | /* 326 | * The default register settings, as obtained from OmniVision. There 327 | * is really no making sense of most of these - lots of "reserved" values 328 | * and such. 329 | * 330 | * These settings give VGA YUYV. 331 | */ 332 | 333 | struct regval_list { 334 | unsigned char reg_num; 335 | unsigned char value; 336 | }; 337 | 338 | static struct regval_list ov7670_default_regs[] = { 339 | { REG_COM7, COM7_RESET }, 340 | /* 341 | * Clock scale: 3 = 15fps 342 | * 2 = 20fps 343 | * 1 = 30fps 344 | */ 345 | { REG_CLKRC, 0x1 }, /* OV: clock scale (30 fps) */ 346 | { REG_TSLB, 0x04 }, /* OV */ 347 | { REG_COM7, 0 }, /* VGA */ 348 | /* 349 | * Set the hardware window. These values from OV don't entirely 350 | * make sense - hstop is less than hstart. But they work... 351 | */ 352 | { REG_HSTART, 0x13 }, { REG_HSTOP, 0x01 }, 353 | { REG_HREF, 0xb6 }, { REG_VSTART, 0x02 }, 354 | { REG_VSTOP, 0x7a }, { REG_VREF, 0x0a }, 355 | 356 | { REG_COM3, 0 }, { REG_COM14, 0 }, 357 | /* Mystery scaling numbers */ 358 | { REG_SCALING_XSC, 0x3a }, 359 | { REG_SCALING_YSC, 0x35 }, 360 | { 0x72, 0x11 }, { 0x73, 0xf0 }, 361 | { 0xa2, 0x02 }, { REG_COM10, 0x0 }, 362 | 363 | /* Gamma curve values */ 364 | { 0x7a, 0x20 }, { 0x7b, 0x10 }, 365 | { 0x7c, 0x1e }, { 0x7d, 0x35 }, 366 | { 0x7e, 0x5a }, { 0x7f, 0x69 }, 367 | { 0x80, 0x76 }, { 0x81, 0x80 }, 368 | { 0x82, 0x88 }, { 0x83, 0x8f }, 369 | { 0x84, 0x96 }, { 0x85, 0xa3 }, 370 | { 0x86, 0xaf }, { 0x87, 0xc4 }, 371 | { 0x88, 0xd7 }, { 0x89, 0xe8 }, 372 | 373 | /* AGC and AEC parameters. Note we start by disabling those features, 374 | then turn them only after tweaking the values. */ 375 | { REG_COM8, COM8_FASTAEC | COM8_AECSTEP | COM8_BFILT }, 376 | { REG_GAIN, 0 }, { REG_AECH, 0 }, 377 | { REG_COM4, 0x40 }, /* magic reserved bit */ 378 | { REG_COM9, 0x18 }, /* 4x gain + magic rsvd bit */ 379 | { REG_BD50MAX, 0x05 }, { REG_BD60MAX, 0x07 }, 380 | { REG_AEW, 0x95 }, { REG_AEB, 0x33 }, 381 | { REG_VPT, 0xe3 }, { REG_HAECC1, 0x78 }, 382 | { REG_HAECC2, 0x68 }, { 0xa1, 0x03 }, /* magic */ 383 | { REG_HAECC3, 0xd8 }, { REG_HAECC4, 0xd8 }, 384 | { REG_HAECC5, 0xf0 }, { REG_HAECC6, 0x90 }, 385 | { REG_HAECC7, 0x94 }, 386 | { REG_COM8, COM8_FASTAEC|COM8_AECSTEP|COM8_BFILT|COM8_AGC|COM8_AEC }, 387 | 388 | /* Almost all of these are magic "reserved" values. */ 389 | { REG_COM5, 0x61 }, { REG_COM6, 0x4b }, 390 | { 0x16, 0x02 }, { REG_MVFP, 0x07 }, 391 | { 0x21, 0x02 }, { 0x22, 0x91 }, 392 | { 0x29, 0x07 }, { 0x33, 0x0b }, 393 | { 0x35, 0x0b }, { 0x37, 0x1d }, 394 | { 0x38, 0x71 }, { 0x39, 0x2a }, 395 | { REG_COM12, 0x78 }, { 0x4d, 0x40 }, 396 | { 0x4e, 0x20 }, { REG_GFIX, 0 }, 397 | { 0x6b, 0x4a }, { 0x74, 0x10 }, 398 | { 0x8d, 0x4f }, { 0x8e, 0 }, 399 | { 0x8f, 0 }, { 0x90, 0 }, 400 | { 0x91, 0 }, { 0x96, 0 }, 401 | { 0x9a, 0 }, { 0xb0, 0x84 }, 402 | { 0xb1, 0x0c }, { 0xb2, 0x0e }, 403 | { 0xb3, 0x82 }, { 0xb8, 0x0a }, 404 | 405 | /* More reserved magic, some of which tweaks white balance */ 406 | { 0x43, 0x0a }, { 0x44, 0xf0 }, 407 | { 0x45, 0x34 }, { 0x46, 0x58 }, 408 | { 0x47, 0x28 }, { 0x48, 0x3a }, 409 | { 0x59, 0x88 }, { 0x5a, 0x88 }, 410 | { 0x5b, 0x44 }, { 0x5c, 0x67 }, 411 | { 0x5d, 0x49 }, { 0x5e, 0x0e }, 412 | { 0x6c, 0x0a }, { 0x6d, 0x55 }, 413 | { 0x6e, 0x11 }, { 0x6f, 0x9f }, /* "9e for advance AWB" */ 414 | { 0x6a, 0x40 }, { REG_BLUE, 0x40 }, 415 | { REG_RED, 0x60 }, 416 | { REG_COM8, COM8_FASTAEC|COM8_AECSTEP|COM8_BFILT|COM8_AGC|COM8_AEC|COM8_AWB }, 417 | 418 | /* Matrix coefficients */ 419 | { 0x4f, 0x80 }, { 0x50, 0x80 }, 420 | { 0x51, 0 }, { 0x52, 0x22 }, 421 | { 0x53, 0x5e }, { 0x54, 0x80 }, 422 | { 0x58, 0x9e }, 423 | 424 | { REG_COM16, COM16_AWBGAIN }, { REG_EDGE, 0 }, 425 | { 0x75, 0x05 }, { 0x76, 0xe1 }, 426 | { 0x4c, 0 }, { 0x77, 0x01 }, 427 | { REG_COM13, 0xc3 }, { 0x4b, 0x09 }, 428 | { 0xc9, 0x60 }, { REG_COM16, 0x38 }, 429 | { 0x56, 0x40 }, 430 | 431 | { 0x34, 0x11 }, { REG_COM11, COM11_EXP|COM11_HZAUTO }, 432 | { 0xa4, 0x88 }, { 0x96, 0 }, 433 | { 0x97, 0x30 }, { 0x98, 0x20 }, 434 | { 0x99, 0x30 }, { 0x9a, 0x84 }, 435 | { 0x9b, 0x29 }, { 0x9c, 0x03 }, 436 | { 0x9d, 0x4c }, { 0x9e, 0x3f }, 437 | { 0x78, 0x04 }, 438 | 439 | /* Extra-weird stuff. Some sort of multiplexor register */ 440 | { 0x79, 0x01 }, { 0xc8, 0xf0 }, 441 | { 0x79, 0x0f }, { 0xc8, 0x00 }, 442 | { 0x79, 0x10 }, { 0xc8, 0x7e }, 443 | { 0x79, 0x0a }, { 0xc8, 0x80 }, 444 | { 0x79, 0x0b }, { 0xc8, 0x01 }, 445 | { 0x79, 0x0c }, { 0xc8, 0x0f }, 446 | { 0x79, 0x0d }, { 0xc8, 0x20 }, 447 | { 0x79, 0x09 }, { 0xc8, 0x80 }, 448 | { 0x79, 0x02 }, { 0xc8, 0xc0 }, 449 | { 0x79, 0x03 }, { 0xc8, 0x40 }, 450 | { 0x79, 0x05 }, { 0xc8, 0x30 }, 451 | { 0x79, 0x26 }, 452 | 453 | { 0xff, 0xff }, /* END MARKER */ 454 | }; 455 | 456 | 457 | /* 458 | * Here we'll try to encapsulate the changes for just the output 459 | * video format. 460 | * 461 | * RGB656 and YUV422 come from OV; RGB444 is homebrewed. 462 | * 463 | * IMPORTANT RULE: the first entry must be for COM7, see ov7670_s_fmt for why. 464 | */ 465 | 466 | 467 | static struct regval_list ov7670_fmt_yuv422[] = { 468 | { REG_COM7, 0x0 }, /* Selects YUV mode */ 469 | { REG_RGB444, 0 }, /* No RGB444 please */ 470 | { REG_COM1, 0 }, /* CCIR601 */ 471 | { REG_COM15, COM15_R00FF }, 472 | { REG_COM9, 0x48 }, /* 32x gain ceiling; 0x8 is reserved bit */ 473 | { 0x4f, 0x80 }, /* "matrix coefficient 1" */ 474 | { 0x50, 0x80 }, /* "matrix coefficient 2" */ 475 | { 0x51, 0 }, /* vb */ 476 | { 0x52, 0x22 }, /* "matrix coefficient 4" */ 477 | { 0x53, 0x5e }, /* "matrix coefficient 5" */ 478 | { 0x54, 0x80 }, /* "matrix coefficient 6" */ 479 | { REG_COM13, COM13_GAMMA|COM13_UVSAT }, 480 | { 0xff, 0xff }, 481 | }; 482 | 483 | static struct regval_list ov7670_fmt_rgb565[] = { 484 | { REG_COM7, COM7_RGB }, /* Selects RGB mode */ 485 | { REG_RGB444, 0 }, /* No RGB444 please */ 486 | { REG_COM1, 0x0 }, /* CCIR601 */ 487 | { REG_COM15, COM15_RGB565 }, 488 | { REG_COM9, 0x38 }, /* 16x gain ceiling; 0x8 is reserved bit */ 489 | { 0x4f, 0xb3 }, /* "matrix coefficient 1" */ 490 | { 0x50, 0xb3 }, /* "matrix coefficient 2" */ 491 | { 0x51, 0 }, /* vb */ 492 | { 0x52, 0x3d }, /* "matrix coefficient 4" */ 493 | { 0x53, 0xa7 }, /* "matrix coefficient 5" */ 494 | { 0x54, 0xe4 }, /* "matrix coefficient 6" */ 495 | { REG_COM13, COM13_GAMMA|COM13_UVSAT }, 496 | { 0xff, 0xff }, 497 | }; 498 | 499 | static struct regval_list ov7670_fmt_rgb444[] = { 500 | { REG_COM7, COM7_RGB }, /* Selects RGB mode */ 501 | { REG_RGB444, R444_ENABLE }, /* Enable xxxxrrrr ggggbbbb */ 502 | { REG_COM1, 0x0 }, /* CCIR601 */ 503 | { REG_COM15, COM15_R01FE|COM15_RGB565 }, /* Data range needed? */ 504 | { REG_COM9, 0x38 }, /* 16x gain ceiling; 0x8 is reserved bit */ 505 | { 0x4f, 0xb3 }, /* "matrix coefficient 1" */ 506 | { 0x50, 0xb3 }, /* "matrix coefficient 2" */ 507 | { 0x51, 0 }, /* vb */ 508 | { 0x52, 0x3d }, /* "matrix coefficient 4" */ 509 | { 0x53, 0xa7 }, /* "matrix coefficient 5" */ 510 | { 0x54, 0xe4 }, /* "matrix coefficient 6" */ 511 | { REG_COM13, COM13_GAMMA|COM13_UVSAT|0x2 }, /* Magic rsvd bit */ 512 | { 0xff, 0xff }, 513 | }; 514 | 515 | static struct regval_list ov7670_fmt_raw[] = { 516 | { REG_COM7, COM7_BAYER }, 517 | { REG_COM13, 0x08 }, /* No gamma, magic rsvd bit */ 518 | { REG_COM16, 0x3d }, /* Edge enhancement, denoise */ 519 | { REG_REG76, 0xe1 }, /* Pix correction, magic rsvd */ 520 | { 0xff, 0xff }, 521 | }; 522 | 523 | 524 | #ifdef ARDUINO 525 | 526 | static int ov7670_read(struct v4l2_subdev *sd, unsigned char reg, 527 | unsigned char *value) 528 | { 529 | return arduino_i2c_read(OV7670_I2C_ADDR >> 1, reg, value); 530 | } 531 | 532 | static int ov7670_write(struct v4l2_subdev *sd, unsigned char reg, 533 | unsigned char value) 534 | { 535 | int ret = arduino_i2c_write(OV7670_I2C_ADDR >> 1, reg, value); 536 | 537 | if (reg == REG_COM7 && (value & COM7_RESET)) 538 | msleep(5); /* Wait for reset to run */ 539 | 540 | return ret; 541 | } 542 | 543 | #else 544 | /* 545 | * Low-level register I/O. 546 | * 547 | * Note that there are two versions of these. On the XO 1, the 548 | * i2c controller only does SMBUS, so that's what we use. The 549 | * ov7670 is not really an SMBUS device, though, so the communication 550 | * is not always entirely reliable. 551 | */ 552 | static int ov7670_read_smbus(struct v4l2_subdev *sd, unsigned char reg, 553 | unsigned char *value) 554 | { 555 | struct i2c_client *client = v4l2_get_subdevdata(sd); 556 | int ret; 557 | 558 | ret = i2c_smbus_read_byte_data(client, reg); 559 | if (ret >= 0) { 560 | *value = (unsigned char)ret; 561 | ret = 0; 562 | } 563 | return ret; 564 | } 565 | 566 | 567 | static int ov7670_write_smbus(struct v4l2_subdev *sd, unsigned char reg, 568 | unsigned char value) 569 | { 570 | struct i2c_client *client = v4l2_get_subdevdata(sd); 571 | int ret = i2c_smbus_write_byte_data(client, reg, value); 572 | 573 | if (reg == REG_COM7 && (value & COM7_RESET)) 574 | msleep(5); /* Wait for reset to run */ 575 | return ret; 576 | } 577 | 578 | /* 579 | * On most platforms, we'd rather do straight i2c I/O. 580 | */ 581 | static int ov7670_read_i2c(struct v4l2_subdev *sd, unsigned char reg, 582 | unsigned char *value) 583 | { 584 | struct i2c_client *client = v4l2_get_subdevdata(sd); 585 | u8 data = reg; 586 | struct i2c_msg msg; 587 | int ret; 588 | 589 | /* 590 | * Send out the register address... 591 | */ 592 | msg.addr = client->addr; 593 | msg.flags = 0; 594 | msg.len = 1; 595 | msg.buf = &data; 596 | ret = i2c_transfer(client->adapter, &msg, 1); 597 | if (ret < 0) { 598 | printk(KERN_ERR "Error %d on register write\n", ret); 599 | return ret; 600 | } 601 | /* 602 | * ...then read back the result. 603 | */ 604 | msg.flags = I2C_M_RD; 605 | ret = i2c_transfer(client->adapter, &msg, 1); 606 | if (ret >= 0) { 607 | *value = data; 608 | ret = 0; 609 | } 610 | return ret; 611 | } 612 | 613 | 614 | static int ov7670_write_i2c(struct v4l2_subdev *sd, unsigned char reg, 615 | unsigned char value) 616 | { 617 | struct i2c_client *client = v4l2_get_subdevdata(sd); 618 | struct i2c_msg msg; 619 | unsigned char data[2] = { reg, value }; 620 | int ret; 621 | 622 | msg.addr = client->addr; 623 | msg.flags = 0; 624 | msg.len = 2; 625 | msg.buf = data; 626 | ret = i2c_transfer(client->adapter, &msg, 1); 627 | if (ret > 0) 628 | ret = 0; 629 | if (reg == REG_COM7 && (value & COM7_RESET)) 630 | msleep(5); /* Wait for reset to run */ 631 | return ret; 632 | } 633 | 634 | static int ov7670_read(struct v4l2_subdev *sd, unsigned char reg, 635 | unsigned char *value) 636 | { 637 | struct ov7670_info *info = to_state(sd); 638 | if (info->use_smbus) 639 | return ov7670_read_smbus(sd, reg, value); 640 | else 641 | return ov7670_read_i2c(sd, reg, value); 642 | } 643 | 644 | static int ov7670_write(struct v4l2_subdev *sd, unsigned char reg, 645 | unsigned char value) 646 | { 647 | struct ov7670_info *info = to_state(sd); 648 | if (info->use_smbus) 649 | return ov7670_write_smbus(sd, reg, value); 650 | else 651 | return ov7670_write_i2c(sd, reg, value); 652 | } 653 | #endif 654 | 655 | static int ov7670_update_bits(struct v4l2_subdev *sd, unsigned char reg, 656 | unsigned char mask, unsigned char value) 657 | { 658 | unsigned char orig; 659 | int ret; 660 | 661 | ret = ov7670_read(sd, reg, &orig); 662 | if (ret) 663 | return ret; 664 | 665 | return ov7670_write(sd, reg, (orig & ~mask) | (value & mask)); 666 | } 667 | 668 | /* 669 | * Write a list of register settings; ff/ff stops the process. 670 | */ 671 | static int ov7670_write_array(struct v4l2_subdev *sd, struct regval_list *vals) 672 | { 673 | while (vals->reg_num != 0xff || vals->value != 0xff) { 674 | int ret = ov7670_write(sd, vals->reg_num, vals->value); 675 | if (ret < 0) 676 | return ret; 677 | vals++; 678 | } 679 | return 0; 680 | } 681 | 682 | 683 | /* 684 | * Stuff that knows about the sensor. 685 | */ 686 | #ifdef ARDUINO 687 | int ov7670_reset(struct v4l2_subdev *sd, u32 val) 688 | #else 689 | static int ov7670_reset(struct v4l2_subdev *sd, u32 val) 690 | #endif 691 | { 692 | ov7670_write(sd, REG_COM7, COM7_RESET); 693 | msleep(1); 694 | return 0; 695 | } 696 | 697 | 698 | static int ov7670_init(struct v4l2_subdev *sd, u32 val) 699 | { 700 | return ov7670_write_array(sd, ov7670_default_regs); 701 | } 702 | 703 | #ifdef ARDUINO 704 | int ov7670_detect(struct v4l2_subdev *sd) 705 | #else 706 | static int ov7670_detect(struct v4l2_subdev *sd) 707 | #endif 708 | { 709 | unsigned char v; 710 | int ret; 711 | 712 | ret = ov7670_init(sd, 0); 713 | if (ret < 0) 714 | return ret; 715 | ret = ov7670_read(sd, REG_MIDH, &v); 716 | if (ret < 0) 717 | return ret; 718 | if (v != 0x7f) /* OV manuf. id. */ 719 | return -ENODEV; 720 | ret = ov7670_read(sd, REG_MIDL, &v); 721 | if (ret < 0) 722 | return ret; 723 | if (v != 0xa2) 724 | return -ENODEV; 725 | /* 726 | * OK, we know we have an OmniVision chip...but which one? 727 | */ 728 | ret = ov7670_read(sd, REG_PID, &v); 729 | if (ret < 0) 730 | return ret; 731 | if (v != 0x76) /* PID + VER = 0x76 / 0x73 */ 732 | return -ENODEV; 733 | ret = ov7670_read(sd, REG_VER, &v); 734 | if (ret < 0) 735 | return ret; 736 | if (v != 0x73) /* PID + VER = 0x76 / 0x73 */ 737 | return -ENODEV; 738 | return 0; 739 | } 740 | 741 | 742 | /* 743 | * Store information about the video data format. The color matrix 744 | * is deeply tied into the format, so keep the relevant values here. 745 | * The magic matrix numbers come from OmniVision. 746 | */ 747 | static struct ov7670_format_struct { 748 | #ifndef ARDUINO 749 | u32 mbus_code; 750 | enum v4l2_colorspace colorspace; 751 | #endif 752 | struct regval_list *regs; 753 | int cmatrix[CMATRIX_LEN]; 754 | } ov7670_formats[] = { 755 | { 756 | #ifndef ARDUINO 757 | .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8, 758 | .colorspace = V4L2_COLORSPACE_SRGB, 759 | #endif 760 | .regs = ov7670_fmt_yuv422, 761 | .cmatrix = { 128, -128, 0, -34, -94, 128 }, 762 | }, 763 | { 764 | #ifndef ARDUINO 765 | .mbus_code = MEDIA_BUS_FMT_RGB444_2X8_PADHI_LE, 766 | .colorspace = V4L2_COLORSPACE_SRGB, 767 | #endif 768 | .regs = ov7670_fmt_rgb444, 769 | .cmatrix = { 179, -179, 0, -61, -176, 228 }, 770 | }, 771 | { 772 | #ifndef ARDUINO 773 | .mbus_code = MEDIA_BUS_FMT_RGB565_2X8_LE, 774 | .colorspace = V4L2_COLORSPACE_SRGB, 775 | #endif 776 | .regs = ov7670_fmt_rgb565, 777 | .cmatrix = { 179, -179, 0, -61, -176, 228 }, 778 | }, 779 | { 780 | #ifndef ARDUINO 781 | .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8, 782 | .colorspace = V4L2_COLORSPACE_SRGB, 783 | #endif 784 | .regs = ov7670_fmt_raw, 785 | .cmatrix = { 0, 0, 0, 0, 0, 0 }, 786 | }, 787 | }; 788 | #define N_OV7670_FMTS ARRAY_SIZE(ov7670_formats) 789 | 790 | 791 | /* 792 | * Then there is the issue of window sizes. Try to capture the info here. 793 | */ 794 | 795 | /* 796 | * QCIF mode is done (by OV) in a very strange way - it actually looks like 797 | * VGA with weird scaling options - they do *not* use the canned QCIF mode 798 | * which is allegedly provided by the sensor. So here's the weird register 799 | * settings. 800 | */ 801 | static struct regval_list ov7670_qcif_regs[] = { 802 | { REG_COM3, COM3_SCALEEN|COM3_DCWEN }, 803 | { REG_COM3, COM3_DCWEN }, 804 | { REG_COM14, COM14_DCWEN | 0x01}, 805 | { 0x73, 0xf1 }, 806 | { 0xa2, 0x52 }, 807 | { 0x7b, 0x1c }, 808 | { 0x7c, 0x28 }, 809 | { 0x7d, 0x3c }, 810 | { 0x7f, 0x69 }, 811 | { REG_COM9, 0x38 }, 812 | { 0xa1, 0x0b }, 813 | { 0x74, 0x19 }, 814 | { 0x9a, 0x80 }, 815 | { 0x43, 0x14 }, 816 | { REG_COM13, 0xc0 }, 817 | { 0xff, 0xff }, 818 | }; 819 | 820 | static struct regval_list ov7670_qqvga_regs[] = { 821 | { REG_COM3, COM3_DCWEN }, 822 | { REG_COM14, 0x1a}, 823 | { 0x72, 0x22 }, // downsample by 4 824 | { 0x73, 0xf2 }, // divide by 4 825 | { REG_HSTART, 0x16 }, 826 | { REG_HSTOP, 0x04 }, 827 | { REG_HREF, 0xa4 }, 828 | { REG_VSTART, 0x02 }, 829 | { REG_VSTOP, 0x7a }, 830 | { REG_VREF, 0x0a }, 831 | { 0xff, 0xff }, /* END MARKER */ 832 | 833 | }; 834 | 835 | /* 836 | * To center the QQVGA window on the OV7675 the REG_VSTART value was increased 837 | */ 838 | static struct regval_list ov7675_qqvga_regs[] = { 839 | { REG_COM3, COM3_DCWEN }, 840 | { REG_COM14, 0x1a}, 841 | { 0x72, 0x22 }, // downsample by 4 842 | { 0x73, 0xf2 }, // divide by 4 843 | { REG_HSTART, 0x16 }, 844 | { REG_HSTOP, 0x04 }, 845 | { REG_HREF, 0xa4 }, 846 | { REG_VSTART, 0x22 }, //Different from OV7670 847 | { REG_VSTOP, 0x7a }, 848 | { REG_VREF, 0x0a }, 849 | { 0xff, 0xff }, /* END MARKER */ 850 | 851 | }; 852 | 853 | static struct ov7670_win_size ov7670_win_sizes[] = { 854 | /* VGA */ 855 | { 856 | .width = VGA_WIDTH, 857 | .height = VGA_HEIGHT, 858 | .com7_bit = COM7_FMT_VGA, 859 | .hstart = 158, /* These values from */ 860 | .hstop = 14, /* Omnivision */ 861 | .vstart = 10, 862 | .vstop = 490, 863 | .regs = NULL, 864 | }, 865 | /* CIF */ 866 | { 867 | .width = CIF_WIDTH, 868 | .height = CIF_HEIGHT, 869 | .com7_bit = COM7_FMT_CIF, 870 | .hstart = 170, /* Empirically determined */ 871 | .hstop = 90, 872 | .vstart = 14, 873 | .vstop = 494, 874 | .regs = NULL, 875 | }, 876 | /* QVGA */ 877 | { 878 | .width = QVGA_WIDTH, 879 | .height = QVGA_HEIGHT, 880 | .com7_bit = COM7_FMT_QVGA, 881 | .hstart = 168, /* Empirically determined */ 882 | .hstop = 24, 883 | .vstart = 12, 884 | .vstop = 492, 885 | .regs = NULL, 886 | }, 887 | /* QCIF */ 888 | { 889 | .width = QCIF_WIDTH, 890 | .height = QCIF_HEIGHT, 891 | .com7_bit = COM7_FMT_VGA, /* see comment above */ 892 | .hstart = 456, /* Empirically determined */ 893 | .hstop = 24, 894 | .vstart = 14, 895 | .vstop = 494, 896 | .regs = ov7670_qcif_regs, 897 | }, 898 | /* QQVGA */ 899 | { 900 | .width = QQVGA_WIDTH, 901 | .height = QQVGA_HEIGHT, 902 | .com7_bit = COM7_FMT_VGA, /* see comment above */ 903 | .hstart = 0x16, /* Empirically determined */ 904 | .hstop = 0x04, 905 | .vstart = 0x02, 906 | .vstop = 0x7a, 907 | .regs = ov7670_qqvga_regs, 908 | } 909 | 910 | }; 911 | 912 | static struct ov7670_win_size ov7675_win_sizes[] = { 913 | /* 914 | * Values copied from ov7670_win_sizes and verified to work. 915 | * The QCIF and QQVGA values were changed to center the cropped window. 916 | */ 917 | { 918 | .width = VGA_WIDTH, 919 | .height = VGA_HEIGHT, 920 | .com7_bit = COM7_FMT_VGA, 921 | .hstart = 158, /* These values from */ 922 | .hstop = 14, /* Omnivision */ 923 | .vstart = 14, /* Empirically determined */ 924 | .vstop = 494, 925 | .regs = NULL, 926 | }, 927 | /* CIF */ 928 | { 929 | .width = CIF_WIDTH, 930 | .height = CIF_HEIGHT, 931 | .com7_bit = COM7_FMT_CIF, 932 | .hstart = 170, /* Copied from ov7670 and verified*/ 933 | .hstop = 90, 934 | .vstart = 14, 935 | .vstop = 494, 936 | .regs = NULL, 937 | }, 938 | /* QVGA */ 939 | { 940 | .width = QVGA_WIDTH, 941 | .height = QVGA_HEIGHT, 942 | .com7_bit = COM7_FMT_QVGA, 943 | .hstart = 168, /* Copied from ov7670 and verified*/ 944 | .hstop = 24, 945 | .vstart = 12, 946 | .vstop = 492, 947 | .regs = NULL, 948 | }, 949 | /* QCIF */ 950 | { 951 | .width = QCIF_WIDTH, 952 | .height = QCIF_HEIGHT, 953 | .com7_bit = COM7_FMT_VGA, /* see comment above */ 954 | .hstart = 250, /* Empirically determined and different from ov7670*/ 955 | .hstop = 24, 956 | .vstart = 120, 957 | .vstop = 180, 958 | .regs = ov7670_qcif_regs, 959 | }, 960 | /* QQVGA */ 961 | { 962 | .width = QQVGA_WIDTH, 963 | .height = QQVGA_HEIGHT, 964 | .com7_bit = COM7_FMT_VGA, /* see comment above */ 965 | .hstart = 0x16, /* Empirically determined and different from ov7670*/ 966 | .hstop = 0x04, 967 | .vstart = 0x22, /* These values seem to be overridden by the regs */ 968 | .vstop = 0x7a, 969 | .regs = ov7675_qqvga_regs,/* changed to better center on OV7675 */ 970 | } 971 | }; 972 | 973 | static void ov7675_get_framerate(struct v4l2_subdev *sd, 974 | struct v4l2_fract *tpf) 975 | { 976 | struct ov7670_info *info = to_state(sd); 977 | u32 clkrc = info->clkrc; 978 | int pll_factor; 979 | 980 | if (info->pll_bypass) 981 | pll_factor = 1; 982 | else 983 | pll_factor = PLL_FACTOR; 984 | 985 | clkrc++; 986 | #ifndef ARDUINO 987 | if (info->fmt->mbus_code == MEDIA_BUS_FMT_SBGGR8_1X8) 988 | clkrc = (clkrc >> 1); 989 | #endif 990 | 991 | tpf->numerator = 1; 992 | tpf->denominator = (5 * pll_factor * info->clock_speed) / 993 | (4 * clkrc); 994 | } 995 | 996 | static int ov7675_apply_framerate(struct v4l2_subdev *sd) 997 | { 998 | struct ov7670_info *info = to_state(sd); 999 | int ret; 1000 | 1001 | ret = ov7670_write(sd, REG_CLKRC, info->clkrc); 1002 | if (ret < 0) 1003 | return ret; 1004 | 1005 | return ov7670_write(sd, REG_DBLV, 1006 | info->pll_bypass ? DBLV_BYPASS : DBLV_X4); 1007 | } 1008 | 1009 | #ifdef ARDUINO 1010 | int ov7675_set_framerate(struct v4l2_subdev *sd, struct v4l2_fract *tpf) 1011 | #else 1012 | static int ov7675_set_framerate(struct v4l2_subdev *sd, 1013 | struct v4l2_fract *tpf) 1014 | #endif 1015 | { 1016 | struct ov7670_info *info = to_state(sd); 1017 | u32 clkrc; 1018 | int pll_factor; 1019 | 1020 | /* 1021 | * The formula is fps = 5/4*pixclk for YUV/RGB and 1022 | * fps = 5/2*pixclk for RAW. 1023 | * 1024 | * pixclk = clock_speed / (clkrc + 1) * PLLfactor 1025 | * 1026 | */ 1027 | if (tpf->numerator == 0 || tpf->denominator == 0) { 1028 | clkrc = 0; 1029 | } else { 1030 | pll_factor = info->pll_bypass ? 1 : PLL_FACTOR; 1031 | clkrc = (5 * pll_factor * info->clock_speed * tpf->numerator) / 1032 | (4 * tpf->denominator); 1033 | #ifndef ARDUINO 1034 | if (info->fmt->mbus_code == MEDIA_BUS_FMT_SBGGR8_1X8) 1035 | clkrc = (clkrc << 1); 1036 | #endif 1037 | clkrc--; 1038 | } 1039 | 1040 | /* 1041 | * The datasheet claims that clkrc = 0 will divide the input clock by 1 1042 | * but we've checked with an oscilloscope that it divides by 2 instead. 1043 | * So, if clkrc = 0 just bypass the divider. 1044 | */ 1045 | if (clkrc <= 0) 1046 | clkrc = CLK_EXT; 1047 | else if (clkrc > CLK_SCALE) 1048 | clkrc = CLK_SCALE; 1049 | info->clkrc = clkrc; 1050 | 1051 | /* Recalculate frame rate */ 1052 | ov7675_get_framerate(sd, tpf); 1053 | 1054 | /* 1055 | * If the device is not powered up by the host driver do 1056 | * not apply any changes to H/W at this time. Instead 1057 | * the framerate will be restored right after power-up. 1058 | */ 1059 | if (info->on) 1060 | return ov7675_apply_framerate(sd); 1061 | 1062 | return 0; 1063 | } 1064 | 1065 | static void ov7670_get_framerate_legacy(struct v4l2_subdev *sd, 1066 | struct v4l2_fract *tpf) 1067 | { 1068 | struct ov7670_info *info = to_state(sd); 1069 | 1070 | tpf->numerator = 1; 1071 | tpf->denominator = info->clock_speed; 1072 | if ((info->clkrc & CLK_EXT) == 0 && (info->clkrc & CLK_SCALE) > 1) 1073 | tpf->denominator /= (info->clkrc & CLK_SCALE); 1074 | } 1075 | 1076 | static int ov7670_set_framerate_legacy(struct v4l2_subdev *sd, 1077 | struct v4l2_fract *tpf) 1078 | { 1079 | struct ov7670_info *info = to_state(sd); 1080 | int div; 1081 | 1082 | if (tpf->numerator == 0 || tpf->denominator == 0) 1083 | div = 1; /* Reset to full rate */ 1084 | else 1085 | div = (tpf->numerator * info->clock_speed) / tpf->denominator; 1086 | if (div == 0) 1087 | div = 1; 1088 | else if (div > CLK_SCALE) 1089 | div = CLK_SCALE; 1090 | info->clkrc = (info->clkrc & 0x80) | div; 1091 | tpf->numerator = 1; 1092 | tpf->denominator = info->clock_speed / div; 1093 | 1094 | /* 1095 | * If the device is not powered up by the host driver do 1096 | * not apply any changes to H/W at this time. Instead 1097 | * the framerate will be restored right after power-up. 1098 | */ 1099 | if (info->on) 1100 | return ov7670_write(sd, REG_CLKRC, info->clkrc); 1101 | 1102 | return 0; 1103 | } 1104 | 1105 | /* 1106 | * Store a set of start/stop values into the camera. 1107 | */ 1108 | static int ov7670_set_hw(struct v4l2_subdev *sd, int hstart, int hstop, 1109 | int vstart, int vstop) 1110 | { 1111 | int ret; 1112 | unsigned char v; 1113 | /* 1114 | * Horizontal: 11 bits, top 8 live in hstart and hstop. Bottom 3 of 1115 | * hstart are in href[2:0], bottom 3 of hstop in href[5:3]. There is 1116 | * a mystery "edge offset" value in the top two bits of href. 1117 | */ 1118 | ret = ov7670_write(sd, REG_HSTART, (hstart >> 3) & 0xff); 1119 | ret += ov7670_write(sd, REG_HSTOP, (hstop >> 3) & 0xff); 1120 | ret += ov7670_read(sd, REG_HREF, &v); 1121 | v = (v & 0xc0) | ((hstop & 0x7) << 3) | (hstart & 0x7); 1122 | msleep(10); 1123 | ret += ov7670_write(sd, REG_HREF, v); 1124 | /* 1125 | * Vertical: similar arrangement, but only 10 bits. 1126 | */ 1127 | ret += ov7670_write(sd, REG_VSTART, (vstart >> 2) & 0xff); 1128 | ret += ov7670_write(sd, REG_VSTOP, (vstop >> 2) & 0xff); 1129 | ret += ov7670_read(sd, REG_VREF, &v); 1130 | v = (v & 0xf0) | ((vstop & 0x3) << 2) | (vstart & 0x3); 1131 | msleep(10); 1132 | ret += ov7670_write(sd, REG_VREF, v); 1133 | return ret; 1134 | } 1135 | 1136 | 1137 | #ifndef ARDUINO 1138 | static int ov7670_enum_mbus_code(struct v4l2_subdev *sd, 1139 | struct v4l2_subdev_pad_config *cfg, 1140 | struct v4l2_subdev_mbus_code_enum *code) 1141 | { 1142 | if (code->pad || code->index >= N_OV7670_FMTS) 1143 | return -EINVAL; 1144 | 1145 | code->code = ov7670_formats[code->index].mbus_code; 1146 | return 0; 1147 | } 1148 | 1149 | static int ov7670_try_fmt_internal(struct v4l2_subdev *sd, 1150 | struct v4l2_mbus_framefmt *fmt, 1151 | struct ov7670_format_struct **ret_fmt, 1152 | struct ov7670_win_size **ret_wsize) 1153 | { 1154 | int index, i; 1155 | struct ov7670_win_size *wsize; 1156 | struct ov7670_info *info = to_state(sd); 1157 | unsigned int n_win_sizes = info->devtype->n_win_sizes; 1158 | unsigned int win_sizes_limit = n_win_sizes; 1159 | 1160 | for (index = 0; index < N_OV7670_FMTS; index++) 1161 | if (ov7670_formats[index].mbus_code == fmt->code) 1162 | break; 1163 | if (index >= N_OV7670_FMTS) { 1164 | /* default to first format */ 1165 | index = 0; 1166 | fmt->code = ov7670_formats[0].mbus_code; 1167 | } 1168 | if (ret_fmt != NULL) 1169 | *ret_fmt = ov7670_formats + index; 1170 | /* 1171 | * Fields: the OV devices claim to be progressive. 1172 | */ 1173 | fmt->field = V4L2_FIELD_NONE; 1174 | 1175 | /* 1176 | * Don't consider values that don't match min_height and min_width 1177 | * constraints. 1178 | */ 1179 | if (info->min_width || info->min_height) 1180 | for (i = 0; i < n_win_sizes; i++) { 1181 | wsize = info->devtype->win_sizes + i; 1182 | 1183 | if (wsize->width < info->min_width || 1184 | wsize->height < info->min_height) { 1185 | win_sizes_limit = i; 1186 | break; 1187 | } 1188 | } 1189 | /* 1190 | * Round requested image size down to the nearest 1191 | * we support, but not below the smallest. 1192 | */ 1193 | for (wsize = info->devtype->win_sizes; 1194 | wsize < info->devtype->win_sizes + win_sizes_limit; wsize++) 1195 | if (fmt->width >= wsize->width && fmt->height >= wsize->height) 1196 | break; 1197 | if (wsize >= info->devtype->win_sizes + win_sizes_limit) 1198 | wsize--; /* Take the smallest one */ 1199 | if (ret_wsize != NULL) 1200 | *ret_wsize = wsize; 1201 | /* 1202 | * Note the size we'll actually handle. 1203 | */ 1204 | fmt->width = wsize->width; 1205 | fmt->height = wsize->height; 1206 | fmt->colorspace = ov7670_formats[index].colorspace; 1207 | 1208 | info->format = *fmt; 1209 | 1210 | return 0; 1211 | } 1212 | #endif 1213 | 1214 | static int ov7670_apply_fmt(struct v4l2_subdev *sd) 1215 | { 1216 | struct ov7670_info *info = to_state(sd); 1217 | struct ov7670_win_size *wsize = info->wsize; 1218 | unsigned char com7, com10 = 0; 1219 | int ret; 1220 | 1221 | /* 1222 | * COM7 is a pain in the ass, it doesn't like to be read then 1223 | * quickly written afterward. But we have everything we need 1224 | * to set it absolutely here, as long as the format-specific 1225 | * register sets list it first. 1226 | */ 1227 | com7 = info->fmt->regs[0].value; 1228 | com7 |= wsize->com7_bit; 1229 | ret = ov7670_write(sd, REG_COM7, com7); 1230 | if (ret) 1231 | return ret; 1232 | 1233 | /* 1234 | * Configure the media bus through COM10 register 1235 | */ 1236 | #ifndef ARDUINO 1237 | if (info->mbus_config & V4L2_MBUS_VSYNC_ACTIVE_LOW) 1238 | com10 |= COM10_VS_NEG; 1239 | if (info->mbus_config & V4L2_MBUS_HSYNC_ACTIVE_LOW) 1240 | com10 |= COM10_HREF_REV; 1241 | #endif 1242 | if (info->pclk_hb_disable) 1243 | com10 |= COM10_PCLK_HB; 1244 | ret = ov7670_write(sd, REG_COM10, com10); 1245 | if (ret) 1246 | return ret; 1247 | 1248 | /* 1249 | * Now write the rest of the array. Also store start/stops 1250 | */ 1251 | ret = ov7670_write_array(sd, info->fmt->regs + 1); 1252 | if (ret) 1253 | return ret; 1254 | 1255 | ret = ov7670_set_hw(sd, wsize->hstart, wsize->hstop, wsize->vstart, 1256 | wsize->vstop); 1257 | if (ret) 1258 | return ret; 1259 | 1260 | if (wsize->regs) { 1261 | ret = ov7670_write_array(sd, wsize->regs); 1262 | if (ret) 1263 | return ret; 1264 | } 1265 | 1266 | /* 1267 | * If we're running RGB565, we must rewrite clkrc after setting 1268 | * the other parameters or the image looks poor. If we're *not* 1269 | * doing RGB565, we must not rewrite clkrc or the image looks 1270 | * *really* poor. 1271 | * 1272 | * (Update) Now that we retain clkrc state, we should be able 1273 | * to write it unconditionally, and that will make the frame 1274 | * rate persistent too. 1275 | */ 1276 | ret = ov7670_write(sd, REG_CLKRC, info->clkrc); 1277 | if (ret) 1278 | return ret; 1279 | 1280 | return 0; 1281 | } 1282 | 1283 | #ifndef ARDUINO 1284 | /* 1285 | * Set a format. 1286 | */ 1287 | static int ov7670_set_fmt(struct v4l2_subdev *sd, 1288 | struct v4l2_subdev_pad_config *cfg, 1289 | struct v4l2_subdev_format *format) 1290 | { 1291 | struct ov7670_info *info = to_state(sd); 1292 | #ifdef CONFIG_VIDEO_V4L2_SUBDEV_API 1293 | struct v4l2_mbus_framefmt *mbus_fmt; 1294 | #endif 1295 | int ret; 1296 | 1297 | if (format->pad) 1298 | return -EINVAL; 1299 | 1300 | if (format->which == V4L2_SUBDEV_FORMAT_TRY) { 1301 | ret = ov7670_try_fmt_internal(sd, &format->format, NULL, NULL); 1302 | if (ret) 1303 | return ret; 1304 | #ifdef CONFIG_VIDEO_V4L2_SUBDEV_API 1305 | mbus_fmt = v4l2_subdev_get_try_format(sd, cfg, format->pad); 1306 | *mbus_fmt = format->format; 1307 | #endif 1308 | return 0; 1309 | } 1310 | 1311 | ret = ov7670_try_fmt_internal(sd, &format->format, &info->fmt, &info->wsize); 1312 | if (ret) 1313 | return ret; 1314 | 1315 | /* 1316 | * If the device is not powered up by the host driver do 1317 | * not apply any changes to H/W at this time. Instead 1318 | * the frame format will be restored right after power-up. 1319 | */ 1320 | if (info->on) 1321 | return ov7670_apply_fmt(sd); 1322 | 1323 | return 0; 1324 | } 1325 | 1326 | static int ov7670_get_fmt(struct v4l2_subdev *sd, 1327 | struct v4l2_subdev_pad_config *cfg, 1328 | struct v4l2_subdev_format *format) 1329 | { 1330 | struct ov7670_info *info = to_state(sd); 1331 | #ifdef CONFIG_VIDEO_V4L2_SUBDEV_API 1332 | struct v4l2_mbus_framefmt *mbus_fmt; 1333 | #endif 1334 | 1335 | if (format->which == V4L2_SUBDEV_FORMAT_TRY) { 1336 | #ifdef CONFIG_VIDEO_V4L2_SUBDEV_API 1337 | mbus_fmt = v4l2_subdev_get_try_format(sd, cfg, 0); 1338 | format->format = *mbus_fmt; 1339 | return 0; 1340 | #else 1341 | return -EINVAL; 1342 | #endif 1343 | } else { 1344 | format->format = info->format; 1345 | } 1346 | 1347 | return 0; 1348 | } 1349 | 1350 | /* 1351 | * Implement G/S_PARM. There is a "high quality" mode we could try 1352 | * to do someday; for now, we just do the frame rate tweak. 1353 | */ 1354 | static int ov7670_g_frame_interval(struct v4l2_subdev *sd, 1355 | struct v4l2_subdev_frame_interval *ival) 1356 | { 1357 | struct ov7670_info *info = to_state(sd); 1358 | 1359 | 1360 | info->devtype->get_framerate(sd, &ival->interval); 1361 | 1362 | return 0; 1363 | } 1364 | 1365 | static int ov7670_s_frame_interval(struct v4l2_subdev *sd, 1366 | struct v4l2_subdev_frame_interval *ival) 1367 | { 1368 | struct v4l2_fract *tpf = &ival->interval; 1369 | struct ov7670_info *info = to_state(sd); 1370 | 1371 | 1372 | return info->devtype->set_framerate(sd, tpf); 1373 | } 1374 | 1375 | 1376 | /* 1377 | * Frame intervals. Since frame rates are controlled with the clock 1378 | * divider, we can only do 30/n for integer n values. So no continuous 1379 | * or stepwise options. Here we just pick a handful of logical values. 1380 | */ 1381 | 1382 | static int ov7670_frame_rates[] = { 30, 15, 10, 5, 1 }; 1383 | 1384 | static int ov7670_enum_frame_interval(struct v4l2_subdev *sd, 1385 | struct v4l2_subdev_pad_config *cfg, 1386 | struct v4l2_subdev_frame_interval_enum *fie) 1387 | { 1388 | struct ov7670_info *info = to_state(sd); 1389 | unsigned int n_win_sizes = info->devtype->n_win_sizes; 1390 | int i; 1391 | 1392 | if (fie->pad) 1393 | return -EINVAL; 1394 | if (fie->index >= ARRAY_SIZE(ov7670_frame_rates)) 1395 | return -EINVAL; 1396 | 1397 | /* 1398 | * Check if the width/height is valid. 1399 | * 1400 | * If a minimum width/height was requested, filter out the capture 1401 | * windows that fall outside that. 1402 | */ 1403 | for (i = 0; i < n_win_sizes; i++) { 1404 | struct ov7670_win_size *win = &info->devtype->win_sizes[i]; 1405 | 1406 | if (info->min_width && win->width < info->min_width) 1407 | continue; 1408 | if (info->min_height && win->height < info->min_height) 1409 | continue; 1410 | if (fie->width == win->width && fie->height == win->height) 1411 | break; 1412 | } 1413 | if (i == n_win_sizes) 1414 | return -EINVAL; 1415 | fie->interval.numerator = 1; 1416 | fie->interval.denominator = ov7670_frame_rates[fie->index]; 1417 | return 0; 1418 | } 1419 | 1420 | /* 1421 | * Frame size enumeration 1422 | */ 1423 | static int ov7670_enum_frame_size(struct v4l2_subdev *sd, 1424 | struct v4l2_subdev_pad_config *cfg, 1425 | struct v4l2_subdev_frame_size_enum *fse) 1426 | { 1427 | struct ov7670_info *info = to_state(sd); 1428 | int i; 1429 | int num_valid = -1; 1430 | __u32 index = fse->index; 1431 | unsigned int n_win_sizes = info->devtype->n_win_sizes; 1432 | 1433 | if (fse->pad) 1434 | return -EINVAL; 1435 | 1436 | /* 1437 | * If a minimum width/height was requested, filter out the capture 1438 | * windows that fall outside that. 1439 | */ 1440 | for (i = 0; i < n_win_sizes; i++) { 1441 | struct ov7670_win_size *win = &info->devtype->win_sizes[i]; 1442 | if (info->min_width && win->width < info->min_width) 1443 | continue; 1444 | if (info->min_height && win->height < info->min_height) 1445 | continue; 1446 | if (index == ++num_valid) { 1447 | fse->min_width = fse->max_width = win->width; 1448 | fse->min_height = fse->max_height = win->height; 1449 | return 0; 1450 | } 1451 | } 1452 | 1453 | return -EINVAL; 1454 | } 1455 | #endif 1456 | 1457 | /* 1458 | * Code for dealing with controls. 1459 | */ 1460 | 1461 | static int ov7670_store_cmatrix(struct v4l2_subdev *sd, 1462 | int matrix[CMATRIX_LEN]) 1463 | { 1464 | int i, ret; 1465 | unsigned char signbits = 0; 1466 | 1467 | /* 1468 | * Weird crap seems to exist in the upper part of 1469 | * the sign bits register, so let's preserve it. 1470 | */ 1471 | ret = ov7670_read(sd, REG_CMATRIX_SIGN, &signbits); 1472 | signbits &= 0xc0; 1473 | 1474 | for (i = 0; i < CMATRIX_LEN; i++) { 1475 | unsigned char raw; 1476 | 1477 | if (matrix[i] < 0) { 1478 | signbits |= (1 << i); 1479 | if (matrix[i] < -255) 1480 | raw = 0xff; 1481 | else 1482 | raw = (-1 * matrix[i]) & 0xff; 1483 | } 1484 | else { 1485 | if (matrix[i] > 255) 1486 | raw = 0xff; 1487 | else 1488 | raw = matrix[i] & 0xff; 1489 | } 1490 | ret += ov7670_write(sd, REG_CMATRIX_BASE + i, raw); 1491 | } 1492 | ret += ov7670_write(sd, REG_CMATRIX_SIGN, signbits); 1493 | return ret; 1494 | } 1495 | 1496 | 1497 | /* 1498 | * Hue also requires messing with the color matrix. It also requires 1499 | * trig functions, which tend not to be well supported in the kernel. 1500 | * So here is a simple table of sine values, 0-90 degrees, in steps 1501 | * of five degrees. Values are multiplied by 1000. 1502 | * 1503 | * The following naive approximate trig functions require an argument 1504 | * carefully limited to -180 <= theta <= 180. 1505 | */ 1506 | #define SIN_STEP 5 1507 | static const int ov7670_sin_table[] = { 1508 | 0, 87, 173, 258, 342, 422, 1509 | 499, 573, 642, 707, 766, 819, 1510 | 866, 906, 939, 965, 984, 996, 1511 | 1000 1512 | }; 1513 | 1514 | static int ov7670_sine(int theta) 1515 | { 1516 | int chs = 1; 1517 | int sine; 1518 | 1519 | if (theta < 0) { 1520 | theta = -theta; 1521 | chs = -1; 1522 | } 1523 | if (theta <= 90) 1524 | sine = ov7670_sin_table[theta/SIN_STEP]; 1525 | else { 1526 | theta -= 90; 1527 | sine = 1000 - ov7670_sin_table[theta/SIN_STEP]; 1528 | } 1529 | return sine*chs; 1530 | } 1531 | 1532 | static int ov7670_cosine(int theta) 1533 | { 1534 | theta = 90 - theta; 1535 | if (theta > 180) 1536 | theta -= 360; 1537 | else if (theta < -180) 1538 | theta += 360; 1539 | return ov7670_sine(theta); 1540 | } 1541 | 1542 | 1543 | 1544 | 1545 | static void ov7670_calc_cmatrix(struct ov7670_info *info, 1546 | int matrix[CMATRIX_LEN], int sat, int hue) 1547 | { 1548 | int i; 1549 | /* 1550 | * Apply the current saturation setting first. 1551 | */ 1552 | for (i = 0; i < CMATRIX_LEN; i++) 1553 | matrix[i] = (info->fmt->cmatrix[i] * sat) >> 7; 1554 | /* 1555 | * Then, if need be, rotate the hue value. 1556 | */ 1557 | if (hue != 0) { 1558 | int sinth, costh, tmpmatrix[CMATRIX_LEN]; 1559 | 1560 | memcpy(tmpmatrix, matrix, CMATRIX_LEN*sizeof(int)); 1561 | sinth = ov7670_sine(hue); 1562 | costh = ov7670_cosine(hue); 1563 | 1564 | matrix[0] = (matrix[3]*sinth + matrix[0]*costh)/1000; 1565 | matrix[1] = (matrix[4]*sinth + matrix[1]*costh)/1000; 1566 | matrix[2] = (matrix[5]*sinth + matrix[2]*costh)/1000; 1567 | matrix[3] = (matrix[3]*costh - matrix[0]*sinth)/1000; 1568 | matrix[4] = (matrix[4]*costh - matrix[1]*sinth)/1000; 1569 | matrix[5] = (matrix[5]*costh - matrix[2]*sinth)/1000; 1570 | } 1571 | } 1572 | 1573 | 1574 | 1575 | #ifdef ARDUINO 1576 | int ov7670_s_sat_hue(struct v4l2_subdev *sd, int sat, int hue) 1577 | #else 1578 | static int ov7670_s_sat_hue(struct v4l2_subdev *sd, int sat, int hue) 1579 | #endif 1580 | { 1581 | struct ov7670_info *info = to_state(sd); 1582 | int matrix[CMATRIX_LEN]; 1583 | int ret; 1584 | 1585 | ov7670_calc_cmatrix(info, matrix, sat, hue); 1586 | ret = ov7670_store_cmatrix(sd, matrix); 1587 | return ret; 1588 | } 1589 | 1590 | 1591 | /* 1592 | * Some weird registers seem to store values in a sign/magnitude format! 1593 | */ 1594 | 1595 | static unsigned char ov7670_abs_to_sm(unsigned char v) 1596 | { 1597 | if (v > 127) 1598 | return v & 0x7f; 1599 | return (128 - v) | 0x80; 1600 | } 1601 | 1602 | #ifdef ARDUINO 1603 | int ov7670_s_brightness(struct v4l2_subdev *sd, int value) 1604 | #else 1605 | static int ov7670_s_brightness(struct v4l2_subdev *sd, int value) 1606 | #endif 1607 | { 1608 | unsigned char com8 = 0, v; 1609 | int ret; 1610 | 1611 | ov7670_read(sd, REG_COM8, &com8); 1612 | com8 &= ~COM8_AEC; 1613 | ov7670_write(sd, REG_COM8, com8); 1614 | v = ov7670_abs_to_sm(value); 1615 | ret = ov7670_write(sd, REG_BRIGHT, v); 1616 | return ret; 1617 | } 1618 | 1619 | #ifdef ARDUINO 1620 | int ov7670_s_contrast(struct v4l2_subdev *sd, int value) 1621 | #else 1622 | static int ov7670_s_contrast(struct v4l2_subdev *sd, int value) 1623 | #endif 1624 | { 1625 | return ov7670_write(sd, REG_CONTRAS, (unsigned char) value); 1626 | } 1627 | 1628 | #ifdef ARDUINO 1629 | int ov7670_s_hflip(struct v4l2_subdev *sd, int value) 1630 | #else 1631 | static int ov7670_s_hflip(struct v4l2_subdev *sd, int value) 1632 | #endif 1633 | { 1634 | unsigned char v = 0; 1635 | int ret; 1636 | 1637 | ret = ov7670_read(sd, REG_MVFP, &v); 1638 | if (value) 1639 | v |= MVFP_MIRROR; 1640 | else 1641 | v &= ~MVFP_MIRROR; 1642 | msleep(10); /* FIXME */ 1643 | ret += ov7670_write(sd, REG_MVFP, v); 1644 | return ret; 1645 | } 1646 | 1647 | #ifdef ARDUINO 1648 | int ov7670_s_vflip(struct v4l2_subdev *sd, int value) 1649 | #else 1650 | static int ov7670_s_vflip(struct v4l2_subdev *sd, int value) 1651 | #endif 1652 | { 1653 | unsigned char v = 0; 1654 | int ret; 1655 | 1656 | ret = ov7670_read(sd, REG_MVFP, &v); 1657 | if (value) 1658 | v |= MVFP_FLIP; 1659 | else 1660 | v &= ~MVFP_FLIP; 1661 | msleep(10); /* FIXME */ 1662 | ret += ov7670_write(sd, REG_MVFP, v); 1663 | return ret; 1664 | } 1665 | 1666 | #ifndef ARDUINO 1667 | /* 1668 | * GAIN is split between REG_GAIN and REG_VREF[7:6]. If one believes 1669 | * the data sheet, the VREF parts should be the most significant, but 1670 | * experience shows otherwise. There seems to be little value in 1671 | * messing with the VREF bits, so we leave them alone. 1672 | */ 1673 | static int ov7670_g_gain(struct v4l2_subdev *sd, __s32 *value) 1674 | { 1675 | int ret; 1676 | unsigned char gain; 1677 | 1678 | ret = ov7670_read(sd, REG_GAIN, &gain); 1679 | *value = gain; 1680 | return ret; 1681 | } 1682 | #endif 1683 | 1684 | #ifdef ARDUINO 1685 | int ov7670_s_gain(struct v4l2_subdev *sd, int value) 1686 | #else 1687 | static int ov7670_s_gain(struct v4l2_subdev *sd, int value) 1688 | #endif 1689 | { 1690 | int ret; 1691 | unsigned char com8; 1692 | 1693 | ret = ov7670_write(sd, REG_GAIN, value & 0xff); 1694 | /* Have to turn off AGC as well */ 1695 | if (ret == 0) { 1696 | ret = ov7670_read(sd, REG_COM8, &com8); 1697 | ret = ov7670_write(sd, REG_COM8, com8 & ~COM8_AGC); 1698 | } 1699 | return ret; 1700 | } 1701 | 1702 | /* 1703 | * Tweak autogain. 1704 | */ 1705 | #ifdef ARDUINO 1706 | int ov7670_s_autogain(struct v4l2_subdev *sd, int value) 1707 | #else 1708 | static int ov7670_s_autogain(struct v4l2_subdev *sd, int value) 1709 | #endif 1710 | { 1711 | int ret; 1712 | unsigned char com8; 1713 | 1714 | ret = ov7670_read(sd, REG_COM8, &com8); 1715 | if (ret == 0) { 1716 | if (value) 1717 | com8 |= COM8_AGC; 1718 | else 1719 | com8 &= ~COM8_AGC; 1720 | ret = ov7670_write(sd, REG_COM8, com8); 1721 | } 1722 | return ret; 1723 | } 1724 | 1725 | #ifdef ARDUINO 1726 | int ov7670_s_exp(struct v4l2_subdev *sd, int value) 1727 | #else 1728 | static int ov7670_s_exp(struct v4l2_subdev *sd, int value) 1729 | #endif 1730 | { 1731 | int ret; 1732 | unsigned char com1, com8, aech, aechh; 1733 | 1734 | ret = ov7670_read(sd, REG_COM1, &com1) + 1735 | ov7670_read(sd, REG_COM8, &com8) + 1736 | ov7670_read(sd, REG_AECHH, &aechh); 1737 | if (ret) 1738 | return ret; 1739 | 1740 | com1 = (com1 & 0xfc) | (value & 0x03); 1741 | aech = (value >> 2) & 0xff; 1742 | aechh = (aechh & 0xc0) | ((value >> 10) & 0x3f); 1743 | ret = ov7670_write(sd, REG_COM1, com1) + 1744 | ov7670_write(sd, REG_AECH, aech) + 1745 | ov7670_write(sd, REG_AECHH, aechh); 1746 | /* Have to turn off AEC as well */ 1747 | if (ret == 0) 1748 | ret = ov7670_write(sd, REG_COM8, com8 & ~COM8_AEC); 1749 | return ret; 1750 | } 1751 | 1752 | /* 1753 | * Tweak autoexposure. 1754 | */ 1755 | #ifdef ARDUINO 1756 | int ov7670_s_autoexp(struct v4l2_subdev *sd, int value) 1757 | #else 1758 | static int ov7670_s_autoexp(struct v4l2_subdev *sd, 1759 | enum v4l2_exposure_auto_type value) 1760 | #endif 1761 | { 1762 | int ret; 1763 | unsigned char com8; 1764 | 1765 | ret = ov7670_read(sd, REG_COM8, &com8); 1766 | if (ret == 0) { 1767 | if (value == V4L2_EXPOSURE_AUTO) 1768 | com8 |= COM8_AEC; 1769 | else 1770 | com8 &= ~COM8_AEC; 1771 | ret = ov7670_write(sd, REG_COM8, com8); 1772 | } 1773 | return ret; 1774 | } 1775 | 1776 | #ifndef ARDUINO 1777 | static const char * const ov7670_test_pattern_menu[] = { 1778 | "No test output", 1779 | "Shifting \"1\"", 1780 | "8-bar color bar", 1781 | "Fade to gray color bar", 1782 | }; 1783 | #endif 1784 | 1785 | #ifdef ARDUINO 1786 | int ov7670_s_test_pattern(struct v4l2_subdev *sd, int value) 1787 | #else 1788 | static int ov7670_s_test_pattern(struct v4l2_subdev *sd, int value) 1789 | #endif 1790 | { 1791 | int ret; 1792 | 1793 | ret = ov7670_update_bits(sd, REG_SCALING_XSC, TEST_PATTTERN_0, 1794 | value & BIT(0) ? TEST_PATTTERN_0 : 0); 1795 | if (ret) 1796 | return ret; 1797 | 1798 | return ov7670_update_bits(sd, REG_SCALING_YSC, TEST_PATTTERN_1, 1799 | value & BIT(1) ? TEST_PATTTERN_1 : 0); 1800 | } 1801 | 1802 | #ifndef ARDUINO 1803 | static int ov7670_g_volatile_ctrl(struct v4l2_ctrl *ctrl) 1804 | { 1805 | struct v4l2_subdev *sd = to_sd(ctrl); 1806 | struct ov7670_info *info = to_state(sd); 1807 | 1808 | switch (ctrl->id) { 1809 | case V4L2_CID_AUTOGAIN: 1810 | return ov7670_g_gain(sd, &info->gain->val); 1811 | } 1812 | return -EINVAL; 1813 | } 1814 | 1815 | static int ov7670_s_ctrl(struct v4l2_ctrl *ctrl) 1816 | { 1817 | struct v4l2_subdev *sd = to_sd(ctrl); 1818 | struct ov7670_info *info = to_state(sd); 1819 | 1820 | switch (ctrl->id) { 1821 | case V4L2_CID_BRIGHTNESS: 1822 | return ov7670_s_brightness(sd, ctrl->val); 1823 | case V4L2_CID_CONTRAST: 1824 | return ov7670_s_contrast(sd, ctrl->val); 1825 | case V4L2_CID_SATURATION: 1826 | return ov7670_s_sat_hue(sd, 1827 | info->saturation->val, info->hue->val); 1828 | case V4L2_CID_VFLIP: 1829 | return ov7670_s_vflip(sd, ctrl->val); 1830 | case V4L2_CID_HFLIP: 1831 | return ov7670_s_hflip(sd, ctrl->val); 1832 | case V4L2_CID_AUTOGAIN: 1833 | /* Only set manual gain if auto gain is not explicitly 1834 | turned on. */ 1835 | if (!ctrl->val) { 1836 | /* ov7670_s_gain turns off auto gain */ 1837 | return ov7670_s_gain(sd, info->gain->val); 1838 | } 1839 | return ov7670_s_autogain(sd, ctrl->val); 1840 | case V4L2_CID_EXPOSURE_AUTO: 1841 | /* Only set manual exposure if auto exposure is not explicitly 1842 | turned on. */ 1843 | if (ctrl->val == V4L2_EXPOSURE_MANUAL) { 1844 | /* ov7670_s_exp turns off auto exposure */ 1845 | return ov7670_s_exp(sd, info->exposure->val); 1846 | } 1847 | return ov7670_s_autoexp(sd, ctrl->val); 1848 | case V4L2_CID_TEST_PATTERN: 1849 | return ov7670_s_test_pattern(sd, ctrl->val); 1850 | } 1851 | return -EINVAL; 1852 | } 1853 | 1854 | static const struct v4l2_ctrl_ops ov7670_ctrl_ops = { 1855 | .s_ctrl = ov7670_s_ctrl, 1856 | .g_volatile_ctrl = ov7670_g_volatile_ctrl, 1857 | }; 1858 | #endif 1859 | 1860 | #ifdef CONFIG_VIDEO_ADV_DEBUG 1861 | static int ov7670_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg) 1862 | { 1863 | unsigned char val = 0; 1864 | int ret; 1865 | 1866 | ret = ov7670_read(sd, reg->reg & 0xff, &val); 1867 | reg->val = val; 1868 | reg->size = 1; 1869 | return ret; 1870 | } 1871 | 1872 | static int ov7670_s_register(struct v4l2_subdev *sd, const struct v4l2_dbg_register *reg) 1873 | { 1874 | ov7670_write(sd, reg->reg & 0xff, reg->val & 0xff); 1875 | return 0; 1876 | } 1877 | #endif 1878 | 1879 | static void ov7670_power_on(struct v4l2_subdev *sd) 1880 | { 1881 | struct ov7670_info *info = to_state(sd); 1882 | 1883 | if (info->on) 1884 | return; 1885 | 1886 | #ifndef ARDUINO 1887 | clk_prepare_enable(info->clk); 1888 | 1889 | if (info->pwdn_gpio) 1890 | gpiod_set_value(info->pwdn_gpio, 0); 1891 | if (info->resetb_gpio) { 1892 | gpiod_set_value(info->resetb_gpio, 1); 1893 | usleep_range(500, 1000); 1894 | gpiod_set_value(info->resetb_gpio, 0); 1895 | } 1896 | if (info->pwdn_gpio || info->resetb_gpio || info->clk) 1897 | usleep_range(3000, 5000); 1898 | #endif 1899 | 1900 | info->on = true; 1901 | } 1902 | 1903 | static void ov7670_power_off(struct v4l2_subdev *sd) 1904 | { 1905 | struct ov7670_info *info = to_state(sd); 1906 | 1907 | if (!info->on) 1908 | return; 1909 | 1910 | #ifndef ARDUINO 1911 | clk_disable_unprepare(info->clk); 1912 | 1913 | if (info->pwdn_gpio) 1914 | gpiod_set_value(info->pwdn_gpio, 1); 1915 | #endif 1916 | 1917 | info->on = false; 1918 | } 1919 | 1920 | #ifdef ARDUINO 1921 | int ov7670_s_power(struct v4l2_subdev *sd, int on) 1922 | #else 1923 | static int ov7670_s_power(struct v4l2_subdev *sd, int on) 1924 | #endif 1925 | { 1926 | struct ov7670_info *info = to_state(sd); 1927 | 1928 | if (info->on == on) 1929 | return 0; 1930 | 1931 | if (on) { 1932 | ov7670_power_on (sd); 1933 | ov7670_init(sd, 0); 1934 | ov7670_apply_fmt(sd); 1935 | ov7675_apply_framerate(sd); 1936 | #ifndef ARDUINO 1937 | v4l2_ctrl_handler_setup(&info->hdl); 1938 | #endif 1939 | } else { 1940 | ov7670_power_off (sd); 1941 | } 1942 | 1943 | return 0; 1944 | } 1945 | 1946 | #ifndef ARDUINO 1947 | 1948 | static void ov7670_get_default_format(struct v4l2_subdev *sd, 1949 | struct v4l2_mbus_framefmt *format) 1950 | { 1951 | struct ov7670_info *info = to_state(sd); 1952 | 1953 | format->width = info->devtype->win_sizes[0].width; 1954 | format->height = info->devtype->win_sizes[0].height; 1955 | format->colorspace = info->fmt->colorspace; 1956 | format->code = info->fmt->mbus_code; 1957 | format->field = V4L2_FIELD_NONE; 1958 | } 1959 | 1960 | #ifdef CONFIG_VIDEO_V4L2_SUBDEV_API 1961 | static int ov7670_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) 1962 | { 1963 | struct v4l2_mbus_framefmt *format = 1964 | v4l2_subdev_get_try_format(sd, fh->pad, 0); 1965 | 1966 | ov7670_get_default_format(sd, format); 1967 | 1968 | return 0; 1969 | } 1970 | #endif 1971 | 1972 | /* ----------------------------------------------------------------------- */ 1973 | 1974 | static const struct v4l2_subdev_core_ops ov7670_core_ops = { 1975 | .reset = ov7670_reset, 1976 | .init = ov7670_init, 1977 | .s_power = ov7670_s_power, 1978 | .log_status = v4l2_ctrl_subdev_log_status, 1979 | .subscribe_event = v4l2_ctrl_subdev_subscribe_event, 1980 | .unsubscribe_event = v4l2_event_subdev_unsubscribe, 1981 | #ifdef CONFIG_VIDEO_ADV_DEBUG 1982 | .g_register = ov7670_g_register, 1983 | .s_register = ov7670_s_register, 1984 | #endif 1985 | }; 1986 | 1987 | static const struct v4l2_subdev_video_ops ov7670_video_ops = { 1988 | .s_frame_interval = ov7670_s_frame_interval, 1989 | .g_frame_interval = ov7670_g_frame_interval, 1990 | }; 1991 | 1992 | static const struct v4l2_subdev_pad_ops ov7670_pad_ops = { 1993 | .enum_frame_interval = ov7670_enum_frame_interval, 1994 | .enum_frame_size = ov7670_enum_frame_size, 1995 | .enum_mbus_code = ov7670_enum_mbus_code, 1996 | .get_fmt = ov7670_get_fmt, 1997 | .set_fmt = ov7670_set_fmt, 1998 | }; 1999 | 2000 | static const struct v4l2_subdev_ops ov7670_ops = { 2001 | .core = &ov7670_core_ops, 2002 | .video = &ov7670_video_ops, 2003 | .pad = &ov7670_pad_ops, 2004 | }; 2005 | 2006 | #ifdef CONFIG_VIDEO_V4L2_SUBDEV_API 2007 | static const struct v4l2_subdev_internal_ops ov7670_subdev_internal_ops = { 2008 | .open = ov7670_open, 2009 | }; 2010 | #endif 2011 | 2012 | #endif 2013 | 2014 | /* ----------------------------------------------------------------------- */ 2015 | 2016 | static const struct ov7670_devtype ov7670_devdata[] = { 2017 | [MODEL_OV7670] = { 2018 | .win_sizes = ov7670_win_sizes, 2019 | .n_win_sizes = ARRAY_SIZE(ov7670_win_sizes), 2020 | .set_framerate = ov7670_set_framerate_legacy, 2021 | .get_framerate = ov7670_get_framerate_legacy, 2022 | }, 2023 | [MODEL_OV7675] = { 2024 | .win_sizes = ov7675_win_sizes, 2025 | .n_win_sizes = ARRAY_SIZE(ov7675_win_sizes), 2026 | .set_framerate = ov7675_set_framerate, 2027 | .get_framerate = ov7675_get_framerate, 2028 | }, 2029 | }; 2030 | 2031 | #ifdef ARDUINO 2032 | 2033 | void* ov7670_alloc() 2034 | { 2035 | return calloc(sizeof(struct ov7670_info), sizeof(uint8_t)); 2036 | } 2037 | 2038 | void ov7670_free(void* p) 2039 | { 2040 | if (p) { 2041 | free(p); 2042 | } 2043 | } 2044 | 2045 | void ov7670_configure(struct v4l2_subdev *sd, int devtype, int format, int wsize, int clock_speed, int pll_bypass, int pclk_hb_disable) 2046 | { 2047 | struct ov7670_info *info = to_state(sd); 2048 | 2049 | info->devtype = &ov7670_devdata[devtype]; 2050 | info->fmt = &ov7670_formats[format]; 2051 | info->wsize = &info->devtype->win_sizes[wsize]; 2052 | info->clock_speed = clock_speed; 2053 | info->pll_bypass = pll_bypass; 2054 | info->pclk_hb_disable = pclk_hb_disable; 2055 | } 2056 | 2057 | #else 2058 | 2059 | static int ov7670_init_gpio(struct i2c_client *client, struct ov7670_info *info) 2060 | { 2061 | info->pwdn_gpio = devm_gpiod_get_optional(&client->dev, "powerdown", 2062 | GPIOD_OUT_LOW); 2063 | if (IS_ERR(info->pwdn_gpio)) { 2064 | dev_info(&client->dev, "can't get %s GPIO\n", "powerdown"); 2065 | return PTR_ERR(info->pwdn_gpio); 2066 | } 2067 | 2068 | info->resetb_gpio = devm_gpiod_get_optional(&client->dev, "reset", 2069 | GPIOD_OUT_LOW); 2070 | if (IS_ERR(info->resetb_gpio)) { 2071 | dev_info(&client->dev, "can't get %s GPIO\n", "reset"); 2072 | return PTR_ERR(info->resetb_gpio); 2073 | } 2074 | 2075 | usleep_range(3000, 5000); 2076 | 2077 | return 0; 2078 | } 2079 | 2080 | /* 2081 | * ov7670_parse_dt() - Parse device tree to collect mbus configuration 2082 | * properties 2083 | */ 2084 | static int ov7670_parse_dt(struct device *dev, 2085 | struct ov7670_info *info) 2086 | { 2087 | struct fwnode_handle *fwnode = dev_fwnode(dev); 2088 | struct v4l2_fwnode_endpoint bus_cfg = { .bus_type = 0 }; 2089 | struct fwnode_handle *ep; 2090 | int ret; 2091 | 2092 | if (!fwnode) 2093 | return -EINVAL; 2094 | 2095 | info->pclk_hb_disable = false; 2096 | if (fwnode_property_present(fwnode, "ov7670,pclk-hb-disable")) 2097 | info->pclk_hb_disable = true; 2098 | 2099 | ep = fwnode_graph_get_next_endpoint(fwnode, NULL); 2100 | if (!ep) 2101 | return -EINVAL; 2102 | 2103 | ret = v4l2_fwnode_endpoint_parse(ep, &bus_cfg); 2104 | fwnode_handle_put(ep); 2105 | if (ret) 2106 | return ret; 2107 | 2108 | if (bus_cfg.bus_type != V4L2_MBUS_PARALLEL) { 2109 | dev_err(dev, "Unsupported media bus type\n"); 2110 | return ret; 2111 | } 2112 | info->mbus_config = bus_cfg.bus.parallel.flags; 2113 | 2114 | return 0; 2115 | } 2116 | 2117 | static int ov7670_probe(struct i2c_client *client, 2118 | const struct i2c_device_id *id) 2119 | { 2120 | struct v4l2_fract tpf; 2121 | struct v4l2_subdev *sd; 2122 | struct ov7670_info *info; 2123 | int ret; 2124 | 2125 | info = devm_kzalloc(&client->dev, sizeof(*info), GFP_KERNEL); 2126 | if (info == NULL) 2127 | return -ENOMEM; 2128 | sd = &info->sd; 2129 | v4l2_i2c_subdev_init(sd, client, &ov7670_ops); 2130 | 2131 | #ifdef CONFIG_VIDEO_V4L2_SUBDEV_API 2132 | sd->internal_ops = &ov7670_subdev_internal_ops; 2133 | sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; 2134 | #endif 2135 | 2136 | info->clock_speed = 30; /* default: a guess */ 2137 | 2138 | if (dev_fwnode(&client->dev)) { 2139 | ret = ov7670_parse_dt(&client->dev, info); 2140 | if (ret) 2141 | return ret; 2142 | 2143 | } else if (client->dev.platform_data) { 2144 | struct ov7670_config *config = client->dev.platform_data; 2145 | 2146 | /* 2147 | * Must apply configuration before initializing device, because it 2148 | * selects I/O method. 2149 | */ 2150 | info->min_width = config->min_width; 2151 | info->min_height = config->min_height; 2152 | info->use_smbus = config->use_smbus; 2153 | 2154 | if (config->clock_speed) 2155 | info->clock_speed = config->clock_speed; 2156 | 2157 | if (config->pll_bypass) 2158 | info->pll_bypass = true; 2159 | 2160 | if (config->pclk_hb_disable) 2161 | info->pclk_hb_disable = true; 2162 | } 2163 | 2164 | info->clk = devm_clk_get(&client->dev, "xclk"); /* optional */ 2165 | if (IS_ERR(info->clk)) { 2166 | ret = PTR_ERR(info->clk); 2167 | if (ret == -ENOENT) 2168 | info->clk = NULL; 2169 | else 2170 | return ret; 2171 | } 2172 | 2173 | ret = ov7670_init_gpio(client, info); 2174 | if (ret) 2175 | return ret; 2176 | 2177 | ov7670_power_on(sd); 2178 | 2179 | if (info->clk) { 2180 | info->clock_speed = clk_get_rate(info->clk) / 1000000; 2181 | if (info->clock_speed < 10 || info->clock_speed > 48) { 2182 | ret = -EINVAL; 2183 | goto power_off; 2184 | } 2185 | } 2186 | 2187 | /* Make sure it's an ov7670 */ 2188 | ret = ov7670_detect(sd); 2189 | if (ret) { 2190 | v4l_dbg(1, debug, client, 2191 | "chip found @ 0x%x (%s) is not an ov7670 chip.\n", 2192 | client->addr << 1, client->adapter->name); 2193 | goto power_off; 2194 | } 2195 | v4l_info(client, "chip found @ 0x%02x (%s)\n", 2196 | client->addr << 1, client->adapter->name); 2197 | 2198 | info->devtype = &ov7670_devdata[id->driver_data]; 2199 | info->fmt = &ov7670_formats[0]; 2200 | info->wsize = &info->devtype->win_sizes[0]; 2201 | 2202 | ov7670_get_default_format(sd, &info->format); 2203 | 2204 | info->clkrc = 0; 2205 | 2206 | /* Set default frame rate to 30 fps */ 2207 | tpf.numerator = 1; 2208 | tpf.denominator = 30; 2209 | info->devtype->set_framerate(sd, &tpf); 2210 | 2211 | v4l2_ctrl_handler_init(&info->hdl, 10); 2212 | v4l2_ctrl_new_std(&info->hdl, &ov7670_ctrl_ops, 2213 | V4L2_CID_BRIGHTNESS, 0, 255, 1, 128); 2214 | v4l2_ctrl_new_std(&info->hdl, &ov7670_ctrl_ops, 2215 | V4L2_CID_CONTRAST, 0, 127, 1, 64); 2216 | v4l2_ctrl_new_std(&info->hdl, &ov7670_ctrl_ops, 2217 | V4L2_CID_VFLIP, 0, 1, 1, 0); 2218 | v4l2_ctrl_new_std(&info->hdl, &ov7670_ctrl_ops, 2219 | V4L2_CID_HFLIP, 0, 1, 1, 0); 2220 | info->saturation = v4l2_ctrl_new_std(&info->hdl, &ov7670_ctrl_ops, 2221 | V4L2_CID_SATURATION, 0, 256, 1, 128); 2222 | info->hue = v4l2_ctrl_new_std(&info->hdl, &ov7670_ctrl_ops, 2223 | V4L2_CID_HUE, -180, 180, 5, 0); 2224 | info->gain = v4l2_ctrl_new_std(&info->hdl, &ov7670_ctrl_ops, 2225 | V4L2_CID_GAIN, 0, 255, 1, 128); 2226 | info->auto_gain = v4l2_ctrl_new_std(&info->hdl, &ov7670_ctrl_ops, 2227 | V4L2_CID_AUTOGAIN, 0, 1, 1, 1); 2228 | info->exposure = v4l2_ctrl_new_std(&info->hdl, &ov7670_ctrl_ops, 2229 | V4L2_CID_EXPOSURE, 0, 65535, 1, 500); 2230 | info->auto_exposure = v4l2_ctrl_new_std_menu(&info->hdl, &ov7670_ctrl_ops, 2231 | V4L2_CID_EXPOSURE_AUTO, V4L2_EXPOSURE_MANUAL, 0, 2232 | V4L2_EXPOSURE_AUTO); 2233 | v4l2_ctrl_new_std_menu_items(&info->hdl, &ov7670_ctrl_ops, 2234 | V4L2_CID_TEST_PATTERN, 2235 | ARRAY_SIZE(ov7670_test_pattern_menu) - 1, 0, 0, 2236 | ov7670_test_pattern_menu); 2237 | sd->ctrl_handler = &info->hdl; 2238 | if (info->hdl.error) { 2239 | ret = info->hdl.error; 2240 | 2241 | goto hdl_free; 2242 | } 2243 | /* 2244 | * We have checked empirically that hw allows to read back the gain 2245 | * value chosen by auto gain but that's not the case for auto exposure. 2246 | */ 2247 | v4l2_ctrl_auto_cluster(2, &info->auto_gain, 0, true); 2248 | v4l2_ctrl_auto_cluster(2, &info->auto_exposure, 2249 | V4L2_EXPOSURE_MANUAL, false); 2250 | v4l2_ctrl_cluster(2, &info->saturation); 2251 | 2252 | #if defined(CONFIG_MEDIA_CONTROLLER) 2253 | info->pad.flags = MEDIA_PAD_FL_SOURCE; 2254 | info->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; 2255 | ret = media_entity_pads_init(&info->sd.entity, 1, &info->pad); 2256 | if (ret < 0) 2257 | goto hdl_free; 2258 | #endif 2259 | 2260 | v4l2_ctrl_handler_setup(&info->hdl); 2261 | 2262 | ret = v4l2_async_register_subdev(&info->sd); 2263 | if (ret < 0) 2264 | goto entity_cleanup; 2265 | 2266 | ov7670_power_off(sd); 2267 | return 0; 2268 | 2269 | entity_cleanup: 2270 | media_entity_cleanup(&info->sd.entity); 2271 | hdl_free: 2272 | v4l2_ctrl_handler_free(&info->hdl); 2273 | power_off: 2274 | ov7670_power_off(sd); 2275 | return ret; 2276 | } 2277 | 2278 | static int ov7670_remove(struct i2c_client *client) 2279 | { 2280 | struct v4l2_subdev *sd = i2c_get_clientdata(client); 2281 | struct ov7670_info *info = to_state(sd); 2282 | 2283 | v4l2_async_unregister_subdev(sd); 2284 | v4l2_ctrl_handler_free(&info->hdl); 2285 | media_entity_cleanup(&info->sd.entity); 2286 | ov7670_power_off(sd); 2287 | return 0; 2288 | } 2289 | 2290 | static const struct i2c_device_id ov7670_id[] = { 2291 | { "ov7670", MODEL_OV7670 }, 2292 | { "ov7675", MODEL_OV7675 }, 2293 | { } 2294 | }; 2295 | MODULE_DEVICE_TABLE(i2c, ov7670_id); 2296 | 2297 | #if IS_ENABLED(CONFIG_OF) 2298 | static const struct of_device_id ov7670_of_match[] = { 2299 | { .compatible = "ovti,ov7670", }, 2300 | { /* sentinel */ }, 2301 | }; 2302 | MODULE_DEVICE_TABLE(of, ov7670_of_match); 2303 | #endif 2304 | 2305 | static struct i2c_driver ov7670_driver = { 2306 | .driver = { 2307 | .name = "ov7670", 2308 | .of_match_table = of_match_ptr(ov7670_of_match), 2309 | }, 2310 | .probe = ov7670_probe, 2311 | .remove = ov7670_remove, 2312 | .id_table = ov7670_id, 2313 | }; 2314 | 2315 | module_i2c_driver(ov7670_driver); 2316 | 2317 | #endif 2318 | -------------------------------------------------------------------------------- /src/utility/ov7670.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | /* 3 | * A V4L2 driver for OmniVision OV7670 cameras. 4 | * 5 | * Copyright 2010 One Laptop Per Child 6 | */ 7 | 8 | #ifndef __OV7670_H 9 | #define __OV7670_H 10 | 11 | struct ov7670_config { 12 | int min_width; /* Filter out smaller sizes */ 13 | int min_height; /* Filter out smaller sizes */ 14 | int clock_speed; /* External clock speed (MHz) */ 15 | bool use_smbus; /* Use smbus I/O instead of I2C */ 16 | bool pll_bypass; /* Choose whether to bypass the PLL */ 17 | bool pclk_hb_disable; /* Disable toggling pixclk during horizontal blanking */ 18 | }; 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /src/utility/ov7670_arduino_shim.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | /* 3 | * This file is part of the Arduino_OX767X library. 4 | * Copyright (c) 2020 Arduino SA. All rights reserved. 5 | */ 6 | #include 7 | #include 8 | 9 | #ifndef OV760_DEBUG 10 | // #define OV760_DEBUG 11 | #endif 12 | 13 | extern "C" { 14 | void msleep(unsigned long ms) 15 | { 16 | delay(ms); 17 | } 18 | 19 | int arduino_i2c_read(unsigned short address, unsigned char reg, unsigned char *value) 20 | { 21 | #ifdef OV760_DEBUG 22 | Serial.print("arduino_i2c_read: address = 0x"); 23 | Serial.print(address, HEX); 24 | Serial.print(", reg = 0x"); 25 | Serial.print(reg, HEX); 26 | #endif 27 | 28 | Wire.beginTransmission(address); 29 | Wire.write(reg); 30 | if (Wire.endTransmission() != 0) { 31 | #ifdef OV760_DEBUG 32 | Serial.println(); 33 | #endif 34 | return -1; 35 | } 36 | 37 | if (Wire.requestFrom(address, 1) != 1) { 38 | #ifdef OV760_DEBUG 39 | Serial.println(); 40 | #endif 41 | return -1; 42 | } 43 | 44 | *value = Wire.read(); 45 | 46 | #ifdef OV760_DEBUG 47 | Serial.print(", value = 0x"); 48 | Serial.println(*value, HEX); 49 | #endif 50 | 51 | return 0; 52 | } 53 | 54 | int arduino_i2c_write(unsigned short address, unsigned char reg, unsigned char value) 55 | { 56 | #ifdef OV760_DEBUG 57 | Serial.print("arduino_i2c_write: address = 0x"); 58 | Serial.print(address, HEX); 59 | Serial.print(", reg = 0x"); 60 | Serial.print(reg, HEX); 61 | Serial.print(", value = 0x"); 62 | Serial.println(value, HEX); 63 | #endif 64 | 65 | Wire.beginTransmission(address); 66 | Wire.write(reg); 67 | Wire.write(value); 68 | 69 | if (Wire.endTransmission() != 0) { 70 | return -1; 71 | } 72 | 73 | return 0; 74 | } 75 | }; 76 | --------------------------------------------------------------------------------