Merge tag 'drm-misc-next-2022-02-23' of git://anongit.freedesktop.org/drm/drm-misc...
authorDave Airlie <airlied@redhat.com>
Thu, 24 Feb 2022 19:30:17 +0000 (05:30 +1000)
committerDave Airlie <airlied@redhat.com>
Thu, 24 Feb 2022 19:50:18 +0000 (05:50 +1000)
drm-misc-next for v5.18:

UAPI Changes:

Cross-subsystem Changes:
- Split out panel-lvds and lvds dt bindings .
- Put yes/no on/off disabled/enabled strings in linux/string_helpers.h
  and use it in drivers and tomoyo.
- Clarify dma_fence_chain and dma_fence_array should never include eachother.
- Flatten chains in syncobj's.
- Don't double add in fbdev/defio when page is already enlisted.
- Don't sort deferred-I/O pages by default in fbdev.

Core Changes:
- Fix missing pm_runtime_put_sync in bridge.
- Set modifier support to only linear fb modifier if drivers don't
  advertise support.
- As a result, we remove allow_fb_modifiers.
- Add missing clear for EDID Deep Color Modes in drm_reset_display_info.
- Assorted documentation updates.
- Warn once in drm_clflush if there is no arch support.
- Add missing select for dp helper in drm_panel_edp.
- Assorted small fixes.
- Improve fb-helper's clipping handling.
- Don't dump shmem mmaps in a core dump.
- Add accounting to ttm resource manager, and use it in amdgpu.
- Allow querying the detected eDP panel through debugfs.
- Add helpers for xrgb8888 to 8 and 1 bits gray.
- Improve drm's buddy allocator.
- Add selftests for the buddy allocator.

Driver Changes:
- Add support for nomodeset to a lot of drm drivers.
- Use drm_module_*_driver in a lot of drm drivers.
- Assorted small fixes to bridge/lt9611, v3d, vc4, vmwgfx, mxsfb, nouveau,
  bridge/dw-hdmi, panfrost, lima, ingenic, sprd, bridge/anx7625, ti-sn65dsi86.
- Add bridge/it6505.
- Create DP and DVI-I connectors in ast.
- Assorted nouveau backlight fixes.
- Rework amdgpu reset handling.
- Add dt bindings for ingenic,jz4780-dw-hdmi.
- Support reading edid through aux channel in ingenic.
- Add a drm driver for Solomon SSD130x OLED displays.
- Add simple support for sharp LQ140M1JW46.
- Add more panels to nt35560.

Signed-off-by: Dave Airlie <airlied@redhat.com>
From: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Link: https://patchwork.freedesktop.org/patch/msgid/686ec871-e77f-c230-22e5-9e3bb80f064a@linux.intel.com
45 files changed:
1  2 
MAINTAINERS
drivers/gpu/drm/amd/amdgpu/amdgpu.h
drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c
drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c
drivers/gpu/drm/amd/amdgpu/amdgpu_device.c
drivers/gpu/drm/amd/amdgpu/amdgpu_display.c
drivers/gpu/drm/amd/amdgpu/amdgpu_gtt_mgr.c
drivers/gpu/drm/amd/amdgpu/amdgpu_job.c
drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c
drivers/gpu/drm/amd/amdgpu/amdgpu_object.c
drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c
drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c
drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.h
drivers/gpu/drm/amd/amdgpu/amdgpu_virt.c
drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.c
drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c
drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.h
drivers/gpu/drm/amd/amdgpu/dce_v10_0.c
drivers/gpu/drm/amd/amdgpu/dce_v11_0.c
drivers/gpu/drm/amd/amdgpu/dce_v6_0.c
drivers/gpu/drm/amd/amdgpu/dce_v8_0.c
drivers/gpu/drm/amd/amdgpu/gmc_v10_0.c
drivers/gpu/drm/amd/amdgpu/gmc_v9_0.c
drivers/gpu/drm/amd/amdgpu/mxgpu_vi.c
drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c
drivers/gpu/drm/ast/ast_drv.h
drivers/gpu/drm/ast/ast_mode.c
drivers/gpu/drm/bridge/nwl-dsi.c
drivers/gpu/drm/dp/drm_dp.c
drivers/gpu/drm/drm_cache.c
drivers/gpu/drm/drm_fb_helper.c
drivers/gpu/drm/drm_gem.c
drivers/gpu/drm/drm_gem_shmem_helper.c
drivers/gpu/drm/drm_privacy_screen.c
drivers/gpu/drm/lima/lima_gem.c
drivers/gpu/drm/lima/lima_sched.c
drivers/gpu/drm/qxl/qxl_drv.h
drivers/gpu/drm/radeon/radeon_object.c
drivers/gpu/drm/solomon/ssd130x.c
drivers/gpu/drm/ttm/ttm_resource.c
drivers/gpu/drm/vc4/vc4_hdmi.c
include/drm/drm_gem_shmem_helper.h
include/drm/ttm/ttm_resource.h
include/linux/fb.h

diff --cc MAINTAINERS
Simple merge
index 566303c9942fcbbd525bd30f3ed60f706d1a9e81,b89406b016946db7796c677224aae30a2cab5b30..8685784f24a928647717d063c4ce1c81d484e81f
@@@ -1096,10 -1100,7 +1096,12 @@@ struct amdgpu_device 
        uint32_t                        ip_versions[MAX_HWIP][HWIP_MAX_INSTANCE];
  
        bool                            ram_is_direct_mapped;
 +
 +      struct list_head                ras_list;
 +
 +      struct ip_discovery_top         *ip_top;
++
+       struct amdgpu_reset_domain      *reset_domain;
  };
  
  static inline struct amdgpu_device *drm_to_adev(struct drm_device *ddev)
