rockchip_ebc: extend mode and add zero_waveform ioctl

Extend mode ioctl to get/set the dither mode and the redraw delay.

Add zero_waveform ioctl that causes the controller to drive the
display with an all-neutral phase buffer. This allows measuring the
VCOM using the following protocol:

1. Perform IOCTL zero_waveform(set_zero_waveform_mode=1,
  zero_waveform_mode=1)
2. Confirm that the phase buffer is all zero. zero_waveform_mode needs
  to be 1 after ICTL zero_waveform(set_zero_waveform_mode=0).
3. Perform the VCOM measurement.
4. Perform IOCTL zero_waveform(set_zero_waveform_mode=1,
  zero_waveform_mode=0)
This commit is contained in:
hrdl 2025-05-05 21:29:59 +02:00 committed by Antoine Martin
parent 519e00dd66
commit 6fce0ee56e
3 changed files with 218 additions and 58 deletions

View file

@ -166,13 +166,15 @@ MODULE_FIRMWARE(EBC_CUSTOM_WF);
static const char *custom_wf_magic_version = "CLUT0002";
#define ROCKCHIP_EBC_WORK_ITEM_CHANGE_LUT 1
#define ROCKCHIP_EBC_WORK_ITEM_GLOBAL_REFRESH 2
#define ROCKCHIP_EBC_WORK_ITEM_INIT 4
#define ROCKCHIP_EBC_WORK_ITEM_SUSPEND 8
#define ROCKCHIP_EBC_WORK_ITEM_RESCHEDULE 16
#define ROCKCHIP_EBC_WORK_ITEM_ENABLE_FAST_MODE 32
#define ROCKCHIP_EBC_WORK_ITEM_DISABLE_FAST_MODE 64
#define ROCKCHIP_EBC_WORK_ITEM_CHANGE_LUT BIT(0)
#define ROCKCHIP_EBC_WORK_ITEM_GLOBAL_REFRESH BIT(1)
#define ROCKCHIP_EBC_WORK_ITEM_INIT BIT(2)
#define ROCKCHIP_EBC_WORK_ITEM_SUSPEND BIT(3)
#define ROCKCHIP_EBC_WORK_ITEM_RESCHEDULE BIT(4)
#define ROCKCHIP_EBC_WORK_ITEM_ENABLE_NORMAL_MODE BIT(5)
#define ROCKCHIP_EBC_WORK_ITEM_ENABLE_FAST_MODE BIT(6)
#define ROCKCHIP_EBC_WORK_ITEM_ENABLE_ZERO_WAVEFORM_MODE BIT(7)
#define ROCKCHIP_EBC_WORK_ITEM_DISABLE_ZERO_WAVEFORM_MODE BIT(8)
static const u8 dither_bayer_04[] = {
7, 8, 2, 10, 7, 8, 2, 10, 7, 8, 2, 10, 7, 8, 2, 10,
@ -512,35 +514,90 @@ static int ioctl_mode(struct drm_device *dev, void *data,
struct rockchip_ebc *ebc = dev_get_drvdata(dev->dev);
int ret = 0;
if (mode->set_mode) {
if (mode->set_driver_mode) {
spin_lock(&ebc->work_item_lock);
switch (mode->mode) {
case ROCKCHIP_EBC_MODE_NORMAL:
switch (mode->driver_mode) {
case ROCKCHIP_EBC_DRIVER_MODE_NORMAL:
ebc->work_item |=
ROCKCHIP_EBC_WORK_ITEM_DISABLE_FAST_MODE;
ebc->work_item &=
~ROCKCHIP_EBC_WORK_ITEM_ENABLE_FAST_MODE;
ROCKCHIP_EBC_WORK_ITEM_ENABLE_NORMAL_MODE;
break;
case ROCKCHIP_EBC_MODE_FAST:
case ROCKCHIP_EBC_DRIVER_MODE_FAST:
ebc->work_item |=
ROCKCHIP_EBC_WORK_ITEM_ENABLE_FAST_MODE;
ebc->work_item &=
~ROCKCHIP_EBC_WORK_ITEM_DISABLE_FAST_MODE;
break;
default:
ret = -EINVAL;
ret |= -EINVAL;
}
spin_unlock(&ebc->work_item_lock);
} else {
if (ebc->fast_mode)
mode->mode = ROCKCHIP_EBC_MODE_FAST;
else
mode->mode = ROCKCHIP_EBC_MODE_NORMAL;
mode->driver_mode = ebc->driver_mode;
}
if (mode->set_dither_mode) {
switch (mode->dither_mode) {
case ROCKCHIP_EBC_DITHER_MODE_BAYER:
ebc->dithering_texture_size_hint = 4;
ebc->dithering_texture = dither_bayer_04;
break;
case ROCKCHIP_EBC_DITHER_MODE_BLUE_NOISE_16:
ebc->dithering_texture_size_hint = 4;
ebc->dithering_texture = dither_blue_noise_16;
ebc->dithering_texture_size_hint = 16;
break;
case ROCKCHIP_EBC_DITHER_MODE_BLUE_NOISE_32:
ebc->dithering_texture = dither_blue_noise_32;
ebc->dithering_texture_size_hint = 32;
break;
default:
ret |= -EINVAL;
}
} else {
if (ebc->dithering_texture == dither_bayer_04)
mode->dither_mode = ROCKCHIP_EBC_DITHER_MODE_BAYER;
else if (ebc->dithering_texture == dither_blue_noise_16)
mode->dither_mode =
ROCKCHIP_EBC_DITHER_MODE_BLUE_NOISE_16;
else if (ebc->dithering_texture == dither_blue_noise_32)
mode->dither_mode =
ROCKCHIP_EBC_DITHER_MODE_BLUE_NOISE_32;
}
if (mode->set_redraw_delay) {
if (mode->redraw_delay != redraw_delay) {
redraw_delay = mode->redraw_delay;
spin_lock(&ebc->work_item_lock);
ebc->work_item |= ROCKCHIP_EBC_WORK_ITEM_CHANGE_LUT;
spin_unlock(&ebc->work_item_lock);
}
} else {
mode->redraw_delay = ebc->redraw_delay;
}
return ret;
}
static int ioctl_zero_waveform(struct drm_device *dev, void *data,
struct drm_file *file_priv)
{
struct drm_rockchip_ebc_zero_waveform *zero_waveform = data;
struct rockchip_ebc *ebc = dev_get_drvdata(dev->dev);
if (zero_waveform->set_zero_waveform_mode) {
spin_lock(&ebc->work_item_lock);
if (zero_waveform->zero_waveform_mode)
ebc->work_item |=
ROCKCHIP_EBC_WORK_ITEM_ENABLE_ZERO_WAVEFORM_MODE;
else
ebc->work_item |=
ROCKCHIP_EBC_WORK_ITEM_DISABLE_ZERO_WAVEFORM_MODE;
spin_unlock(&ebc->work_item_lock);
} else {
zero_waveform->zero_waveform_mode =
ebc->driver_mode ==
ROCKCHIP_EBC_DRIVER_MODE_ZERO_WAVEFORM;
}
return 0;
}
static const struct drm_ioctl_desc ioctls[DRM_COMMAND_END - DRM_COMMAND_BASE] = {
DRM_IOCTL_DEF_DRV(ROCKCHIP_EBC_GLOBAL_REFRESH,
ioctl_trigger_global_refresh, DRM_RENDER_ALLOW),
@ -551,6 +608,8 @@ static const struct drm_ioctl_desc ioctls[DRM_COMMAND_END - DRM_COMMAND_BASE] =
DRM_IOCTL_DEF_DRV(ROCKCHIP_EBC_RECT_HINTS, ioctl_rect_hints,
DRM_RENDER_ALLOW),
DRM_IOCTL_DEF_DRV(ROCKCHIP_EBC_MODE, ioctl_mode, DRM_RENDER_ALLOW),
DRM_IOCTL_DEF_DRV(ROCKCHIP_EBC_ZERO_WAVEFORM, ioctl_zero_waveform,
DRM_RENDER_ALLOW),
};
static const struct drm_driver rockchip_ebc_drm_driver = {
@ -648,6 +707,7 @@ static void rockchip_ebc_change_lut(struct rockchip_ebc *ebc)
}
}
}
ebc->redraw_delay = redraw_delay = redraw_delay - waiting_remaining;
// TODO: generalise for temperature ranges for which more than 31 phases are required
ebc->inner_0_15 = lut->lut[(0xf << ROCKCHIP_EBC_CUSTOM_WF_SEQ_SHIFT) +
lut->offsets[ROCKCHIP_EBC_CUSTOM_WF_DU]];
@ -680,7 +740,7 @@ static void rockchip_ebc_partial_refresh(struct rockchip_ebc *ebc,
struct drm_rect clip_ongoing = DRM_RECT_EMPTY_EXTANDABLE;
struct drm_rect clip_ongoing_or_waiting = clip_ongoing;
u8 work_item = ebc->work_item;
u32 work_item = ebc->work_item;
ktime_t time_last_start = ktime_get();
// TODO: move into logic setting these values
@ -709,7 +769,9 @@ static void rockchip_ebc_partial_refresh(struct rockchip_ebc *ebc,
bool awaiting_completion = false;
bool awaiting_start = false;
bool no_schedule_until_clip_empty = false;
bool is_enabling_fast_mode = false, is_disabling_fast_mode = false;
int enabling_mode = ebc->driver_mode;
int zero_waveform_mode_num_zero_phase_buffers = 0;
bool inhibit_suspend = false;
bool is_suspending = false;
ktime_t time_last_report = ktime_get();
int num_frames_since_last_report = 0;
@ -730,17 +792,50 @@ static void rockchip_ebc_partial_refresh(struct rockchip_ebc *ebc,
work_item |= ebc->work_item;
ebc->work_item = 0;
spin_unlock(&ebc->work_item_lock);
if (ebc->driver_mode ==
ROCKCHIP_EBC_DRIVER_MODE_ZERO_WAVEFORM &&
!(work_item &
ROCKCHIP_EBC_WORK_ITEM_DISABLE_ZERO_WAVEFORM_MODE)) {
pr_err("Ignoring work items until zero waveform mode is left explicitly");
work_item = 0;
}
if ((work_item &
ROCKCHIP_EBC_WORK_ITEM_ENABLE_FAST_MODE) &&
!ebc->fast_mode) {
ROCKCHIP_EBC_WORK_ITEM_DISABLE_ZERO_WAVEFORM_MODE) &&
(ebc->driver_mode ==
ROCKCHIP_EBC_DRIVER_MODE_ZERO_WAVEFORM)) {
pr_warn("Disabling zero waveform mode");
inhibit_suspend = false;
// Make sure to set a mode
if (!(work_item &
(ROCKCHIP_EBC_WORK_ITEM_ENABLE_ZERO_WAVEFORM_MODE |
ROCKCHIP_EBC_WORK_ITEM_ENABLE_FAST_MODE |
ROCKCHIP_EBC_WORK_ITEM_ENABLE_NORMAL_MODE))) {
work_item |=
ROCKCHIP_EBC_WORK_ITEM_ENABLE_NORMAL_MODE;
}
}
if ((work_item &
ROCKCHIP_EBC_WORK_ITEM_ENABLE_ZERO_WAVEFORM_MODE) &&
(ebc->driver_mode !=
ROCKCHIP_EBC_DRIVER_MODE_ZERO_WAVEFORM)) {
pr_info("Enabling zero waveform mode, finishing ongoing updates");
// Finish ongoing updates to resume with a zero-only inner/outer buffer
no_schedule_until_clip_empty = true;
is_enabling_fast_mode = true;
zero_waveform_mode_num_zero_phase_buffers = 0;
enabling_mode =
ROCKCHIP_EBC_DRIVER_MODE_ZERO_WAVEFORM;
inhibit_suspend = true;
} else if ((work_item &
ROCKCHIP_EBC_WORK_ITEM_ENABLE_FAST_MODE) &&
ebc->driver_mode != ROCKCHIP_EBC_DRIVER_MODE_FAST) {
no_schedule_until_clip_empty = true;
enabling_mode = ROCKCHIP_EBC_DRIVER_MODE_FAST;
work_item |= ROCKCHIP_EBC_WORK_ITEM_GLOBAL_REFRESH;
} else if ((work_item &
ROCKCHIP_EBC_WORK_ITEM_DISABLE_FAST_MODE) &&
ebc->fast_mode) {
ROCKCHIP_EBC_WORK_ITEM_ENABLE_NORMAL_MODE) &&
ebc->driver_mode != ROCKCHIP_EBC_DRIVER_MODE_NORMAL) {
no_schedule_until_clip_empty = true;
is_disabling_fast_mode = true;
enabling_mode = ROCKCHIP_EBC_DRIVER_MODE_NORMAL;
}
if (work_item & ROCKCHIP_EBC_WORK_ITEM_CHANGE_LUT) {
rockchip_ebc_change_lut(ebc);
@ -769,9 +864,14 @@ static void rockchip_ebc_partial_refresh(struct rockchip_ebc *ebc,
ebc->suspend_was_requested = 1;
} else if (work_item &
ROCKCHIP_EBC_WORK_ITEM_GLOBAL_REFRESH) {
if (ebc->fast_mode || is_enabling_fast_mode) {
ebc->fast_mode = false;
is_enabling_fast_mode = true;
if (ebc->driver_mode ==
ROCKCHIP_EBC_DRIVER_MODE_FAST ||
enabling_mode ==
ROCKCHIP_EBC_DRIVER_MODE_FAST) {
ebc->driver_mode = ROCKCHIP_EBC_DRIVER_MODE_NORMAL;
enabling_mode =
ROCKCHIP_EBC_DRIVER_MODE_FAST;
// TODO: use NEON implementation
for (int i = 0; i < ebc->num_pixels; ++i) {
u8 prelim = prelim_target[i] & 0xf0;
prelim_target[i] = prelim | prelim >> 4;
@ -781,12 +881,14 @@ static void rockchip_ebc_partial_refresh(struct rockchip_ebc *ebc,
kernel_neon_begin();
rockchip_ebc_schedule_advance_neon(ebc, prelim_target, hints, phase_buffer, &clip_ongoing, &clip_ongoing_or_waiting, 0, ROCKCHIP_EBC_CUSTOM_WF_GC16, 0, ROCKCHIP_EBC_HINT_REDRAW, true);
kernel_neon_end();
// Skip a single advance as we have performed one just now
skip_advance = true;
no_schedule_until_clip_empty = true;
ebc->suspend_was_requested = 0;
}
work_item = 0;
} else if (drm_rect_width(&clip_ongoing_or_waiting) <= 0 &&
!inhibit_suspend &&
(is_suspending ||
((drm_rect_width(&clip_incoming) <= 0) &&
(ktime_ms_delta(ktime_get(), time_last_start) >
@ -799,10 +901,18 @@ static void rockchip_ebc_partial_refresh(struct rockchip_ebc *ebc,
&clip_ongoing_or_waiting, &clip_incoming);
clip_incoming = DRM_RECT_EMPTY_EXTANDABLE;
}
pr_debug("%s frame=%d clip_ongoing=" DRM_RECT_FMT " clip_ongoing_or_waiting=" DRM_RECT_FMT " work_item=%d no_schedule_until_clip_empty=%d time_elapsed_since_last_start=%llu", __func__, frame, DRM_RECT_ARG(&clip_ongoing), DRM_RECT_ARG(&clip_ongoing_or_waiting), work_item, no_schedule_until_clip_empty, ktime_ms_delta(ktime_get(), time_last_start));
pr_debug(
"%s frame=%d clip_ongoing=" DRM_RECT_FMT
" clip_ongoing_or_waiting=" DRM_RECT_FMT
" work_item=%d no_schedule_until_clip_empty=%d time_elapsed_since_last_start=%llu",
__func__, frame, DRM_RECT_ARG(&clip_ongoing),
DRM_RECT_ARG(&clip_ongoing_or_waiting), work_item,
no_schedule_until_clip_empty,
ktime_ms_delta(ktime_get(), time_last_start));
pr_debug("%s ebc->driver_mode=%d enabling_mode=%d", __func__, ebc->driver_mode, enabling_mode);
if (drm_rect_width(&clip_ongoing_or_waiting) > 0 &&
!skip_advance) {
if (ebc->fast_mode) {
if (ebc->driver_mode == ROCKCHIP_EBC_DRIVER_MODE_FAST) {
kernel_neon_begin();
rockchip_ebc_schedule_advance_fast_neon(
ebc, prelim_target, hints,
@ -811,7 +921,7 @@ static void rockchip_ebc_partial_refresh(struct rockchip_ebc *ebc,
early_cancellation_addition, 0, 0, 0,
!no_schedule_until_clip_empty && !work_item);
kernel_neon_end();
} else {
} else if (ebc->driver_mode == ROCKCHIP_EBC_DRIVER_MODE_NORMAL) {
kernel_neon_begin();
rockchip_ebc_schedule_advance_neon(
ebc, prelim_target, hints,
@ -824,14 +934,24 @@ static void rockchip_ebc_partial_refresh(struct rockchip_ebc *ebc,
}
if (drm_rect_width(&clip_ongoing) <= 0 &&
no_schedule_until_clip_empty) {
no_schedule_until_clip_empty = false;
if (is_enabling_fast_mode) {
ebc->fast_mode = true;
is_enabling_fast_mode = false;
}
if (is_disabling_fast_mode) {
ebc->fast_mode = false;
is_disabling_fast_mode = false;
if (enabling_mode ==
ROCKCHIP_EBC_DRIVER_MODE_ZERO_WAVEFORM &&
zero_waveform_mode_num_zero_phase_buffers < 2) {
memset(phase_buffer, 0, ebc->phase_size);
if (++zero_waveform_mode_num_zero_phase_buffers >=
2) {
ebc->driver_mode =
ROCKCHIP_EBC_DRIVER_MODE_ZERO_WAVEFORM;
pr_info("Zero waveform mode enabled");
}
} else {
switch (enabling_mode) {
case ROCKCHIP_EBC_DRIVER_MODE_NORMAL:
case ROCKCHIP_EBC_DRIVER_MODE_FAST:
ebc->driver_mode = enabling_mode;
no_schedule_until_clip_empty = false;
break;
}
}
}
pr_debug("%s schedul2 frame=%d clip_ongoing=" DRM_RECT_FMT " clip_ongoing_or_waiting=" DRM_RECT_FMT,
@ -854,7 +974,9 @@ static void rockchip_ebc_partial_refresh(struct rockchip_ebc *ebc,
num_frames_since_last_report = 0;
max_advance_time_since_last_report = 0;
}
awaiting_start = drm_rect_width(&clip_ongoing) > 0;
awaiting_start = drm_rect_width(&clip_ongoing) > 0 ||
ebc->driver_mode ==
ROCKCHIP_EBC_DRIVER_MODE_ZERO_WAVEFORM;
if (awaiting_start) {
// TODO: make sure we've synced all zeros as well
int win_start = clip_ongoing.y1 * ebc->phase_pitch + (direct_mode ? clip_ongoing.x1 / 4 : clip_ongoing.x1);
@ -925,7 +1047,7 @@ static void rockchip_ebc_partial_refresh(struct rockchip_ebc *ebc,
s64 switch_buffer = 11700 - delta_advance - delta_wait - 1000;
// TODO: consider adding || !work_item
while(!is_suspending) {
while(!is_suspending && ebc->driver_mode != ROCKCHIP_EBC_DRIVER_MODE_ZERO_WAVEFORM) {
spin_lock(&ctx->buffer_switch_lock);
ctx->refresh_index = ctx->next_refresh_index;
for (int i = 0; i < 3; ++i) {
@ -2059,7 +2181,8 @@ static int rockchip_ebc_probe(struct platform_device *pdev)
*((u32 *) ebc->hardware_wf + 32) = 0xaaaaaaaa;
}
ebc->fast_mode = false;
ebc->driver_mode = ROCKCHIP_EBC_DRIVER_MODE_NORMAL;
ebc->redraw_delay = redraw_delay;
// Sensible temperature default
ebc->temperature = temp_override > 0 ? temp_override : 25;

View file

@ -120,10 +120,11 @@ struct rockchip_ebc {
u8 dithering_texture_size_hint;
// whether we use direct mode or 3WIN mode. Only 3WIN mode is implemented right now
bool direct_mode;
bool fast_mode;
int driver_mode;
int redraw_delay;
// Used to change the driver LUT due to temperature update, trigger a global refresh, or buffer changes
spinlock_t work_item_lock;
u8 work_item;
u32 work_item;
// used to detect when we are suspending so we can do different things to
// the ebc display depending on whether we are sleeping or suspending
int suspend_was_requested;

View file

@ -17,8 +17,13 @@ extern "C" {
#define ROCKCHIP_EBC_HINT_REDRAW 1 << 7
#define ROCKCHIP_EBC_HINT_MASK 0xf0
#define ROCKCHIP_EBC_MODE_NORMAL 0
#define ROCKCHIP_EBC_MODE_FAST 1
#define ROCKCHIP_EBC_DRIVER_MODE_NORMAL 0
#define ROCKCHIP_EBC_DRIVER_MODE_FAST 1
#define ROCKCHIP_EBC_DRIVER_MODE_ZERO_WAVEFORM 8
#define ROCKCHIP_EBC_DITHER_MODE_BAYER 0
#define ROCKCHIP_EBC_DITHER_MODE_BLUE_NOISE_16 1
#define ROCKCHIP_EBC_DITHER_MODE_BLUE_NOISE_32 2
struct drm_rockchip_ebc_trigger_global_refresh {
bool trigger_global_refresh;
@ -87,24 +92,55 @@ struct drm_rockchip_ebc_rect_hints {
};
/**
* struct drm_rockchip_ebc_mode - Query and set driver mode.
* @set_mode: apply mode instead of reading it to the same field.
* @mode: one of ROCKCHIP_EBC_MODE_NORMAL or ROCKCHIP_EBC_MODE_FAST.
* @padding: 64bit alignment padding.
* struct drm_rockchip_ebc_mode - Query and set driver/dither modes and
* redraw delay
* @set_driver_mode: apply driver_mode instead of reading it to the same
* field.
* @driver_mode: one of ROCKCHIP_EBC_DRIVER_MODE_NORMAL or
* ROCKCHIP_EBC_DRIVER_MODE_FAST.
* @set_dither_mode: apply dither_mode instead of reading it to the same
* field.
* @dither_mode: one of ROCKCHIP_EBC_DITHER_MODE_BAYER,
* ROCKCHIP_EBC_DITHER_MODE_BLUE_NOISE_16, or
* ROCKCHIP_EBC_DITHER_MODE_BLUE_NOISE_32.
* @redraw_delay: number of hardware frames to delay redraws.
* @set_redraw_delay: apply redraw_delay instead of reading it to the same
* field.
*/
struct drm_rockchip_ebc_mode {
__u8 set_mode;
__u8 mode;
__u8 padding[6];
__u8 set_driver_mode;
__u8 driver_mode;
__u8 set_dither_mode;
__u8 dither_mode;
__u16 redraw_delay;
__u8 set_redraw_delay;
__u8 _pad;
};
#define DRM_ROCKCHIP_EBC_NUM_IOCTLS 0x05
/**
* struct drm_rockchip_ebc_zero_waveform- Query and enable/disable zero
* waveform mode
* @set_zero_waveform_mode: apply zero_waveform_mode instead of reading it to
* the same field.
* @zero_waveform_mode: 0 for disable(d), 1 for enable(d)
*/
struct drm_rockchip_ebc_zero_waveform {
__u8 set_zero_waveform_mode;
__u8 zero_waveform_mode;
__u8 _pad[6];
};
#define DRM_ROCKCHIP_EBC_NUM_IOCTLS 0x06
#define DRM_IOCTL_ROCKCHIP_EBC_GLOBAL_REFRESH DRM_IOWR(DRM_COMMAND_BASE + 0x00, struct drm_rockchip_ebc_trigger_global_refresh)
#define DRM_IOCTL_ROCKCHIP_EBC_OFF_SCREEN DRM_IOW(DRM_COMMAND_BASE + 0x01, struct drm_rockchip_ebc_off_screen)
#define DRM_IOCTL_ROCKCHIP_EBC_EXTRACT_FBS DRM_IOWR(DRM_COMMAND_BASE + 0x02, struct drm_rockchip_ebc_extract_fbs)
#define DRM_IOCTL_ROCKCHIP_EBC_RECT_HINTS DRM_IOW(DRM_COMMAND_BASE + 0x03, struct drm_rockchip_ebc_rect_hints)
#define DRM_IOCTL_ROCKCHIP_EBC_MODE DRM_IOWR(DRM_COMMAND_BASE + 0x04, struct drm_rockchip_ebc_mode)
#define DRM_IOCTL_ROCKCHIP_EBC_ZERO_WAVEFORM DRM_IOWR(DRM_COMMAND_BASE + 0x05, struct drm_rockchip_ebc_zero_waveform)
#if defined(__cplusplus)
}
#endif
#endif /* __ROCKCHIP_EBC_DRM_H__*/