Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753907AbdFSHpE (ORCPT ); Mon, 19 Jun 2017 03:45:04 -0400 Received: from mail-eopbgr00092.outbound.protection.outlook.com ([40.107.0.92]:14664 "EHLO EUR02-AM5-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1753787AbdFSHnz (ORCPT ); Mon, 19 Jun 2017 03:43:55 -0400 Authentication-Results: vger.kernel.org; dkim=none (message not signed) header.d=none;vger.kernel.org; dmarc=none action=none header.from=axentia.se; From: Peter Rosin To: linux-kernel@vger.kernel.org Cc: Peter Rosin , Boris Brezillon , David Airlie , Daniel Vetter , Jani Nikula , Sean Paul , dri-devel@lists.freedesktop.org Subject: [PATCH v3 0/3] drm: atmel-hlcdc: clut support Date: Mon, 19 Jun 2017 09:44:23 +0200 Message-Id: <1497858266-17844-1-git-send-email-peda@axentia.se> X-Mailer: git-send-email 2.1.4 MIME-Version: 1.0 Content-Type: text/plain X-Originating-IP: [81.224.171.159] X-ClientProxiedBy: DB6PR07CA0129.eurprd07.prod.outlook.com (2603:10a6:6:16::22) To DB6PR0202MB2549.eurprd02.prod.outlook.com (2603:10a6:4:1b::7) X-MS-PublicTrafficType: Email X-MS-Office365-Filtering-Correlation-Id: ecc94c07-74b2-4d8d-eff8-08d4b6e6eb5b X-Microsoft-Antispam: UriScan:;BCL:0;PCL:0;RULEID:(22001)(201703131423075);SRVR:DB6PR0202MB2549; X-Microsoft-Exchange-Diagnostics: 1;DB6PR0202MB2549;3:BS6ifo+zXXbcTP/HeGoJc8MW/SQRopUhud7QWqXaz3QNqN61uOmKVoiWGV7/f4K98PtY6/pdvW3bV9EcPWXA294Sn7SStTIIZBAtvhJe1TYYEx9/0VrTR3On42yq3tFScdE13xBYknppU8zEF/3V4A9ggKcR1yR9ykryLrEEAOsb8Ws7tS+4lYp6B/Keo1YXQKL08ZKOEObUTfbQi8F8ewKsfKUokt2qPpDwO2aBKA2AieB3MOxntzaMwS/tyO3Xc4T1LIbLcEhJksUCyHHGQU1bl/iPM45tZPAmaC8yjhV5/C54DmGxck2PBRrc24cM;25:Rj8ymipAqP1cnS3OcauVzkI3dJSviTBDeMUjtoUvgwyaGGdAAsyK6V+obcyqDa2vjji8x3gF4YaT9NkF/V5INMuQqM8TwBc89qkrIIU7Rc+fJ8WNtXzdANwuETkXdWZkIvPuBwzC774efa4e3SU4NN81hcNhoi7wvrVhNSuXTadnvqnzreMtEQWOq3vwq4q+bCv7vr5KXnukdvkPmZvUJJICOLts+z+Yg2Hx8zntTBBmt+ZGFx1MiV5N78I/Lvzjhd9TSArwGuruisttSQRzmASe51pAO2YjiDHtnz7Uwo4RoWQa5QGYv2O10/niKY47k75ZZk5GSMNljonUhs/NzutMAceWtb9pqCOv12LmXggTPsAtGSPi/ZbZXIWP7ECnyfDYgC4dNhHZlae8ODtr3jEInDFQoiowkf6nUJdRQVLyRiWZ1yJiPTj3yBes5oUxco+rb1+3/70t0xtzp5SUNjrNszJLuiL+GadpqbGnxRI= X-MS-TrafficTypeDiagnostic: DB6PR0202MB2549: X-Microsoft-Exchange-Diagnostics: 1;DB6PR0202MB2549;31:iF4kSUi8VrtYBQqxv3Dd0AO8TjG+j/Cs+2NpkucR56YtF1wRNujCOl9AOo7XMmX2fW8UPbowg4r2hpe2ZH2BEYqUQF9MYoX9xH4fjCjxK+jyAxWjC63XzKMmFiyhKCWSjUiWyDNyWO/ejyLDglaukqd7ztG12WTkcoWyykjIXDN/XjEQplnieMy225APydDuudhlDx96oE7XOsuUVvYg81+GnZSLESArkeTGZy3Z4Fg= X-Microsoft-Antispam-PRVS: X-Exchange-Antispam-Report-Test: UriScan:(278428928389397)(166708455590820)(21532816269658)(8415204561270)(5213294742642); X-Exchange-Antispam-Report-CFA-Test: BCL:0;PCL:0;RULEID:(100000700101)(100105000095)(100000701101)(100105300095)(100000702101)(100105100095)(6040450)(601004)(2401047)(8121501046)(5005006)(100000703101)(100105400095)(10201501046)(3002001)(93006095)(93001095)(6041248)(20161123562025)(20161123555025)(20161123564025)(2016111802025)(201703131423075)(201702281528075)(201703061421075)(201703061406153)(20161123558100)(20161123560025)(6043046)(6072148)(100000704101)(100105200095)(100000705101)(100105500095);SRVR:DB6PR0202MB2549;BCL:0;PCL:0;RULEID:(100000800101)(100110000095)(100000801101)(100110300095)(100000802101)(100110100095)(100000803101)(100110400095)(100000804101)(100110200095)(100000805101)(100110500095);SRVR:DB6PR0202MB2549; X-Microsoft-Exchange-Diagnostics: =?us-ascii?Q?1;DB6PR0202MB2549;4:w1A0SNcq3A7uHXpz1yJLgSI6B/UqG/v73dild4O1?= =?us-ascii?Q?mP+xffwYocTbLOMJXhsmiFvihANPie6KupXhC6nVGm+JTeJ6ISMYqqkcywh7?= =?us-ascii?Q?HX3UeAXfgqAn9ppA1yeSEsJNfya7qy5WTYCQGpfX+qEPqTIDpS7a09KfypIf?= =?us-ascii?Q?yjrjY3qlIWxACx+KpRK9IdqiVgeJj6px9CyxRFIhKoDpk12gnLqzEEznwfb0?= =?us-ascii?Q?6/YjZo2xBJzaCBlyEqkFNi9YOz7I/ntCgmvsRTJ0tyUnYNADASaxsRGYIzr0?= =?us-ascii?Q?Edak39Tg8MtytFtfiWIUa1ttKKXGLmOvqNxUA4yoiULO1JvJf1HVHsZGjOXL?= =?us-ascii?Q?RLUFwYc6KVYEpWdqANbUAWhtxSK756feIAnt20h7zMqSXUL2skYIb2O2Tn6u?= =?us-ascii?Q?7WAqg3sxIhFt8w97rKjiyFxA4qh3+KafBl4onNwGxAOqF12z+B1/UOdmEMJj?= =?us-ascii?Q?pFWrWXoCC0xvhS4+CBKuLLIC9eveLYbie5gNVhq0zofXF0LKtTLf/iEb82uh?= =?us-ascii?Q?1cyU3ZJtNewm/5yrcWIo36K2lvTVjKtMxUNxE50RFJRcQb3qW7UWf0VfDXdv?= =?us-ascii?Q?yHmO010zPcrdZaaGk8A/kisL79W8tdN/QEZCDBlL0O6nTtuTO6CRCbCGhVm9?= =?us-ascii?Q?H4xPlr1xqF8dwQE4wCvpk6VRUwAoeFp2q2J3g4/OWlKy9G1c5MnQy6jBhPFE?= =?us-ascii?Q?fEWV9FJR5Yp0cgwxt8994AvmyhRwCNxMIoZCRiO5L1hqDMYB4+hZ0tL6cPHG?= =?us-ascii?Q?bqYw8mqfJWsHoMRL44Npbr+d7w0IfySIlg+OLvsQfpJo5A7ktEPRFJjxMiR2?= =?us-ascii?Q?o1JtM9LlovXFJ5Zf254ULMXN6935sI0KETmUwDTI0JW+09nqH4xMduDS/K9V?= =?us-ascii?Q?7wtJn+ieiKoPG7XBtnjfKTyZT3KhpIdVIz6tqe4K1z7Zuad6E0Ww6H/eC+Dg?= =?us-ascii?Q?WwuOP3RtY04FBbjCT3pVJd5nhTsmurc/URWYrUfRt/QVEQqLeDYKucmVWdT1?= =?us-ascii?Q?EUTkW37AOZVF/qjcZMQUEiOFX9Jv+SSanvt+OJci+C/kga+aixn5uKxOMKsu?= =?us-ascii?Q?zllL+LxS2q1M0Sbicg+AoVQOCmFjF0zG1jBqBNdiF3ompoRA6n8BQZPQ3Zpo?= =?us-ascii?Q?tbGpveFIPKRtKjkPuJtUMCvaVVIISWnz505UNH4ERUC7VJRecoCyEuJTIQ/7?= =?us-ascii?Q?uwBvnVbtCGRHW034isjBfxXch9oy1XRQwvJEYnA8RLmmSuX4o+ZF735UsykZ?= =?us-ascii?Q?mHIb4rjgvp40FSwmgiWtyuQqyGhvoyoXxZQtFDt7B2joaFsiNiq5tdsukA/e?= =?us-ascii?Q?2Z4X7wYPKNOloGqUZFi6aWpd7WQAc2zlUuJXb+ykbrBf3v69GgsZU4dEpnEp?= =?us-ascii?Q?+tNzC1ubJbFVB0ob0V+c8mEyfiw=3D?= X-Forefront-PRVS: 0343AC1D30 X-Forefront-Antispam-Report: SFV:NSPM;SFS:(10019020)(4630300001)(6009001)(39410400002)(39830400002)(39400400002)(39450400003)(478600001)(38730400002)(53376002)(50226002)(189998001)(6486002)(19273905006)(7736002)(8676002)(50986999)(966005)(33646002)(36756003)(5003940100001)(81166006)(4326008)(53936002)(2906002)(42186005)(110136004)(3846002)(6306002)(66066001)(86362001)(48376002)(74482002)(5660300001)(2351001)(305945005)(2361001)(6666003)(6916009)(54906002)(25786009)(6506006)(6116002)(6512007)(47776003)(1720100001)(42262002)(563064011);DIR:OUT;SFP:1102;SCL:1;SRVR:DB6PR0202MB2549;H:localhost.localdomain;FPR:;SPF:None;MLV:sfv;LANG:en; X-Microsoft-Exchange-Diagnostics: =?us-ascii?Q?1;DB6PR0202MB2549;23:hxIHZ51/S0Nb1r0/8BuLy3wNHT7XMSCfGQ74cxP?= =?us-ascii?Q?oldkEiB/Q8H1S5nLTLM+c0Gbr77Nq1FpmgEcumcBW5V6bDvsEFfAUkFsEdJP?= =?us-ascii?Q?GIO65iIqEtIjWXXWV/O/kem7+MjGYoHkCSojOh2BxvSQkqiz0hu5rOLOJOb6?= =?us-ascii?Q?82GMGoJqbOuAmRq+/kqUPCDDTxlGq6eC/MV5VCk2ypj+Po5rrY+Ms7o7agfc?= =?us-ascii?Q?cZXUqyUh7YtQN7ZJwiD/KhwI5vaoTfbfV1qNehAyxfvfM6QmWTHxhBHy8Bsv?= =?us-ascii?Q?ldKpjQJgOa+b/+T8z+cQajZ+WPDZghHySEA8+s00hWeMbUZKKh27d7nzDuNR?= =?us-ascii?Q?cvEBKV3FokJBrE3PE0t4ZKTRNeX2zc1UHVU6SMBu7qbAs4pcCM6uzGPDn+b4?= =?us-ascii?Q?PHRfJaqaZwXa4bMkMsEqqJDkOayTszH2loaplSoMyoa/8++8cWwwgLuKfX2B?= =?us-ascii?Q?fTmy0LTbvSVuK0dXnwwkGDCOoW8I14m2Bey3aO20WYyzjnmG3q+E/5bWCzah?= =?us-ascii?Q?doq+GFpwq+wLWF7GlaCceCE6G6dSjjazdTr8ePX3F7+DCCmpDZT/tri/rvgy?= =?us-ascii?Q?yUvkdUdAwt+eCKeWO8ngcJ/VUuE6Xcoj+LnEJOGjJCKZ7lGqy0bOWULRAQ0J?= =?us-ascii?Q?Y0kJWzu78MD4eo/2kYJ6Ut29YJPjZNCKuB2wExu3ZaV+L/KpztZPcrYH0P1G?= =?us-ascii?Q?TLfpaIiBMvP6vnrSx+IFWYgiY8q+lA+xv03rbAWBP9d1Aa1rMJPZ6wxJ1wVe?= =?us-ascii?Q?YPFjC+WZSw7lovZloomOH/Z4cVasCpXhdn3Pryw27Yzf7k7un64eFtAcQY8q?= =?us-ascii?Q?y4WitXPZUQfuahwj94x2yxOKNuT5MpMKB0Q5R6+wAaANiE3qNyX9Fl4w8Sn4?= =?us-ascii?Q?suJAaol6CYbShKXZFso7fZV1AONPovHAN4nmjS0T0GMPJIVwd4B2VfpY2Ti4?= =?us-ascii?Q?V/Bk2kq2XD0Ir7hF3dhsT80ZN2zCmYqx7UVpxRR2QVBmNSIVvQqSdFyS0meh?= =?us-ascii?Q?R1Z392ViJhC92E1X29S3LwqtR1jf/yhvQCqGHNxE8xyoIBVBckdmKKNGlID4?= =?us-ascii?Q?z+buxOFC28FbpIjGcdyHuc8HjV9Tz6FIdk1ED2sZVxws5yM+jm5rpItUqyZS?= =?us-ascii?Q?Tues6iaZVz1/rfCxOngOw4ZiVgM3rLmKYPq3PrX0oKYKZ/+4iWSUDBg=3D?= =?us-ascii?Q?=3D?= X-Microsoft-Exchange-Diagnostics: =?us-ascii?Q?1;DB6PR0202MB2549;6:ZePM3JfSORkcAddwfoIHMn9pCWUhT2lHRml61eA5?= =?us-ascii?Q?jlMiRuxFYT5/dhW237x35d1XBJf7PeIfIWWqjHuTvKpNrQd7GhaIzf/DfJ89?= =?us-ascii?Q?8JEvZqCu5ZH+fk5Sww9Ep+T1mb2hEyq9hF21yW/Z3lj/kELDFGpPOnBV0w4Y?= =?us-ascii?Q?uvl/khQkxpPvZ1u3hKsTOmKq5hBwSXfdjiIu5fUxLY14mp/DD6p52mx7iwQ2?= =?us-ascii?Q?NXC1LJtcpXi8Sz0QiFRG8oF4XPHxr9lWwXD9iP02wW92avxSY/JSAjoYkZyL?= =?us-ascii?Q?oaUH+Sl5fixGGlH3z78UmJHDFeOaIIwdRumuMOQdsUqjSCrvL7AWz91oh1B6?= =?us-ascii?Q?8GbdVuRQb3Rrt30JG+ac2IXBo1kb4D0bwxiz2t/hYKo+kEbfZZ2mMbO9on7i?= =?us-ascii?Q?W8VOHZx5LcGbD24GABi5ND2AZwYjOtLSQXKC4b9unyJhjJEvXmG7kJ1ep3ci?= =?us-ascii?Q?+zZXhkxzgsX8ZwP3M9wDvi6s3PcnF/VgrraLZ2CBd2llAIhULEXmcpfp2KG4?= =?us-ascii?Q?MdhWEloz5Y6k5Ucki9iZ6d6xm74109Gk4c27dhV5tqQon91JGMxAf4tPe2q0?= =?us-ascii?Q?rG9CuiXVe0wa32f8qoup8trQJ7qgKHFkzIw4jtc68/mCTSKqUuqKNxPwe9cA?= =?us-ascii?Q?JIPnecQzP0o/+27QQ8UwhgAGauz/dc0gXK2JkSQAc57jRPlXYb5azoU0qW5+?= =?us-ascii?Q?HILaoyaG9hICMYl1Bs54oKeTG8X0Qtnf7u+l3nK3mygXNjBOq5QwdOpDdVcY?= =?us-ascii?Q?AdDJZbTx8IMfW99sKKxcd8rI5784RxWPL88AQVVE5bJY26Jx4WJEs+3j1r0m?= =?us-ascii?Q?Jo/DntTexmyaGLqi+wQr1pdilzHUmJn3sMU358DJ0SGzbv+DxVJck54QrsvY?= =?us-ascii?Q?Ggyp2nync16CBX/H0wiaPi0xJAIRpUtfATrrRliw2ABzL4DYvY5Vib8/KNJ2?= =?us-ascii?Q?d7Dqc0IODWuHkcoUpNCy/l3Kescc15EfDZk1LkQNRH1olgfik8GFg6ayFgwa?= =?us-ascii?Q?24A=3D?= X-Microsoft-Exchange-Diagnostics: 1;DB6PR0202MB2549;5:hTRfIzS1sPuFYOaSsfAKZdNHymt0X/7fJ2xd/6HHxiJrqLrpIZBnx1QiYiCoOF3e/MHPq2CKFE9CULax66ONOx41JaefHLiUejES27s+J61Tmrbn9phGSjZiGQVZvE1TmsmdwS7ymqPExomTbyfJgIveu5IxqlooiWswb+LnTGSuc9J6Vl/yEKNb0ZJU7ZJz6RZDs5uO/tAFt0Uo4RqOZha7c4orKKlhTcXWeztnT8Lp8Yz12whyoWnOKG6bc26+tL93pDLe0T7uptN27bqaC47MUGUhjIH60537yaU6tP4XRchkozREG7BJgCgvUZLkUrx9Kp72crgWIQD5ueQdR+VHeqwmkTt8WVSXvCXlCRz2Ak6/PmIYdgXDk098WYbkAeyDI8qfgRJdndwAfOieCzhWFLp2Rfz0ykRqGGrqHJaCUBoJsEwuRQfOzfEshhj1N8aVfAMV4snQL8oHXHGKMd97OKw5lFVm2rEwpWZ0rGloMJrlgZb1fImrUYxNW0J9;24:jDPmaqss1Q9Xma22PZ47RTR6vqLofyN4uJidLB0nXvTYZPAL5nay8BVWyJHfWTulN629pF7mhfXdRkMzg10h8QedsIR44B/r+TRb8JDib1M= SpamDiagnosticOutput: 1:99 SpamDiagnosticMetadata: NSPM X-Microsoft-Exchange-Diagnostics: 1;DB6PR0202MB2549;7:C+ZowSB5hjhubBNu0ytYxZDQPXIitwErOurMwEsps+Sy5qzy53/sSCfzuiJxlJkjxvkl/0Dfa7bn4lt4M1t3VNxeyQiEpBOEjNvdkA77FN8ehhrYbwB3UU8Dk4A9h4yTq2VoKjc4w4QgHTzhFhQzZ/suq0K44Xr+2Dalc6SKFQY9PE6WbBaPe8+gzr3+xyyjC4YNuP01O1VKlJGzSLNl8J0DmiyY6+pUaly8TGKRGLyxJz7yNghH9E0/8FKHaLi1loARrUcwgghmyoAErAQ5cOUNlW7R3EHajJ8qfAOqqg1bwDHfN8hNwXEGmZBTQa5UsrVyPogxCKYl+qZvLUkbBOhN8TKiOlpgE7z69ePywjETcp4n4tAO/zNfB3xaLdVIV9y/PqGniejF0ln8rTITmBymUMQmy5szYrj+olqiL6r+8P3SK8HKLFHfSbeBkQ6CIvxMVvnsU4QFMRTMJ6MBzi6k0mFo2uYMsoJy/L4TOrYeCPOlDwTvN/+DBzh+55ud8lN+/ZiVTzTHfI+Fevwh7vozV/uGc01EshNw+HPAHDicqv6mAUoblQefl6cKP7t6hTD0RUABiu6e5bU2GgBbZW4zJsw9eCYKUtvkkqMZ498WggKYQNhBFQQnRtYTYcTPSKdsulVj9n5By0p27mqfIMt7IVYD/cJrg4JPfSbNITAXzodAhpyKD0fvodBJkvOWn2/XJ3iKHhCKYdh2a+PYcs7cRwI9la2TARcHX+Yeq/al2LvJjL7+kp94XaOGTNLaliLjAyCZuBiXfBotmwRD9uWEdHJ9dpaXP5iARoQWNBo= X-OriginatorOrg: axentia.se X-MS-Exchange-CrossTenant-OriginalArrivalTime: 19 Jun 2017 07:43:46.3828 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-Transport-CrossTenantHeadersStamped: DB6PR0202MB2549 Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 27271 Lines: 811 Hi! This series adds support for an 8-bit clut mode in the atmel-hlcdc driver. I have now tested patch 1 with the below program (modeset.c adapted from https://github.com/dvdhrm/docs/tree/master/drm-howto to use an 8-bit mode). Since v2 I have also cleared up why the first 16 entries of the clut was not working right. It was of course my own damn fault, and the fix was in atmel_hlcdc_layer_write_clut function which called the ...write_reg function which in turn added an extra offset of 16 registers... Changes since v2: - Fix mapping to the clut registers. Changes since v1: - Move the clut update from atmel_hlcdc_crtc_mode_valid to atmel_hlcdc_plane_atomic_update. - Add default .gamma_set helper (drm_atomic_helper_legacy_gamma_set). - Don't keep a spare copy of the clut, reuse gamma_store instead. - Don't try to synchronize the legacy fb clut with the drm clut. As I said in v2, I have not added any .clut_offset to the overlay2 layer of sama5d4, since the chip does not appear to have that layer. I didn't do that to make it easier to work with the patch previously sent to remove that layer, but I suspect bad things may happen to sama5d4 users if they do not have that layer removed. Cheers, peda modeset-pal.c (didn't update any comments, sorry) ----------------8<--------------- /* * modeset - DRM Modesetting Example * * Written 2012 by David Herrmann * Dedicated to the Public Domain. */ /* * DRM Modesetting Howto * This document describes the DRM modesetting API. Before we can use the DRM * API, we have to include xf86drm.h and xf86drmMode.h. Both are provided by * libdrm which every major distribution ships by default. It has no other * dependencies and is pretty small. * * Please ignore all forward-declarations of functions which are used later. I * reordered the functions so you can read this document from top to bottom. If * you reimplement it, you would probably reorder the functions to avoid all the * nasty forward declarations. * * For easier reading, we ignore all memory-allocation errors of malloc() and * friends here. However, we try to correctly handle all other kinds of errors * that may occur. * * All functions and global variables are prefixed with "modeset_*" in this * file. So it should be clear whether a function is a local helper or if it is * provided by some external library. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include struct modeset_dev; static int modeset_find_crtc(int fd, drmModeRes *res, drmModeConnector *conn, struct modeset_dev *dev); static int modeset_create_fb(int fd, struct modeset_dev *dev); static int modeset_setup_dev(int fd, drmModeRes *res, drmModeConnector *conn, struct modeset_dev *dev); static int modeset_open(int *out, const char *node); static int modeset_prepare(int fd); static void modeset_draw(int fd); static void modeset_cleanup(int fd); /* * When the linux kernel detects a graphics-card on your machine, it loads the * correct device driver (located in kernel-tree at ./drivers/gpu/drm/) and * provides two character-devices to control it. Udev (or whatever hotplugging * application you use) will create them as: * /dev/dri/card0 * /dev/dri/controlID64 * We only need the first one. You can hard-code this path into your application * like we do here, but it is recommended to use libudev with real hotplugging * and multi-seat support. However, this is beyond the scope of this document. * Also note that if you have multiple graphics-cards, there may also be * /dev/dri/card1, /dev/dri/card2, ... * * We simply use /dev/dri/card0 here but the user can specify another path on * the command line. * * modeset_open(out, node): This small helper function opens the DRM device * which is given as @node. The new fd is stored in @out on success. On failure, * a negative error code is returned. * After opening the file, we also check for the DRM_CAP_DUMB_BUFFER capability. * If the driver supports this capability, we can create simple memory-mapped * buffers without any driver-dependent code. As we want to avoid any radeon, * nvidia, intel, etc. specific code, we depend on DUMB_BUFFERs here. */ static int modeset_open(int *out, const char *node) { int fd, ret; uint64_t has_dumb; fd = open(node, O_RDWR | O_CLOEXEC); if (fd < 0) { ret = -errno; fprintf(stderr, "cannot open '%s': %m\n", node); return ret; } if (drmGetCap(fd, DRM_CAP_DUMB_BUFFER, &has_dumb) < 0 || !has_dumb) { fprintf(stderr, "drm device '%s' does not support dumb buffers\n", node); close(fd); return -EOPNOTSUPP; } *out = fd; return 0; } /* * As a next step we need to find our available display devices. libdrm provides * a drmModeRes structure that contains all the needed information. We can * retrieve it via drmModeGetResources(fd) and free it via * drmModeFreeResources(res) again. * * A physical connector on your graphics card is called a "connector". You can * plug a monitor into it and control what is displayed. We are definitely * interested in what connectors are currently used, so we simply iterate * through the list of connectors and try to display a test-picture on each * available monitor. * However, this isn't as easy as it sounds. First, we need to check whether the * connector is actually used (a monitor is plugged in and turned on). Then we * need to find a CRTC that can control this connector. CRTCs are described * later on. After that we create a framebuffer object. If we have all this, we * can mmap() the framebuffer and draw a test-picture into it. Then we can tell * the DRM device to show the framebuffer on the given CRTC with the selected * connector. * * As we want to draw moving pictures on the framebuffer, we actually have to * remember all these settings. Therefore, we create one "struct modeset_dev" * object for each connector+crtc+framebuffer pair that we successfully * initialized and push it into the global device-list. * * Each field of this structure is described when it is first used. But as a * summary: * "struct modeset_dev" contains: { * - @next: points to the next device in the single-linked list * * - @width: width of our buffer object * - @height: height of our buffer object * - @stride: stride value of our buffer object * - @size: size of the memory mapped buffer * - @handle: a DRM handle to the buffer object that we can draw into * - @map: pointer to the memory mapped buffer * * - @mode: the display mode that we want to use * - @fb: a framebuffer handle with our buffer object as scanout buffer * - @conn: the connector ID that we want to use with this buffer * - @crtc: the crtc ID that we want to use with this connector * - @saved_crtc: the configuration of the crtc before we changed it. We use it * so we can restore the same mode when we exit. * } */ struct modeset_dev { struct modeset_dev *next; uint32_t width; uint32_t height; uint32_t stride; uint32_t size; uint32_t handle; uint8_t *map; drmModeModeInfo mode; uint32_t fb; uint32_t conn; uint32_t crtc; drmModeCrtc *saved_crtc; }; static struct modeset_dev *modeset_list = NULL; /* * So as next step we need to actually prepare all connectors that we find. We * do this in this little helper function: * * modeset_prepare(fd): This helper function takes the DRM fd as argument and * then simply retrieves the resource-info from the device. It then iterates * through all connectors and calls other helper functions to initialize this * connector (described later on). * If the initialization was successful, we simply add this object as new device * into the global modeset device list. * * The resource-structure contains a list of all connector-IDs. We use the * helper function drmModeGetConnector() to retrieve more information on each * connector. After we are done with it, we free it again with * drmModeFreeConnector(). * Our helper modeset_setup_dev() returns -ENOENT if the connector is currently * unused and no monitor is plugged in. So we can ignore this connector. */ static int modeset_prepare(int fd) { drmModeRes *res; drmModeConnector *conn; unsigned int i; struct modeset_dev *dev; int ret; /* retrieve resources */ res = drmModeGetResources(fd); if (!res) { fprintf(stderr, "cannot retrieve DRM resources (%d): %m\n", errno); return -errno; } /* iterate all connectors */ for (i = 0; i < res->count_connectors; ++i) { /* get information for each connector */ conn = drmModeGetConnector(fd, res->connectors[i]); if (!conn) { fprintf(stderr, "cannot retrieve DRM connector %u:%u (%d): %m\n", i, res->connectors[i], errno); continue; } /* create a device structure */ dev = malloc(sizeof(*dev)); memset(dev, 0, sizeof(*dev)); dev->conn = conn->connector_id; /* call helper function to prepare this connector */ ret = modeset_setup_dev(fd, res, conn, dev); if (ret) { if (ret != -ENOENT) { errno = -ret; fprintf(stderr, "cannot setup device for connector %u:%u (%d): %m\n", i, res->connectors[i], errno); } free(dev); drmModeFreeConnector(conn); continue; } /* free connector data and link device into global list */ drmModeFreeConnector(conn); dev->next = modeset_list; modeset_list = dev; } /* free resources again */ drmModeFreeResources(res); return 0; } /* * Now we dig deeper into setting up a single connector. As described earlier, * we need to check several things first: * * If the connector is currently unused, that is, no monitor is plugged in, * then we can ignore it. * * We have to find a suitable resolution and refresh-rate. All this is * available in drmModeModeInfo structures saved for each crtc. We simply * use the first mode that is available. This is always the mode with the * highest resolution. * A more sophisticated mode-selection should be done in real applications, * though. * * Then we need to find an CRTC that can drive this connector. A CRTC is an * internal resource of each graphics-card. The number of CRTCs controls how * many connectors can be controlled indepedently. That is, a graphics-cards * may have more connectors than CRTCs, which means, not all monitors can be * controlled independently. * There is actually the possibility to control multiple connectors via a * single CRTC if the monitors should display the same content. However, we * do not make use of this here. * So think of connectors as pipelines to the connected monitors and the * CRTCs are the controllers that manage which data goes to which pipeline. * If there are more pipelines than CRTCs, then we cannot control all of * them at the same time. * * We need to create a framebuffer for this connector. A framebuffer is a * memory buffer that we can write XRGB32 data into. So we use this to * render our graphics and then the CRTC can scan-out this data from the * framebuffer onto the monitor. */ static int modeset_setup_dev(int fd, drmModeRes *res, drmModeConnector *conn, struct modeset_dev *dev) { int ret; /* check if a monitor is connected */ if (conn->connection != DRM_MODE_CONNECTED) { fprintf(stderr, "ignoring unused connector %u\n", conn->connector_id); return -ENOENT; } /* check if there is at least one valid mode */ if (conn->count_modes == 0) { fprintf(stderr, "no valid mode for connector %u\n", conn->connector_id); return -EFAULT; } /* copy the mode information into our device structure */ memcpy(&dev->mode, &conn->modes[0], sizeof(dev->mode)); dev->width = conn->modes[0].hdisplay; dev->height = conn->modes[0].vdisplay; fprintf(stderr, "mode for connector %u is %ux%u\n", conn->connector_id, dev->width, dev->height); /* find a crtc for this connector */ ret = modeset_find_crtc(fd, res, conn, dev); if (ret) { fprintf(stderr, "no valid crtc for connector %u\n", conn->connector_id); return ret; } /* create a framebuffer for this CRTC */ ret = modeset_create_fb(fd, dev); if (ret) { fprintf(stderr, "cannot create framebuffer for connector %u\n", conn->connector_id); return ret; } return 0; } /* * modeset_find_crtc(fd, res, conn, dev): This small helper tries to find a * suitable CRTC for the given connector. We have actually have to introduce one * more DRM object to make this more clear: Encoders. * Encoders help the CRTC to convert data from a framebuffer into the right * format that can be used for the chosen connector. We do not have to * understand any more of these conversions to make use of it. However, you must * know that each connector has a limited list of encoders that it can use. And * each encoder can only work with a limited list of CRTCs. So what we do is * trying each encoder that is available and looking for a CRTC that this * encoder can work with. If we find the first working combination, we are happy * and write it into the @dev structure. * But before iterating all available encoders, we first try the currently * active encoder+crtc on a connector to avoid a full modeset. * * However, before we can use a CRTC we must make sure that no other device, * that we setup previously, is already using this CRTC. Remember, we can only * drive one connector per CRTC! So we simply iterate through the "modeset_list" * of previously setup devices and check that this CRTC wasn't used before. * Otherwise, we continue with the next CRTC/Encoder combination. */ static int modeset_find_crtc(int fd, drmModeRes *res, drmModeConnector *conn, struct modeset_dev *dev) { drmModeEncoder *enc; unsigned int i, j; int32_t crtc; struct modeset_dev *iter; /* first try the currently conected encoder+crtc */ if (conn->encoder_id) enc = drmModeGetEncoder(fd, conn->encoder_id); else enc = NULL; if (enc) { if (enc->crtc_id) { crtc = enc->crtc_id; for (iter = modeset_list; iter; iter = iter->next) { if (iter->crtc == crtc) { crtc = -1; break; } } if (crtc >= 0) { drmModeFreeEncoder(enc); dev->crtc = crtc; return 0; } } drmModeFreeEncoder(enc); } /* If the connector is not currently bound to an encoder or if the * encoder+crtc is already used by another connector (actually unlikely * but lets be safe), iterate all other available encoders to find a * matching CRTC. */ for (i = 0; i < conn->count_encoders; ++i) { enc = drmModeGetEncoder(fd, conn->encoders[i]); if (!enc) { fprintf(stderr, "cannot retrieve encoder %u:%u (%d): %m\n", i, conn->encoders[i], errno); continue; } /* iterate all global CRTCs */ for (j = 0; j < res->count_crtcs; ++j) { /* check whether this CRTC works with the encoder */ if (!(enc->possible_crtcs & (1 << j))) continue; /* check that no other device already uses this CRTC */ crtc = res->crtcs[j]; for (iter = modeset_list; iter; iter = iter->next) { if (iter->crtc == crtc) { crtc = -1; break; } } /* we have found a CRTC, so save it and return */ if (crtc >= 0) { drmModeFreeEncoder(enc); dev->crtc = crtc; return 0; } } drmModeFreeEncoder(enc); } fprintf(stderr, "cannot find suitable CRTC for connector %u\n", conn->connector_id); return -ENOENT; } /* * modeset_create_fb(fd, dev): After we have found a crtc+connector+mode * combination, we need to actually create a suitable framebuffer that we can * use with it. There are actually two ways to do that: * * We can create a so called "dumb buffer". This is a buffer that we can * memory-map via mmap() and every driver supports this. We can use it for * unaccelerated software rendering on the CPU. * * We can use libgbm to create buffers available for hardware-acceleration. * libgbm is an abstraction layer that creates these buffers for each * available DRM driver. As there is no generic API for this, each driver * provides its own way to create these buffers. * We can then use such buffers to create OpenGL contexts with the mesa3D * library. * We use the first solution here as it is much simpler and doesn't require any * external libraries. However, if you want to use hardware-acceleration via * OpenGL, it is actually pretty easy to create such buffers with libgbm and * libEGL. But this is beyond the scope of this document. * * So what we do is requesting a new dumb-buffer from the driver. We specify the * same size as the current mode that we selected for the connector. * Then we request the driver to prepare this buffer for memory mapping. After * that we perform the actual mmap() call. So we can now access the framebuffer * memory directly via the dev->map memory map. */ static int modeset_create_fb(int fd, struct modeset_dev *dev) { struct drm_mode_create_dumb creq; struct drm_mode_destroy_dumb dreq; struct drm_mode_map_dumb mreq; int ret; /* create dumb buffer */ memset(&creq, 0, sizeof(creq)); creq.width = dev->width; creq.height = dev->height; creq.bpp = 8; ret = drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq); if (ret < 0) { fprintf(stderr, "cannot create dumb buffer (%d): %m\n", errno); return -errno; } dev->stride = creq.pitch; dev->size = creq.size; dev->handle = creq.handle; /* create framebuffer object for the dumb-buffer */ ret = drmModeAddFB(fd, dev->width, dev->height, 8, 8, dev->stride, dev->handle, &dev->fb); if (ret) { fprintf(stderr, "cannot create framebuffer (%d): %m\n", errno); ret = -errno; goto err_destroy; } /* prepare buffer for memory mapping */ memset(&mreq, 0, sizeof(mreq)); mreq.handle = dev->handle; ret = drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq); if (ret) { fprintf(stderr, "cannot map dumb buffer (%d): %m\n", errno); ret = -errno; goto err_fb; } /* perform actual memory mapping */ dev->map = mmap(0, dev->size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, mreq.offset); if (dev->map == MAP_FAILED) { fprintf(stderr, "cannot mmap dumb buffer (%d): %m\n", errno); ret = -errno; goto err_fb; } /* clear the framebuffer to 0 */ memset(dev->map, 0, dev->size); return 0; err_fb: drmModeRmFB(fd, dev->fb); err_destroy: memset(&dreq, 0, sizeof(dreq)); dreq.handle = dev->handle; drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq); return ret; } /* * Finally! We have a connector with a suitable CRTC. We know which mode we want * to use and we have a framebuffer of the correct size that we can write to. * There is nothing special left to do. We only have to program the CRTC to * connect each new framebuffer to each selected connector for each combination * that we saved in the global modeset_list. * This is done with a call to drmModeSetCrtc(). * * So we are ready for our main() function. First we check whether the user * specified a DRM device on the command line, otherwise we use the default * /dev/dri/card0. Then we open the device via modeset_open(). modeset_prepare() * prepares all connectors and we can loop over "modeset_list" and call * drmModeSetCrtc() on every CRTC/connector combination. * * But printing empty black pages is boring so we have another helper function * modeset_draw() that draws some colors into the framebuffer for 5 seconds and * then returns. And then we have all the cleanup functions which correctly free * all devices again after we used them. All these functions are described below * the main() function. * * As a side note: drmModeSetCrtc() actually takes a list of connectors that we * want to control with this CRTC. We pass only one connector, though. As * explained earlier, if we used multiple connectors, then all connectors would * have the same controlling framebuffer so the output would be cloned. This is * most often not what you want so we avoid explaining this feature here. * Furthermore, all connectors will have to run with the same mode, which is * also often not guaranteed. So instead, we only use one connector per CRTC. * * Before calling drmModeSetCrtc() we also save the current CRTC configuration. * This is used in modeset_cleanup() to restore the CRTC to the same mode as was * before we changed it. * If we don't do this, the screen will stay blank after we exit until another * application performs modesetting itself. */ int main(int argc, char **argv) { int ret, fd; const char *card; struct modeset_dev *iter; /* check which DRM device to open */ if (argc > 1) card = argv[1]; else card = "/dev/dri/card0"; fprintf(stderr, "using card '%s'\n", card); /* open the DRM device */ ret = modeset_open(&fd, card); if (ret) goto out_return; /* prepare all connectors and CRTCs */ ret = modeset_prepare(fd); if (ret) goto out_close; /* perform actual modesetting on each found connector+CRTC */ for (iter = modeset_list; iter; iter = iter->next) { iter->saved_crtc = drmModeGetCrtc(fd, iter->crtc); ret = drmModeSetCrtc(fd, iter->crtc, iter->fb, 0, 0, &iter->conn, 1, &iter->mode); if (ret) fprintf(stderr, "cannot set CRTC for connector %u (%d): %m\n", iter->conn, errno); } /* draw some colors for 5seconds */ modeset_draw(fd); /* cleanup everything */ modeset_cleanup(fd); ret = 0; out_close: close(fd); out_return: if (ret) { errno = -ret; fprintf(stderr, "modeset failed with error %d: %m\n", errno); } else { fprintf(stderr, "exiting\n"); } return ret; } /* * A short helper function to compute a changing color value. No need to * understand it. */ static uint8_t next_color(bool *up, uint8_t cur, unsigned int mod) { uint8_t next; next = cur + (*up ? 1 : -1) * (rand() % mod); if ((*up && next < cur) || (!*up && next > cur)) { *up = !*up; next = cur; } return next; } static void crtc_lut(int fd, struct modeset_dev *dev, int p) { struct drm_mode_crtc_lut clut; uint16_t r[256]; uint16_t g[256]; uint16_t b[256]; int ret; int i; /* prepare buffer for memory mapping */ memset(&clut, 0, sizeof(clut)); clut.crtc_id = dev->crtc; clut.gamma_size = 256; clut.red = (uint64_t)r; clut.green = (uint64_t)g; clut.blue = (uint64_t)b; for (i = 0; i < 256; ++i) { r[i] = ((p + 2 * i) & 255) * 257; g[i] = ((p + 3 * i) & 255) * 257; b[i] = ((p + 5 * i) & 255) * 257; } ret = drmIoctl(fd, DRM_IOCTL_MODE_SETGAMMA, &clut); if (ret) fprintf(stderr, "cannot set gamma lut (%d): %m\n", errno); } /* * modeset_draw(): This draws a solid color into all configured framebuffers. * Every 100ms the color changes to a slightly different color so we get some * kind of smoothly changing color-gradient. * * The color calculation can be ignored as it is pretty boring. So the * interesting stuff is iterating over "modeset_list" and then through all lines * and width. We then set each pixel individually to the current color. * * We do this 50 times as we sleep 100ms after each redraw round. This makes * 50*100ms = 5000ms = 5s so it takes about 5seconds to finish this loop. * * Please note that we draw directly into the framebuffer. This means that you * will see flickering as the monitor might refresh while we redraw the screen. * To avoid this you would need to use two framebuffers and a call to * drmModeSetCrtc() to switch between both buffers. * You can also use drmModePageFlip() to do a vsync'ed pageflip. But this is * beyond the scope of this document. */ static void modeset_draw(int fd) { uint8_t p = 0; unsigned int i, j, k; struct modeset_dev *iter; for (iter = modeset_list; iter; iter = iter->next) { for (k = 0; k < iter->width; ++k) { for (j = 0; j < iter->height / 3; ++j) { iter->map[iter->stride * j + k] = k * 256 / iter->width; } for (; j < iter->height; ++j) iter->map[iter->stride * j + k] = 26; } } for (i = 0; i < 50; ++i, ++p) { for (iter = modeset_list; iter; iter = iter->next) crtc_lut(fd, iter, p); usleep(100000); } } /* * modeset_cleanup(fd): This cleans up all the devices we created during * modeset_prepare(). It resets the CRTCs to their saved states and deallocates * all memory. * It should be pretty obvious how all of this works. */ static void modeset_cleanup(int fd) { struct modeset_dev *iter; struct drm_mode_destroy_dumb dreq; while (modeset_list) { /* remove from global list */ iter = modeset_list; modeset_list = iter->next; /* restore saved CRTC configuration */ drmModeSetCrtc(fd, iter->saved_crtc->crtc_id, iter->saved_crtc->buffer_id, iter->saved_crtc->x, iter->saved_crtc->y, &iter->conn, 1, &iter->saved_crtc->mode); drmModeFreeCrtc(iter->saved_crtc); /* unmap buffer */ munmap(iter->map, iter->size); /* delete framebuffer */ drmModeRmFB(fd, iter->fb); /* delete dumb buffer */ memset(&dreq, 0, sizeof(dreq)); dreq.handle = iter->handle; drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq); /* free allocated memory */ free(iter); } } /* * I hope this was a short but easy overview of the DRM modesetting API. The DRM * API offers much more capabilities including: * - double-buffering or tripple-buffering (or whatever you want) * - vsync'ed page-flips * - hardware-accelerated rendering (for example via OpenGL) * - output cloning * - graphics-clients plus authentication * - DRM planes/overlays/sprites * - ... * If you are interested in these topics, I can currently only redirect you to * existing implementations, including: * - plymouth (which uses dumb-buffers like this example; very easy to understand) * - kmscon (which uses libuterm to do this) * - wayland (very sophisticated DRM renderer; hard to understand fully as it * uses more complicated techniques like DRM planes) * - xserver (very hard to understand as it is split across many files/projects) * * But understanding how modesetting (as described in this document) works, is * essential to understand all further DRM topics. * * Any feedback is welcome. Feel free to use this code freely for your own * documentation or projects. * * - Hosted on http://github.com/dvdhrm/docs * - Written by David Herrmann */ ----------------8<--------------- Peter Rosin (3): drm: atmel-hlcdc: add support for 8-bit color lookup table mode drm/fb-cma-helper: expose more of fb cma guts drm: atmel-hlcdc: add clut support for legacy fbdev drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_crtc.c | 58 +++++++++++++++++++++++++ drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c | 25 ++++++++++- drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h | 20 +++++++++ drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c | 29 +++++++++++++ drivers/gpu/drm/drm_fb_cma_helper.c | 55 ++++++++++++++++++----- include/drm/drm_fb_cma_helper.h | 8 +++- 6 files changed, 182 insertions(+), 13 deletions(-) -- 2.1.4