index 0cfccea722f8843e8b8086ea71d7fa8b97897498,cd1f9140a878b828582bdf16a500496e542e6950..ca8e76771ea17a05795681f316c775403a7b2423
@@@ -3543,13 -3556,10 +3606,11 @@@ int amdgpu_device_init(struct amdgpu_de
        mutex_init(&adev->mn_lock);
        mutex_init(&adev->virt.vf_errors.lock);
        hash_init(adev->mn_hash);
-       atomic_set(&adev->in_gpu_reset, 0);
-       init_rwsem(&adev->reset_sem);
        mutex_init(&adev->psp.mutex);
        mutex_init(&adev->notifier_lock);
 +      mutex_init(&adev->pm.stable_pstate_ctx_lock);
  
 -       amdgpu_device_init_apu_flags(adev);
 +      amdgpu_device_init_apu_flags(adev);
  
        r = amdgpu_device_check_arguments(adev);
        if (r)
index 4870e093213d931a4e7b1af502f0e393e5f6162a,38c9fd7b7ad4e0571484c92ddd5051bf7e7684ae..d970336d2261ef4ab8e4f38244b2bf25c43602f9
@@@ -64,10 -63,7 +64,9 @@@ static enum drm_gpu_sched_stat amdgpu_j
                  ti.process_name, ti.tgid, ti.task_name, ti.pid);
  
        if (amdgpu_device_should_recover_gpu(ring->adev)) {
-               r = amdgpu_device_gpu_recover(ring->adev, job);
 -              amdgpu_device_gpu_recover_imp(ring->adev, job);
++              r = amdgpu_device_gpu_recover_imp(ring->adev, job);
 +              if (r)
 +                      DRM_ERROR("GPU Recovery Failed: %d\n", r);
        } else {
                drm_sched_suspend_timeout(&ring->sched);
                if (amdgpu_sriov_vf(adev))
index 2b844a5aafdbf1787eccbfe5ae8b6b8ab849c178,d3e055314804ae009a28924c5f0060ad69df9cc5..a44f2eeed6ef7b695b8ed339233b404f4f52386b
@@@ -193,8 -195,8 +195,8 @@@ static int __write_table_header(struct 
        __encode_table_header_to_buf(&control->tbl_hdr, buf);
  
        /* i2c may be unstable in gpu reset */
-       down_read(&adev->reset_sem);
+       down_read(&adev->reset_domain->sem);
 -      res = amdgpu_eeprom_write(&adev->pm.smu_i2c,
 +      res = amdgpu_eeprom_write(adev->pm.ras_eeprom_i2c_bus,
                                  control->i2c_address +
                                  control->ras_header_offset,
                                  buf, RAS_TABLE_HEADER_SIZE);
@@@ -390,9 -389,9 +392,9 @@@ static int __amdgpu_ras_eeprom_write(st
        int res;
  
        /* i2c may be unstable in gpu reset */
-       down_read(&adev->reset_sem);
+       down_read(&adev->reset_domain->sem);
        buf_size = num * RAS_TABLE_RECORD_SIZE;
 -      res = amdgpu_eeprom_write(&adev->pm.smu_i2c,
 +      res = amdgpu_eeprom_write(adev->pm.ras_eeprom_i2c_bus,
                                  control->i2c_address +
                                  RAS_INDEX_TO_OFFSET(control, fri),
                                  buf, buf_size);
@@@ -550,8 -549,8 +552,8 @@@ amdgpu_ras_eeprom_update_header(struct 
                goto Out;
        }
  
-       down_read(&adev->reset_sem);
+       down_read(&adev->reset_domain->sem);
 -      res = amdgpu_eeprom_read(&adev->pm.smu_i2c,
 +      res = amdgpu_eeprom_read(adev->pm.ras_eeprom_i2c_bus,
                                 control->i2c_address +
                                 control->ras_record_offset,
                                 buf, buf_size);
@@@ -645,9 -644,9 +647,9 @@@ static int __amdgpu_ras_eeprom_read(str
        int res;
  
        /* i2c may be unstable in gpu reset */
-       down_read(&adev->reset_sem);
+       down_read(&adev->reset_domain->sem);
        buf_size = num * RAS_TABLE_RECORD_SIZE;
 -      res = amdgpu_eeprom_read(&adev->pm.smu_i2c,
 +      res = amdgpu_eeprom_read(adev->pm.ras_eeprom_i2c_bus,
                                 control->i2c_address +
                                 RAS_INDEX_TO_OFFSET(control, fri),
                                 buf, buf_size);
index 0e4ecc77db3f44dcec63d2c34354b9a406ae54bc,58c64871c94a5e94d4f151b89933fbcea2614c01..9120ae80ef5238cb755e19d0ad320d602e28bab7
@@@ -118,8 -107,7 +111,7 @@@ int amdgpu_vram_mgr_init(struct amdgpu_
  void amdgpu_vram_mgr_fini(struct amdgpu_device *adev);
  
  bool amdgpu_gtt_mgr_has_gart_addr(struct ttm_resource *mem);
- uint64_t amdgpu_gtt_mgr_usage(struct amdgpu_gtt_mgr *mgr);
 -int amdgpu_gtt_mgr_recover(struct amdgpu_gtt_mgr *mgr);
 +void amdgpu_gtt_mgr_recover(struct amdgpu_gtt_mgr *mgr);
  
  uint64_t amdgpu_preempt_mgr_usage(struct ttm_resource_manager *man);
  
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
index 8c7b24f4b0e4a72065f61c90a75d2c96445e458b,3c888db59ea4a0a86932fa53885d6aa15c06dd25..56fb87885146506b6337d154bcad673259e145f5
   *
   */
  
- #include <linux/types.h>
- #include <linux/slab.h>
- #include <linux/mm.h>
- #include <linux/uaccess.h>
- #include <linux/fs.h>
- #include <linux/file.h>
- #include <linux/module.h>
- #include <linux/mman.h>
- #include <linux/pagemap.h>
- #include <linux/shmem_fs.h>
 -#include <linux/dma-buf-map.h>
  #include <linux/dma-buf.h>
+ #include <linux/file.h>
+ #include <linux/fs.h>
 +#include <linux/iosys-map.h>
  #include <linux/mem_encrypt.h>
+ #include <linux/mm.h>
+ #include <linux/mman.h>
+ #include <linux/module.h>
+ #include <linux/pagemap.h>
  #include <linux/pagevec.h>
+ #include <linux/shmem_fs.h>
+ #include <linux/slab.h>
+ #include <linux/string_helpers.h>
+ #include <linux/types.h>
+ #include <linux/uaccess.h>
  
  #include <drm/drm.h>
  #include <drm/drm_device.h>
Simple merge
Simple merge
Simple merge
Simple merge
index 0000000000000000000000000000000000000000,19697c8c5a2c78e16648c2182cb44d74353982c5..92c1902f53e4bea400c04662dde63b055dadfa73
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,843 +1,843 @@@
 -static int ssd130x_fb_blit_rect(struct drm_framebuffer *fb, const struct dma_buf_map *map,
+ // SPDX-License-Identifier: GPL-2.0-only
+ /*
+  * DRM driver for Solomon SSD130x OLED displays
+  *
+  * Copyright 2022 Red Hat Inc.
+  * Author: Javier Martinez Canillas <javierm@redhat.com>
+  *
+  * Based on drivers/video/fbdev/ssd1307fb.c
+  * Copyright 2012 Free Electrons
+  */
+ #include <linux/backlight.h>
+ #include <linux/bitfield.h>
+ #include <linux/bits.h>
+ #include <linux/delay.h>
+ #include <linux/gpio/consumer.h>
+ #include <linux/property.h>
+ #include <linux/pwm.h>
+ #include <linux/regulator/consumer.h>
+ #include <drm/drm_atomic_helper.h>
+ #include <drm/drm_damage_helper.h>
+ #include <drm/drm_fb_cma_helper.h>
+ #include <drm/drm_fb_helper.h>
+ #include <drm/drm_format_helper.h>
+ #include <drm/drm_gem_atomic_helper.h>
+ #include <drm/drm_gem_framebuffer_helper.h>
+ #include <drm/drm_gem_shmem_helper.h>
+ #include <drm/drm_managed.h>
+ #include <drm/drm_modes.h>
+ #include <drm/drm_rect.h>
+ #include <drm/drm_probe_helper.h>
+ #include "ssd130x.h"
+ #define DRIVER_NAME   "ssd130x"
+ #define DRIVER_DESC   "DRM driver for Solomon SSD130x OLED displays"
+ #define DRIVER_DATE   "20220131"
+ #define DRIVER_MAJOR  1
+ #define DRIVER_MINOR  0
+ #define SSD130X_DATA                          0x40
+ #define SSD130X_COMMAND                               0x80
+ #define SSD130X_SET_ADDRESS_MODE              0x20
+ #define SSD130X_SET_COL_RANGE                 0x21
+ #define SSD130X_SET_PAGE_RANGE                        0x22
+ #define SSD130X_CONTRAST                      0x81
+ #define SSD130X_SET_LOOKUP_TABLE              0x91
+ #define SSD130X_CHARGE_PUMP                   0x8d
+ #define SSD130X_SEG_REMAP_ON                  0xa1
+ #define SSD130X_DISPLAY_OFF                   0xae
+ #define SSD130X_SET_MULTIPLEX_RATIO           0xa8
+ #define SSD130X_DISPLAY_ON                    0xaf
+ #define SSD130X_START_PAGE_ADDRESS            0xb0
+ #define SSD130X_SET_COM_SCAN_DIR              0xc0
+ #define SSD130X_SET_DISPLAY_OFFSET            0xd3
+ #define SSD130X_SET_CLOCK_FREQ                        0xd5
+ #define SSD130X_SET_AREA_COLOR_MODE           0xd8
+ #define SSD130X_SET_PRECHARGE_PERIOD          0xd9
+ #define SSD130X_SET_COM_PINS_CONFIG           0xda
+ #define SSD130X_SET_VCOMH                     0xdb
+ #define SSD130X_SET_COM_SCAN_DIR_MASK         GENMASK(3, 2)
+ #define SSD130X_SET_COM_SCAN_DIR_SET(val)     FIELD_PREP(SSD130X_SET_COM_SCAN_DIR_MASK, (val))
+ #define SSD130X_SET_CLOCK_DIV_MASK            GENMASK(3, 0)
+ #define SSD130X_SET_CLOCK_DIV_SET(val)                FIELD_PREP(SSD130X_SET_CLOCK_DIV_MASK, (val))
+ #define SSD130X_SET_CLOCK_FREQ_MASK           GENMASK(7, 4)
+ #define SSD130X_SET_CLOCK_FREQ_SET(val)               FIELD_PREP(SSD130X_SET_CLOCK_FREQ_MASK, (val))
+ #define SSD130X_SET_PRECHARGE_PERIOD1_MASK    GENMASK(3, 0)
+ #define SSD130X_SET_PRECHARGE_PERIOD1_SET(val)        FIELD_PREP(SSD130X_SET_PRECHARGE_PERIOD1_MASK, (val))
+ #define SSD130X_SET_PRECHARGE_PERIOD2_MASK    GENMASK(7, 4)
+ #define SSD130X_SET_PRECHARGE_PERIOD2_SET(val)        FIELD_PREP(SSD130X_SET_PRECHARGE_PERIOD2_MASK, (val))
+ #define SSD130X_SET_COM_PINS_CONFIG1_MASK     GENMASK(4, 4)
+ #define SSD130X_SET_COM_PINS_CONFIG1_SET(val) FIELD_PREP(SSD130X_SET_COM_PINS_CONFIG1_MASK, !(val))
+ #define SSD130X_SET_COM_PINS_CONFIG2_MASK     GENMASK(5, 5)
+ #define SSD130X_SET_COM_PINS_CONFIG2_SET(val) FIELD_PREP(SSD130X_SET_COM_PINS_CONFIG2_MASK, (val))
+ #define SSD130X_SET_ADDRESS_MODE_HORIZONTAL   0x00
+ #define SSD130X_SET_ADDRESS_MODE_VERTICAL     0x01
+ #define SSD130X_SET_ADDRESS_MODE_PAGE         0x02
+ #define SSD130X_SET_AREA_COLOR_MODE_ENABLE    0x1e
+ #define SSD130X_SET_AREA_COLOR_MODE_LOW_POWER 0x05
+ #define MAX_CONTRAST 255
+ static inline struct ssd130x_device *drm_to_ssd130x(struct drm_device *drm)
+ {
+       return container_of(drm, struct ssd130x_device, drm);
+ }
+ /*
+  * Helper to write data (SSD130X_DATA) to the device.
+  */
+ static int ssd130x_write_data(struct ssd130x_device *ssd130x, u8 *values, int count)
+ {
+       return regmap_bulk_write(ssd130x->regmap, SSD130X_DATA, values, count);
+ }
+ /*
+  * Helper to write command (SSD130X_COMMAND). The fist variadic argument
+  * is the command to write and the following are the command options.
+  *
+  * Note that the ssd130x protocol requires each command and option to be
+  * written as a SSD130X_COMMAND device register value. That is why a call
+  * to regmap_write(..., SSD130X_COMMAND, ...) is done for each argument.
+  */
+ static int ssd130x_write_cmd(struct ssd130x_device *ssd130x, int count,
+                            /* u8 cmd, u8 option, ... */...)
+ {
+       va_list ap;
+       u8 value;
+       int ret;
+       va_start(ap, count);
+       do {
+               value = va_arg(ap, int);
+               ret = regmap_write(ssd130x->regmap, SSD130X_COMMAND, value);
+               if (ret)
+                       goto out_end;
+       } while (--count);
+ out_end:
+       va_end(ap);
+       return ret;
+ }
+ static int ssd130x_set_col_range(struct ssd130x_device *ssd130x,
+                                u8 col_start, u8 cols)
+ {
+       u8 col_end = col_start + cols - 1;
+       int ret;
+       if (col_start == ssd130x->col_start && col_end == ssd130x->col_end)
+               return 0;
+       ret = ssd130x_write_cmd(ssd130x, 3, SSD130X_SET_COL_RANGE, col_start, col_end);
+       if (ret < 0)
+               return ret;
+       ssd130x->col_start = col_start;
+       ssd130x->col_end = col_end;
+       return 0;
+ }
+ static int ssd130x_set_page_range(struct ssd130x_device *ssd130x,
+                                 u8 page_start, u8 pages)
+ {
+       u8 page_end = page_start + pages - 1;
+       int ret;
+       if (page_start == ssd130x->page_start && page_end == ssd130x->page_end)
+               return 0;
+       ret = ssd130x_write_cmd(ssd130x, 3, SSD130X_SET_PAGE_RANGE, page_start, page_end);
+       if (ret < 0)
+               return ret;
+       ssd130x->page_start = page_start;
+       ssd130x->page_end = page_end;
+       return 0;
+ }
+ static int ssd130x_pwm_enable(struct ssd130x_device *ssd130x)
+ {
+       struct device *dev = ssd130x->dev;
+       struct pwm_state pwmstate;
+       ssd130x->pwm = pwm_get(dev, NULL);
+       if (IS_ERR(ssd130x->pwm)) {
+               dev_err(dev, "Could not get PWM from firmware description!\n");
+               return PTR_ERR(ssd130x->pwm);
+       }
+       pwm_init_state(ssd130x->pwm, &pwmstate);
+       pwm_set_relative_duty_cycle(&pwmstate, 50, 100);
+       pwm_apply_state(ssd130x->pwm, &pwmstate);
+       /* Enable the PWM */
+       pwm_enable(ssd130x->pwm);
+       dev_dbg(dev, "Using PWM%d with a %lluns period.\n",
+               ssd130x->pwm->pwm, pwm_get_period(ssd130x->pwm));
+       return 0;
+ }
+ static void ssd130x_reset(struct ssd130x_device *ssd130x)
+ {
+       if (!ssd130x->reset)
+               return;
+       /* Reset the screen */
+       gpiod_set_value_cansleep(ssd130x->reset, 1);
+       udelay(4);
+       gpiod_set_value_cansleep(ssd130x->reset, 0);
+       udelay(4);
+ }
+ static int ssd130x_power_on(struct ssd130x_device *ssd130x)
+ {
+       struct device *dev = ssd130x->dev;
+       int ret;
+       ssd130x_reset(ssd130x);
+       ret = regulator_enable(ssd130x->vcc_reg);
+       if (ret) {
+               dev_err(dev, "Failed to enable VCC: %d\n", ret);
+               return ret;
+       }
+       if (ssd130x->device_info->need_pwm) {
+               ret = ssd130x_pwm_enable(ssd130x);
+               if (ret) {
+                       dev_err(dev, "Failed to enable PWM: %d\n", ret);
+                       regulator_disable(ssd130x->vcc_reg);
+                       return ret;
+               }
+       }
+       return 0;
+ }
+ static void ssd130x_power_off(struct ssd130x_device *ssd130x)
+ {
+       pwm_disable(ssd130x->pwm);
+       pwm_put(ssd130x->pwm);
+       regulator_disable(ssd130x->vcc_reg);
+ }
+ static int ssd130x_init(struct ssd130x_device *ssd130x)
+ {
+       u32 precharge, dclk, com_invdir, compins, chargepump;
+       int ret;
+       /* Set initial contrast */
+       ret = ssd130x_write_cmd(ssd130x, 2, SSD130X_CONTRAST, ssd130x->contrast);
+       if (ret < 0)
+               return ret;
+       /* Set segment re-map */
+       if (ssd130x->seg_remap) {
+               ret = ssd130x_write_cmd(ssd130x, 1, SSD130X_SEG_REMAP_ON);
+               if (ret < 0)
+                       return ret;
+       }
+       /* Set COM direction */
+       com_invdir = (SSD130X_SET_COM_SCAN_DIR |
+                     SSD130X_SET_COM_SCAN_DIR_SET(ssd130x->com_invdir));
+       ret = ssd130x_write_cmd(ssd130x,  1, com_invdir);
+       if (ret < 0)
+               return ret;
+       /* Set multiplex ratio value */
+       ret = ssd130x_write_cmd(ssd130x, 2, SSD130X_SET_MULTIPLEX_RATIO, ssd130x->height - 1);
+       if (ret < 0)
+               return ret;
+       /* set display offset value */
+       ret = ssd130x_write_cmd(ssd130x, 2, SSD130X_SET_DISPLAY_OFFSET, ssd130x->com_offset);
+       if (ret < 0)
+               return ret;
+       /* Set clock frequency */
+       dclk = (SSD130X_SET_CLOCK_DIV_SET(ssd130x->dclk_div - 1) |
+               SSD130X_SET_CLOCK_FREQ_SET(ssd130x->dclk_frq));
+       ret = ssd130x_write_cmd(ssd130x, 2, SSD130X_SET_CLOCK_FREQ, dclk);
+       if (ret < 0)
+               return ret;
+       /* Set Area Color Mode ON/OFF & Low Power Display Mode */
+       if (ssd130x->area_color_enable || ssd130x->low_power) {
+               u32 mode = 0;
+               if (ssd130x->area_color_enable)
+                       mode |= SSD130X_SET_AREA_COLOR_MODE_ENABLE;
+               if (ssd130x->low_power)
+                       mode |= SSD130X_SET_AREA_COLOR_MODE_LOW_POWER;
+               ret = ssd130x_write_cmd(ssd130x, 2, SSD130X_SET_AREA_COLOR_MODE, mode);
+               if (ret < 0)
+                       return ret;
+       }
+       /* Set precharge period in number of ticks from the internal clock */
+       precharge = (SSD130X_SET_PRECHARGE_PERIOD1_SET(ssd130x->prechargep1) |
+                    SSD130X_SET_PRECHARGE_PERIOD1_SET(ssd130x->prechargep2));
+       ret = ssd130x_write_cmd(ssd130x, 2, SSD130X_SET_PRECHARGE_PERIOD, precharge);
+       if (ret < 0)
+               return ret;
+       /* Set COM pins configuration */
+       compins = BIT(1);
+       compins |= (SSD130X_SET_COM_PINS_CONFIG1_SET(ssd130x->com_seq) |
+                   SSD130X_SET_COM_PINS_CONFIG2_SET(ssd130x->com_lrremap));
+       ret = ssd130x_write_cmd(ssd130x, 2, SSD130X_SET_COM_PINS_CONFIG, compins);
+       if (ret < 0)
+               return ret;
+       /* Set VCOMH */
+       ret = ssd130x_write_cmd(ssd130x, 2, SSD130X_SET_VCOMH, ssd130x->vcomh);
+       if (ret < 0)
+               return ret;
+       /* Turn on the DC-DC Charge Pump */
+       chargepump = BIT(4);
+       if (ssd130x->device_info->need_chargepump)
+               chargepump |= BIT(2);
+       ret = ssd130x_write_cmd(ssd130x, 2, SSD130X_CHARGE_PUMP, chargepump);
+       if (ret < 0)
+               return ret;
+       /* Set lookup table */
+       if (ssd130x->lookup_table_set) {
+               int i;
+               ret = ssd130x_write_cmd(ssd130x, 1, SSD130X_SET_LOOKUP_TABLE);
+               if (ret < 0)
+                       return ret;
+               for (i = 0; i < ARRAY_SIZE(ssd130x->lookup_table); i++) {
+                       u8 val = ssd130x->lookup_table[i];
+                       if (val < 31 || val > 63)
+                               dev_warn(ssd130x->dev,
+                                        "lookup table index %d value out of range 31 <= %d <= 63\n",
+                                        i, val);
+                       ret = ssd130x_write_cmd(ssd130x, 1, val);
+                       if (ret < 0)
+                               return ret;
+               }
+       }
+       /* Switch to horizontal addressing mode */
+       return ssd130x_write_cmd(ssd130x, 2, SSD130X_SET_ADDRESS_MODE,
+                                SSD130X_SET_ADDRESS_MODE_HORIZONTAL);
+ }
+ static int ssd130x_update_rect(struct ssd130x_device *ssd130x, u8 *buf,
+                              struct drm_rect *rect)
+ {
+       unsigned int x = rect->x1;
+       unsigned int y = rect->y1;
+       unsigned int width = drm_rect_width(rect);
+       unsigned int height = drm_rect_height(rect);
+       unsigned int line_length = DIV_ROUND_UP(width, 8);
+       unsigned int pages = DIV_ROUND_UP(y % 8 + height, 8);
+       u32 array_idx = 0;
+       int ret, i, j, k;
+       u8 *data_array = NULL;
+       data_array = kcalloc(width, pages, GFP_KERNEL);
+       if (!data_array)
+               return -ENOMEM;
+       /*
+        * The screen is divided in pages, each having a height of 8
+        * pixels, and the width of the screen. When sending a byte of
+        * data to the controller, it gives the 8 bits for the current
+        * column. I.e, the first byte are the 8 bits of the first
+        * column, then the 8 bits for the second column, etc.
+        *
+        *
+        * Representation of the screen, assuming it is 5 bits
+        * wide. Each letter-number combination is a bit that controls
+        * one pixel.
+        *
+        * A0 A1 A2 A3 A4
+        * B0 B1 B2 B3 B4
+        * C0 C1 C2 C3 C4
+        * D0 D1 D2 D3 D4
+        * E0 E1 E2 E3 E4
+        * F0 F1 F2 F3 F4
+        * G0 G1 G2 G3 G4
+        * H0 H1 H2 H3 H4
+        *
+        * If you want to update this screen, you need to send 5 bytes:
+        *  (1) A0 B0 C0 D0 E0 F0 G0 H0
+        *  (2) A1 B1 C1 D1 E1 F1 G1 H1
+        *  (3) A2 B2 C2 D2 E2 F2 G2 H2
+        *  (4) A3 B3 C3 D3 E3 F3 G3 H3
+        *  (5) A4 B4 C4 D4 E4 F4 G4 H4
+        */
+       ret = ssd130x_set_col_range(ssd130x, ssd130x->col_offset + x, width);
+       if (ret < 0)
+               goto out_free;
+       ret = ssd130x_set_page_range(ssd130x, ssd130x->page_offset + y / 8, pages);
+       if (ret < 0)
+               goto out_free;
+       for (i = y / 8; i < y / 8 + pages; i++) {
+               int m = 8;
+               /* Last page may be partial */
+               if (8 * (i + 1) > ssd130x->height)
+                       m = ssd130x->height % 8;
+               for (j = x; j < x + width; j++) {
+                       u8 data = 0;
+                       for (k = 0; k < m; k++) {
+                               u8 byte = buf[(8 * i + k) * line_length + j / 8];
+                               u8 bit = (byte >> (j % 8)) & 1;
+                               data |= bit << k;
+                       }
+                       data_array[array_idx++] = data;
+               }
+       }
+       ret = ssd130x_write_data(ssd130x, data_array, width * pages);
+ out_free:
+       kfree(data_array);
+       return ret;
+ }
+ static void ssd130x_clear_screen(struct ssd130x_device *ssd130x)
+ {
+       u8 *buf = NULL;
+       struct drm_rect fullscreen = {
+               .x1 = 0,
+               .x2 = ssd130x->width,
+               .y1 = 0,
+               .y2 = ssd130x->height,
+       };
+       buf = kcalloc(ssd130x->width, ssd130x->height, GFP_KERNEL);
+       if (!buf)
+               return;
+       ssd130x_update_rect(ssd130x, buf, &fullscreen);
+       kfree(buf);
+ }
++static int ssd130x_fb_blit_rect(struct drm_framebuffer *fb, const struct iosys_map *map,
+                               struct drm_rect *rect)
+ {
+       struct ssd130x_device *ssd130x = drm_to_ssd130x(fb->dev);
+       void *vmap = map->vaddr; /* TODO: Use mapping abstraction properly */
+       int ret = 0;
+       u8 *buf = NULL;
+       buf = kcalloc(fb->width, fb->height, GFP_KERNEL);
+       if (!buf)
+               return -ENOMEM;
+       drm_fb_xrgb8888_to_mono_reversed(buf, 0, vmap, fb, rect);
+       ssd130x_update_rect(ssd130x, buf, rect);
+       kfree(buf);
+       return ret;
+ }
+ static int ssd130x_display_pipe_mode_valid(struct drm_simple_display_pipe *pipe,
+                                          const struct drm_display_mode *mode)
+ {
+       struct ssd130x_device *ssd130x = drm_to_ssd130x(pipe->crtc.dev);
+       if (mode->hdisplay != ssd130x->mode.hdisplay &&
+           mode->vdisplay != ssd130x->mode.vdisplay)
+               return MODE_ONE_SIZE;
+       if (mode->hdisplay != ssd130x->mode.hdisplay)
+               return MODE_ONE_WIDTH;
+       if (mode->vdisplay != ssd130x->mode.vdisplay)
+               return MODE_ONE_HEIGHT;
+       return MODE_OK;
+ }
+ static void ssd130x_display_pipe_enable(struct drm_simple_display_pipe *pipe,
+                                       struct drm_crtc_state *crtc_state,
+                                       struct drm_plane_state *plane_state)
+ {
+       struct ssd130x_device *ssd130x = drm_to_ssd130x(pipe->crtc.dev);
+       struct drm_shadow_plane_state *shadow_plane_state = to_drm_shadow_plane_state(plane_state);
+       struct drm_device *drm = &ssd130x->drm;
+       int idx, ret;
+       ret = ssd130x_power_on(ssd130x);
+       if (ret)
+               return;
+       ret = ssd130x_init(ssd130x);
+       if (ret)
+               goto out_power_off;
+       if (!drm_dev_enter(drm, &idx))
+               goto out_power_off;
+       ssd130x_fb_blit_rect(plane_state->fb, &shadow_plane_state->data[0], &plane_state->dst);
+       ssd130x_write_cmd(ssd130x, 1, SSD130X_DISPLAY_ON);
+       backlight_enable(ssd130x->bl_dev);
+       drm_dev_exit(idx);
+       return;
+ out_power_off:
+       ssd130x_power_off(ssd130x);
+ }
+ static void ssd130x_display_pipe_disable(struct drm_simple_display_pipe *pipe)
+ {
+       struct ssd130x_device *ssd130x = drm_to_ssd130x(pipe->crtc.dev);
+       struct drm_device *drm = &ssd130x->drm;
+       int idx;
+       if (!drm_dev_enter(drm, &idx))
+               return;
+       ssd130x_clear_screen(ssd130x);
+       backlight_disable(ssd130x->bl_dev);
+       ssd130x_write_cmd(ssd130x, 1, SSD130X_DISPLAY_OFF);
+       ssd130x_power_off(ssd130x);
+       drm_dev_exit(idx);
+ }
+ static void ssd130x_display_pipe_update(struct drm_simple_display_pipe *pipe,
+                                       struct drm_plane_state *old_plane_state)
+ {
+       struct ssd130x_device *ssd130x = drm_to_ssd130x(pipe->crtc.dev);
+       struct drm_plane_state *plane_state = pipe->plane.state;
+       struct drm_shadow_plane_state *shadow_plane_state = to_drm_shadow_plane_state(plane_state);
+       struct drm_framebuffer *fb = plane_state->fb;
+       struct drm_device *drm = &ssd130x->drm;
+       struct drm_rect src_clip, dst_clip;
+       int idx;
+       if (!fb)
+               return;
+       if (!pipe->crtc.state->active)
+               return;
+       if (!drm_atomic_helper_damage_merged(old_plane_state, plane_state, &src_clip))
+               return;
+       dst_clip = plane_state->dst;
+       if (!drm_rect_intersect(&dst_clip, &src_clip))
+               return;
+       if (!drm_dev_enter(drm, &idx))
+               return;
+       ssd130x_fb_blit_rect(plane_state->fb, &shadow_plane_state->data[0], &dst_clip);
+       drm_dev_exit(idx);
+ }
+ static const struct drm_simple_display_pipe_funcs ssd130x_pipe_funcs = {
+       .mode_valid = ssd130x_display_pipe_mode_valid,
+       .enable = ssd130x_display_pipe_enable,
+       .disable = ssd130x_display_pipe_disable,
+       .update = ssd130x_display_pipe_update,
+       DRM_GEM_SIMPLE_DISPLAY_PIPE_SHADOW_PLANE_FUNCS,
+ };
+ static int ssd130x_connector_get_modes(struct drm_connector *connector)
+ {
+       struct ssd130x_device *ssd130x = drm_to_ssd130x(connector->dev);
+       struct drm_display_mode *mode = &ssd130x->mode;
+       struct device *dev = ssd130x->dev;
+       mode = drm_mode_duplicate(connector->dev, &ssd130x->mode);
+       if (!mode) {
+               dev_err(dev, "Failed to duplicated mode\n");
+               return 0;
+       }
+       drm_mode_probed_add(connector, mode);
+       drm_set_preferred_mode(connector, mode->hdisplay, mode->vdisplay);
+       /* There is only a single mode */
+       return 1;
+ }
+ static const struct drm_connector_helper_funcs ssd130x_connector_helper_funcs = {
+       .get_modes = ssd130x_connector_get_modes,
+ };
+ static const struct drm_connector_funcs ssd130x_connector_funcs = {
+       .reset = drm_atomic_helper_connector_reset,
+       .fill_modes = drm_helper_probe_single_connector_modes,
+       .destroy = drm_connector_cleanup,
+       .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+       .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+ };
+ static const struct drm_mode_config_funcs ssd130x_mode_config_funcs = {
+       .fb_create = drm_gem_fb_create_with_dirty,
+       .atomic_check = drm_atomic_helper_check,
+       .atomic_commit = drm_atomic_helper_commit,
+ };
+ static const uint32_t ssd130x_formats[] = {
+       DRM_FORMAT_XRGB8888,
+ };
+ DEFINE_DRM_GEM_FOPS(ssd130x_fops);
+ static const struct drm_driver ssd130x_drm_driver = {
+       DRM_GEM_SHMEM_DRIVER_OPS,
+       .name                   = DRIVER_NAME,
+       .desc                   = DRIVER_DESC,
+       .date                   = DRIVER_DATE,
+       .major                  = DRIVER_MAJOR,
+       .minor                  = DRIVER_MINOR,
+       .driver_features        = DRIVER_ATOMIC | DRIVER_GEM | DRIVER_MODESET,
+       .fops                   = &ssd130x_fops,
+ };
+ static int ssd130x_update_bl(struct backlight_device *bdev)
+ {
+       struct ssd130x_device *ssd130x = bl_get_data(bdev);
+       int brightness = backlight_get_brightness(bdev);
+       int ret;
+       ssd130x->contrast = brightness;
+       ret = ssd130x_write_cmd(ssd130x, 1, SSD130X_CONTRAST);
+       if (ret < 0)
+               return ret;
+       ret = ssd130x_write_cmd(ssd130x, 1, ssd130x->contrast);
+       if (ret < 0)
+               return ret;
+       return 0;
+ }
+ static const struct backlight_ops ssd130xfb_bl_ops = {
+       .update_status  = ssd130x_update_bl,
+ };
+ static void ssd130x_parse_properties(struct ssd130x_device *ssd130x)
+ {
+       struct device *dev = ssd130x->dev;
+       if (device_property_read_u32(dev, "solomon,width", &ssd130x->width))
+               ssd130x->width = 96;
+       if (device_property_read_u32(dev, "solomon,height", &ssd130x->height))
+               ssd130x->height = 16;
+       if (device_property_read_u32(dev, "solomon,page-offset", &ssd130x->page_offset))
+               ssd130x->page_offset = 1;
+       if (device_property_read_u32(dev, "solomon,col-offset", &ssd130x->col_offset))
+               ssd130x->col_offset = 0;
+       if (device_property_read_u32(dev, "solomon,com-offset", &ssd130x->com_offset))
+               ssd130x->com_offset = 0;
+       if (device_property_read_u32(dev, "solomon,prechargep1", &ssd130x->prechargep1))
+               ssd130x->prechargep1 = 2;
+       if (device_property_read_u32(dev, "solomon,prechargep2", &ssd130x->prechargep2))
+               ssd130x->prechargep2 = 2;
+       if (!device_property_read_u8_array(dev, "solomon,lookup-table",
+                                          ssd130x->lookup_table,
+                                          ARRAY_SIZE(ssd130x->lookup_table)))
+               ssd130x->lookup_table_set = 1;
+       ssd130x->seg_remap = !device_property_read_bool(dev, "solomon,segment-no-remap");
+       ssd130x->com_seq = device_property_read_bool(dev, "solomon,com-seq");
+       ssd130x->com_lrremap = device_property_read_bool(dev, "solomon,com-lrremap");
+       ssd130x->com_invdir = device_property_read_bool(dev, "solomon,com-invdir");
+       ssd130x->area_color_enable =
+               device_property_read_bool(dev, "solomon,area-color-enable");
+       ssd130x->low_power = device_property_read_bool(dev, "solomon,low-power");
+       ssd130x->contrast = 127;
+       ssd130x->vcomh = ssd130x->device_info->default_vcomh;
+       /* Setup display timing */
+       if (device_property_read_u32(dev, "solomon,dclk-div", &ssd130x->dclk_div))
+               ssd130x->dclk_div = ssd130x->device_info->default_dclk_div;
+       if (device_property_read_u32(dev, "solomon,dclk-frq", &ssd130x->dclk_frq))
+               ssd130x->dclk_frq = ssd130x->device_info->default_dclk_frq;
+ }
+ static int ssd130x_init_modeset(struct ssd130x_device *ssd130x)
+ {
+       struct drm_display_mode *mode = &ssd130x->mode;
+       struct device *dev = ssd130x->dev;
+       struct drm_device *drm = &ssd130x->drm;
+       unsigned long max_width, max_height;
+       int ret;
+       ret = drmm_mode_config_init(drm);
+       if (ret) {
+               dev_err(dev, "DRM mode config init failed: %d\n", ret);
+               return ret;
+       }
+       mode->type = DRM_MODE_TYPE_DRIVER;
+       mode->clock = 1;
+       mode->hdisplay = mode->htotal = ssd130x->width;
+       mode->hsync_start = mode->hsync_end = ssd130x->width;
+       mode->vdisplay = mode->vtotal = ssd130x->height;
+       mode->vsync_start = mode->vsync_end = ssd130x->height;
+       mode->width_mm = 27;
+       mode->height_mm = 27;
+       max_width = max_t(unsigned long, mode->hdisplay, DRM_SHADOW_PLANE_MAX_WIDTH);
+       max_height = max_t(unsigned long, mode->vdisplay, DRM_SHADOW_PLANE_MAX_HEIGHT);
+       drm->mode_config.min_width = mode->hdisplay;
+       drm->mode_config.max_width = max_width;
+       drm->mode_config.min_height = mode->vdisplay;
+       drm->mode_config.max_height = max_height;
+       drm->mode_config.preferred_depth = 32;
+       drm->mode_config.funcs = &ssd130x_mode_config_funcs;
+       ret = drm_connector_init(drm, &ssd130x->connector, &ssd130x_connector_funcs,
+                                DRM_MODE_CONNECTOR_Unknown);
+       if (ret) {
+               dev_err(dev, "DRM connector init failed: %d\n", ret);
+               return ret;
+       }
+       drm_connector_helper_add(&ssd130x->connector, &ssd130x_connector_helper_funcs);
+       ret = drm_simple_display_pipe_init(drm, &ssd130x->pipe, &ssd130x_pipe_funcs,
+                                          ssd130x_formats, ARRAY_SIZE(ssd130x_formats),
+                                          NULL, &ssd130x->connector);
+       if (ret) {
+               dev_err(dev, "DRM simple display pipeline init failed: %d\n", ret);
+               return ret;
+       }
+       drm_plane_enable_fb_damage_clips(&ssd130x->pipe.plane);
+       drm_mode_config_reset(drm);
+       return 0;
+ }
+ static int ssd130x_get_resources(struct ssd130x_device *ssd130x)
+ {
+       struct device *dev = ssd130x->dev;
+       ssd130x->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
+       if (IS_ERR(ssd130x->reset))
+               return dev_err_probe(dev, PTR_ERR(ssd130x->reset),
+                                    "Failed to get reset gpio\n");
+       ssd130x->vcc_reg = devm_regulator_get(dev, "vcc");
+       if (IS_ERR(ssd130x->vcc_reg))
+               return dev_err_probe(dev, PTR_ERR(ssd130x->vcc_reg),
+                                    "Failed to get VCC regulator\n");
+       return 0;
+ }
+ struct ssd130x_device *ssd130x_probe(struct device *dev, struct regmap *regmap)
+ {
+       struct ssd130x_device *ssd130x;
+       struct backlight_device *bl;
+       struct drm_device *drm;
+       int ret;
+       ssd130x = devm_drm_dev_alloc(dev, &ssd130x_drm_driver,
+                                    struct ssd130x_device, drm);
+       if (IS_ERR(ssd130x))
+               return ERR_PTR(dev_err_probe(dev, PTR_ERR(ssd130x),
+                                            "Failed to allocate DRM device\n"));
+       drm = &ssd130x->drm;
+       ssd130x->dev = dev;
+       ssd130x->regmap = regmap;
+       ssd130x->device_info = device_get_match_data(dev);
+       ssd130x_parse_properties(ssd130x);
+       ret = ssd130x_get_resources(ssd130x);
+       if (ret)
+               return ERR_PTR(ret);
+       bl = devm_backlight_device_register(dev, dev_name(dev), dev, ssd130x,
+                                           &ssd130xfb_bl_ops, NULL);
+       if (IS_ERR(bl))
+               return ERR_PTR(dev_err_probe(dev, PTR_ERR(bl),
+                                            "Unable to register backlight device\n"));
+       bl->props.brightness = ssd130x->contrast;
+       bl->props.max_brightness = MAX_CONTRAST;
+       ssd130x->bl_dev = bl;
+       ret = ssd130x_init_modeset(ssd130x);
+       if (ret)
+               return ERR_PTR(ret);
+       ret = drm_dev_register(drm, 0);
+       if (ret)
+               return ERR_PTR(dev_err_probe(dev, ret, "DRM device register failed\n"));
+       drm_fbdev_generic_setup(drm, 0);
+       return ssd130x;
+ }
+ EXPORT_SYMBOL_GPL(ssd130x_probe);
+ int ssd130x_remove(struct ssd130x_device *ssd130x)
+ {
+       drm_dev_unplug(&ssd130x->drm);
+       return 0;
+ }
+ EXPORT_SYMBOL_GPL(ssd130x_remove);
+ void ssd130x_shutdown(struct ssd130x_device *ssd130x)
+ {
+       drm_atomic_helper_shutdown(&ssd130x->drm);
+ }
+ EXPORT_SYMBOL_GPL(ssd130x_shutdown);
+ MODULE_DESCRIPTION(DRIVER_DESC);
+ MODULE_AUTHOR("Javier Martinez Canillas <javierm@redhat.com>");
+ MODULE_LICENSE("GPL v2");
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge