The host1x driver uses currently syncpoints statically from host1x point of
view. If we do a wait inside a job, it always has a constant value to wait.
host1x supports also doing relative syncpoint waits with respect to syncpoint
bases. This allows doing multiple operations inside a single submit and
waiting an operation to complete before moving to next one.
This set of patches adds support for syncpoint bases to host1x driver and
enables the support for gr2d client.
I have tested the series using the host1x test application (available at [0],
function test_wait_base() in tests/tegra/host1x/tegra_host1x_test.c) on cardhu.
I would appreciate help in reviewing the series and testing the patches
on other boards.
Changes in v2:
- Reordered various code blocks to improve code consistency
- Functions host1x_syncpt_alloc() and host1x_syncpt_request() take now a single
bitfield argument instead of separate boolean arguments
- Added a separate ioctl call for querying the base associated with some
syncpoint
[0] https://gitorious.org/linux-host1x/libdrm-host1x
Arto Merilainen (4):
gpu: host1x: Add 'flags' field to syncpt request
gpu: host1x: Add syncpoint base support
drm/tegra: Deliver syncpoint base to user space
drm/tegra: Reserve base for gr2d
drivers/gpu/host1x/dev.h | 2 ++
drivers/gpu/host1x/drm/drm.c | 25 +++++++++++++
drivers/gpu/host1x/drm/gr2d.c | 2 +-
drivers/gpu/host1x/hw/channel_hw.c | 19 ++++++++++
drivers/gpu/host1x/hw/hw_host1x01_uclass.h | 6 ++++
drivers/gpu/host1x/syncpt.c | 58 +++++++++++++++++++++++++-----
drivers/gpu/host1x/syncpt.h | 10 +++++-
include/uapi/drm/tegra_drm.h | 26 +++++++++-----
8 files changed, 128 insertions(+), 20 deletions(-)
--
1.8.1.5
This patch adds support for hardware syncpoint bases. This creates
a simple mechanism to stall the command FIFO until an operation is
completed.
Signed-off-by: Arto Merilainen <[email protected]>
---
drivers/gpu/host1x/dev.h | 2 ++
drivers/gpu/host1x/hw/channel_hw.c | 19 +++++++++++++
drivers/gpu/host1x/hw/hw_host1x01_uclass.h | 6 ++++
drivers/gpu/host1x/syncpt.c | 44 ++++++++++++++++++++++++++++--
drivers/gpu/host1x/syncpt.h | 7 +++++
5 files changed, 76 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/host1x/dev.h b/drivers/gpu/host1x/dev.h
index bed90a8..516ce0a 100644
--- a/drivers/gpu/host1x/dev.h
+++ b/drivers/gpu/host1x/dev.h
@@ -27,6 +27,7 @@
#include "job.h"
struct host1x_syncpt;
+struct host1x_syncpt_base;
struct host1x_channel;
struct host1x_cdma;
struct host1x_job;
@@ -102,6 +103,7 @@ struct host1x {
void __iomem *regs;
struct host1x_syncpt *syncpt;
+ struct host1x_syncpt_base *bases;
struct device *dev;
struct clk *clk;
diff --git a/drivers/gpu/host1x/hw/channel_hw.c b/drivers/gpu/host1x/hw/channel_hw.c
index ee19962..06f44bf 100644
--- a/drivers/gpu/host1x/hw/channel_hw.c
+++ b/drivers/gpu/host1x/hw/channel_hw.c
@@ -67,6 +67,21 @@ static void submit_gathers(struct host1x_job *job)
}
}
+static inline void synchronize_syncpt_base(struct host1x_job *job)
+{
+ struct host1x_channel *ch = job->channel;
+ struct host1x *host = dev_get_drvdata(ch->dev->parent);
+ struct host1x_syncpt *sp = host->syncpt + job->syncpt_id;
+ u32 base_id = sp->base->id;
+ u32 base_val = host1x_syncpt_read_max(sp);
+
+ host1x_cdma_push(&ch->cdma,
+ host1x_opcode_setclass(HOST1X_CLASS_HOST1X,
+ HOST1X_UCLASS_LOAD_SYNCPT_BASE, 1),
+ HOST1X_UCLASS_LOAD_SYNCPT_BASE_BASE_INDX_F(base_id) |
+ HOST1X_UCLASS_LOAD_SYNCPT_BASE_VALUE_F(base_val));
+}
+
static int channel_submit(struct host1x_job *job)
{
struct host1x_channel *ch = job->channel;
@@ -118,6 +133,10 @@ static int channel_submit(struct host1x_job *job)
host1x_syncpt_read_max(sp)));
}
+ /* Synchronize base register to allow using it for relative waiting */
+ if (sp->base)
+ synchronize_syncpt_base(job);
+
syncval = host1x_syncpt_incr_max(sp, user_syncpt_incrs);
job->syncpt_end = syncval;
diff --git a/drivers/gpu/host1x/hw/hw_host1x01_uclass.h b/drivers/gpu/host1x/hw/hw_host1x01_uclass.h
index 42f3ce1..f755359 100644
--- a/drivers/gpu/host1x/hw/hw_host1x01_uclass.h
+++ b/drivers/gpu/host1x/hw/hw_host1x01_uclass.h
@@ -111,6 +111,12 @@ static inline u32 host1x_uclass_wait_syncpt_base_offset_f(u32 v)
}
#define HOST1X_UCLASS_WAIT_SYNCPT_BASE_OFFSET_F(v) \
host1x_uclass_wait_syncpt_base_offset_f(v)
+static inline u32 host1x_uclass_load_syncpt_base_r(void)
+{
+ return 0xb;
+}
+#define HOST1X_UCLASS_LOAD_SYNCPT_BASE \
+ host1x_uclass_load_syncpt_base_r()
static inline u32 host1x_uclass_load_syncpt_base_base_indx_f(u32 v)
{
return (v & 0xff) << 24;
diff --git a/drivers/gpu/host1x/syncpt.c b/drivers/gpu/host1x/syncpt.c
index d376cd4..b5cb97c 100644
--- a/drivers/gpu/host1x/syncpt.c
+++ b/drivers/gpu/host1x/syncpt.c
@@ -30,6 +30,28 @@
#define SYNCPT_CHECK_PERIOD (2 * HZ)
#define MAX_STUCK_CHECK_COUNT 15
+static struct host1x_syncpt_base *host1x_base_alloc(struct host1x *host)
+{
+ struct host1x_syncpt_base *bases = host->bases;
+ unsigned int i;
+
+ for (i = 0; i < host->info->nb_bases; i++)
+ if (!bases[i].requested)
+ break;
+
+ if (i >= host->info->nb_bases)
+ return NULL;
+
+ bases[i].requested = true;
+ return &bases[i];
+}
+
+static void host1x_syncpt_base_free(struct host1x_syncpt_base *base)
+{
+ if (base)
+ base->requested = false;
+}
+
static struct host1x_syncpt *host1x_syncpt_alloc(struct host1x *host,
struct device *dev,
unsigned long flags)
@@ -44,6 +66,12 @@ static struct host1x_syncpt *host1x_syncpt_alloc(struct host1x *host,
if (i >= host->info->nb_pts)
return NULL;
+ if (flags & HOST1X_SYNCPT_HAS_BASE) {
+ sp->base = host1x_base_alloc(host);
+ if (!sp->base)
+ return NULL;
+ }
+
name = kasprintf(GFP_KERNEL, "%02d-%s", sp->id,
dev ? dev_name(dev) : NULL);
if (!name)
@@ -304,19 +332,29 @@ int host1x_syncpt_patch_wait(struct host1x_syncpt *sp, void *patch_addr)
int host1x_syncpt_init(struct host1x *host)
{
struct host1x_syncpt *syncpt;
+ struct host1x_syncpt_base *bases;
int i;
syncpt = devm_kzalloc(host->dev, sizeof(*syncpt) * host->info->nb_pts,
- GFP_KERNEL);
+ GFP_KERNEL);
if (!syncpt)
return -ENOMEM;
- for (i = 0; i < host->info->nb_pts; ++i) {
+ bases = devm_kzalloc(host->dev, sizeof(*bases) * host->info->nb_bases,
+ GFP_KERNEL);
+ if (!bases)
+ return -ENOMEM;
+
+ for (i = 0; i < host->info->nb_pts; i++) {
syncpt[i].id = i;
syncpt[i].host = host;
}
+ for (i = 0; i < host->info->nb_bases; i++)
+ bases[i].id = i;
+
host->syncpt = syncpt;
+ host->bases = bases;
host1x_syncpt_restore(host);
@@ -340,7 +378,9 @@ void host1x_syncpt_free(struct host1x_syncpt *sp)
if (!sp)
return;
+ host1x_syncpt_base_free(sp->base);
kfree(sp->name);
+ sp->base = NULL;
sp->dev = NULL;
sp->name = NULL;
sp->client_managed = false;
diff --git a/drivers/gpu/host1x/syncpt.h b/drivers/gpu/host1x/syncpt.h
index 1de7b58..28ec886 100644
--- a/drivers/gpu/host1x/syncpt.h
+++ b/drivers/gpu/host1x/syncpt.h
@@ -30,6 +30,11 @@ struct host1x;
/* Reserved for replacing an expired wait with a NOP */
#define HOST1X_SYNCPT_RESERVED 0
+struct host1x_syncpt_base {
+ unsigned int id;
+ bool requested;
+};
+
struct host1x_syncpt {
int id;
atomic_t min_val;
@@ -39,6 +44,7 @@ struct host1x_syncpt {
bool client_managed;
struct host1x *host;
struct device *dev;
+ struct host1x_syncpt_base *base;
/* interrupt data */
struct host1x_syncpt_intr intr;
@@ -154,6 +160,7 @@ u32 host1x_syncpt_id(struct host1x_syncpt *sp);
/* Allocate a sync point for a device. */
#define HOST1X_SYNCPT_CLIENT_MANAGED (1 << 0)
+#define HOST1X_SYNCPT_HAS_BASE (1 << 1)
struct host1x_syncpt *host1x_syncpt_request(struct device *dev,
unsigned long flags);
--
1.8.1.5
This patch adds a separate ioctl for delivering syncpoint base number
to user space. If the syncpoint does not have an associated base, the
function returns -ENXIO.
Signed-off-by: Arto Merilainen <[email protected]>
---
drivers/gpu/host1x/drm/drm.c | 25 +++++++++++++++++++++++++
include/uapi/drm/tegra_drm.h | 26 +++++++++++++++++---------
2 files changed, 42 insertions(+), 9 deletions(-)
diff --git a/drivers/gpu/host1x/drm/drm.c b/drivers/gpu/host1x/drm/drm.c
index 8c61cee..c11cbf5 100644
--- a/drivers/gpu/host1x/drm/drm.c
+++ b/drivers/gpu/host1x/drm/drm.c
@@ -472,6 +472,30 @@ static int tegra_get_syncpt(struct drm_device *drm, void *data,
return 0;
}
+static int tegra_get_syncpt_base(struct drm_device *drm, void *data,
+ struct drm_file *file)
+{
+ struct drm_tegra_get_syncpt_base *args = data;
+ struct host1x_drm_file *fpriv = file->driver_priv;
+ struct host1x_drm_context *context =
+ (struct host1x_drm_context *)(uintptr_t)args->context;
+ struct host1x_syncpt_base *base;
+
+ if (!host1x_drm_file_owns_context(fpriv, context))
+ return -ENODEV;
+
+ if (args->index >= context->client->num_syncpts)
+ return -EINVAL;
+
+ base = context->client->syncpts[args->index]->base;
+ if (!base)
+ return -ENXIO;
+
+ args->base_id = base->id;
+
+ return 0;
+}
+
static int tegra_submit(struct drm_device *drm, void *data,
struct drm_file *file)
{
@@ -498,6 +522,7 @@ static const struct drm_ioctl_desc tegra_drm_ioctls[] = {
DRM_IOCTL_DEF_DRV(TEGRA_CLOSE_CHANNEL, tegra_close_channel, DRM_UNLOCKED),
DRM_IOCTL_DEF_DRV(TEGRA_GET_SYNCPT, tegra_get_syncpt, DRM_UNLOCKED),
DRM_IOCTL_DEF_DRV(TEGRA_SUBMIT, tegra_submit, DRM_UNLOCKED),
+ DRM_IOCTL_DEF_DRV(TEGRA_GET_SYNCPT_BASE, tegra_get_syncpt_base, DRM_UNLOCKED),
#endif
};
diff --git a/include/uapi/drm/tegra_drm.h b/include/uapi/drm/tegra_drm.h
index 73bde4e..8b8094c 100644
--- a/include/uapi/drm/tegra_drm.h
+++ b/include/uapi/drm/tegra_drm.h
@@ -65,6 +65,12 @@ struct drm_tegra_get_syncpt {
__u32 id;
};
+struct drm_tegra_get_syncpt_base {
+ __u64 context;
+ __u32 index;
+ __u32 base_id;
+};
+
struct drm_tegra_syncpt {
__u32 id;
__u32 incrs;
@@ -115,15 +121,16 @@ struct drm_tegra_submit {
__u32 reserved[5]; /* future expansion */
};
-#define DRM_TEGRA_GEM_CREATE 0x00
-#define DRM_TEGRA_GEM_MMAP 0x01
-#define DRM_TEGRA_SYNCPT_READ 0x02
-#define DRM_TEGRA_SYNCPT_INCR 0x03
-#define DRM_TEGRA_SYNCPT_WAIT 0x04
-#define DRM_TEGRA_OPEN_CHANNEL 0x05
-#define DRM_TEGRA_CLOSE_CHANNEL 0x06
-#define DRM_TEGRA_GET_SYNCPT 0x07
-#define DRM_TEGRA_SUBMIT 0x08
+#define DRM_TEGRA_GEM_CREATE 0x00
+#define DRM_TEGRA_GEM_MMAP 0x01
+#define DRM_TEGRA_SYNCPT_READ 0x02
+#define DRM_TEGRA_SYNCPT_INCR 0x03
+#define DRM_TEGRA_SYNCPT_WAIT 0x04
+#define DRM_TEGRA_OPEN_CHANNEL 0x05
+#define DRM_TEGRA_CLOSE_CHANNEL 0x06
+#define DRM_TEGRA_GET_SYNCPT 0x07
+#define DRM_TEGRA_SUBMIT 0x08
+#define DRM_TEGRA_GET_SYNCPT_BASE 0x09
#define DRM_IOCTL_TEGRA_GEM_CREATE DRM_IOWR(DRM_COMMAND_BASE + DRM_TEGRA_GEM_CREATE, struct drm_tegra_gem_create)
#define DRM_IOCTL_TEGRA_GEM_MMAP DRM_IOWR(DRM_COMMAND_BASE + DRM_TEGRA_GEM_MMAP, struct drm_tegra_gem_mmap)
@@ -134,5 +141,6 @@ struct drm_tegra_submit {
#define DRM_IOCTL_TEGRA_CLOSE_CHANNEL DRM_IOWR(DRM_COMMAND_BASE + DRM_TEGRA_CLOSE_CHANNEL, struct drm_tegra_open_channel)
#define DRM_IOCTL_TEGRA_GET_SYNCPT DRM_IOWR(DRM_COMMAND_BASE + DRM_TEGRA_GET_SYNCPT, struct drm_tegra_get_syncpt)
#define DRM_IOCTL_TEGRA_SUBMIT DRM_IOWR(DRM_COMMAND_BASE + DRM_TEGRA_SUBMIT, struct drm_tegra_submit)
+#define DRM_IOCTL_TEGRA_GET_SYNCPT_BASE DRM_IOWR(DRM_COMMAND_BASE + DRM_TEGRA_GET_SYNCPT_BASE, struct drm_tegra_get_syncpt_base)
#endif
--
1.8.1.5
This patch modifies the gr2d to reserve a base for syncpoint.
Signed-off-by: Arto Merilainen <[email protected]>
---
drivers/gpu/host1x/drm/gr2d.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/gpu/host1x/drm/gr2d.c b/drivers/gpu/host1x/drm/gr2d.c
index 7efd97b..337e1ad 100644
--- a/drivers/gpu/host1x/drm/gr2d.c
+++ b/drivers/gpu/host1x/drm/gr2d.c
@@ -285,7 +285,7 @@ static int gr2d_probe(struct platform_device *pdev)
if (!gr2d->channel)
return -ENOMEM;
- *syncpts = host1x_syncpt_request(dev, 0);
+ *syncpts = host1x_syncpt_request(dev, HOST1X_SYNCPT_HAS_BASE);
if (!(*syncpts)) {
host1x_channel_free(gr2d->channel);
return -ENOMEM;
--
1.8.1.5
Functions host1x_syncpt_request() and _host1x_syncpt_alloc() have
been taking a separate boolean flag ('client_managed') for indicating
if the syncpoint value should be tracked by the host1x driver.
This patch converts the field into generic 'flags' field so that
we can easily add more information while requesting a syncpoint.
Clients are adapted to use the new interface accordingly.
Signed-off-by: Arto Merilainen <[email protected]>
---
drivers/gpu/host1x/drm/gr2d.c | 2 +-
drivers/gpu/host1x/syncpt.c | 14 +++++++-------
drivers/gpu/host1x/syncpt.h | 3 ++-
3 files changed, 10 insertions(+), 9 deletions(-)
diff --git a/drivers/gpu/host1x/drm/gr2d.c b/drivers/gpu/host1x/drm/gr2d.c
index 27ffcf1..7efd97b 100644
--- a/drivers/gpu/host1x/drm/gr2d.c
+++ b/drivers/gpu/host1x/drm/gr2d.c
@@ -285,7 +285,7 @@ static int gr2d_probe(struct platform_device *pdev)
if (!gr2d->channel)
return -ENOMEM;
- *syncpts = host1x_syncpt_request(dev, false);
+ *syncpts = host1x_syncpt_request(dev, 0);
if (!(*syncpts)) {
host1x_channel_free(gr2d->channel);
return -ENOMEM;
diff --git a/drivers/gpu/host1x/syncpt.c b/drivers/gpu/host1x/syncpt.c
index 409745b..d376cd4 100644
--- a/drivers/gpu/host1x/syncpt.c
+++ b/drivers/gpu/host1x/syncpt.c
@@ -30,9 +30,9 @@
#define SYNCPT_CHECK_PERIOD (2 * HZ)
#define MAX_STUCK_CHECK_COUNT 15
-static struct host1x_syncpt *_host1x_syncpt_alloc(struct host1x *host,
- struct device *dev,
- bool client_managed)
+static struct host1x_syncpt *host1x_syncpt_alloc(struct host1x *host,
+ struct device *dev,
+ unsigned long flags)
{
int i;
struct host1x_syncpt *sp = host->syncpt;
@@ -51,7 +51,7 @@ static struct host1x_syncpt *_host1x_syncpt_alloc(struct host1x *host,
sp->dev = dev;
sp->name = name;
- sp->client_managed = client_managed;
+ sp->client_managed = !!(flags & HOST1X_SYNCPT_CLIENT_MANAGED);
return sp;
}
@@ -321,7 +321,7 @@ int host1x_syncpt_init(struct host1x *host)
host1x_syncpt_restore(host);
/* Allocate sync point to use for clearing waits for expired fences */
- host->nop_sp = _host1x_syncpt_alloc(host, NULL, false);
+ host->nop_sp = host1x_syncpt_alloc(host, NULL, 0);
if (!host->nop_sp)
return -ENOMEM;
@@ -329,10 +329,10 @@ int host1x_syncpt_init(struct host1x *host)
}
struct host1x_syncpt *host1x_syncpt_request(struct device *dev,
- bool client_managed)
+ unsigned long flags)
{
struct host1x *host = dev_get_drvdata(dev->parent);
- return _host1x_syncpt_alloc(host, dev, client_managed);
+ return host1x_syncpt_alloc(host, dev, flags);
}
void host1x_syncpt_free(struct host1x_syncpt *sp)
diff --git a/drivers/gpu/host1x/syncpt.h b/drivers/gpu/host1x/syncpt.h
index 267c0b9..1de7b58 100644
--- a/drivers/gpu/host1x/syncpt.h
+++ b/drivers/gpu/host1x/syncpt.h
@@ -153,8 +153,9 @@ int host1x_syncpt_patch_wait(struct host1x_syncpt *sp, void *patch_addr);
u32 host1x_syncpt_id(struct host1x_syncpt *sp);
/* Allocate a sync point for a device. */
+#define HOST1X_SYNCPT_CLIENT_MANAGED (1 << 0)
struct host1x_syncpt *host1x_syncpt_request(struct device *dev,
- bool client_managed);
+ unsigned long flags);
/* Free a sync point. */
void host1x_syncpt_free(struct host1x_syncpt *sp);
--
1.8.1.5
On 14.10.2013 15:21, Arto Merilainen wrote:
> The host1x driver uses currently syncpoints statically from host1x point of
> view. If we do a wait inside a job, it always has a constant value to wait.
> host1x supports also doing relative syncpoint waits with respect to syncpoint
> bases. This allows doing multiple operations inside a single submit and
> waiting an operation to complete before moving to next one.
>
> This set of patches adds support for syncpoint bases to host1x driver and
> enables the support for gr2d client.
>
> I have tested the series using the host1x test application (available at [0],
> function test_wait_base() in tests/tegra/host1x/tegra_host1x_test.c) on cardhu.
> I would appreciate help in reviewing the series and testing the patches
> on other boards.
>
> Changes in v2:
> - Reordered various code blocks to improve code consistency
> - Functions host1x_syncpt_alloc() and host1x_syncpt_request() take now a single
> bitfield argument instead of separate boolean arguments
> - Added a separate ioctl call for querying the base associated with some
> syncpoint
>
> [0] https://gitorious.org/linux-host1x/libdrm-host1x
>
> Arto Merilainen (4):
> gpu: host1x: Add 'flags' field to syncpt request
> gpu: host1x: Add syncpoint base support
> drm/tegra: Deliver syncpoint base to user space
> drm/tegra: Reserve base for gr2d
>
> drivers/gpu/host1x/dev.h | 2 ++
> drivers/gpu/host1x/drm/drm.c | 25 +++++++++++++
> drivers/gpu/host1x/drm/gr2d.c | 2 +-
> drivers/gpu/host1x/hw/channel_hw.c | 19 ++++++++++
> drivers/gpu/host1x/hw/hw_host1x01_uclass.h | 6 ++++
> drivers/gpu/host1x/syncpt.c | 58 +++++++++++++++++++++++++-----
> drivers/gpu/host1x/syncpt.h | 10 +++++-
> include/uapi/drm/tegra_drm.h | 26 +++++++++-----
> 8 files changed, 128 insertions(+), 20 deletions(-)
>
The series,
Reviewed-by: Terje Bergstrom <[email protected]>
Terje
On Mon, Oct 14, 2013 at 03:21:51PM +0300, Arto Merilainen wrote:
> The host1x driver uses currently syncpoints statically from host1x point of
> view. If we do a wait inside a job, it always has a constant value to wait.
> host1x supports also doing relative syncpoint waits with respect to syncpoint
> bases. This allows doing multiple operations inside a single submit and
> waiting an operation to complete before moving to next one.
>
> This set of patches adds support for syncpoint bases to host1x driver and
> enables the support for gr2d client.
>
> I have tested the series using the host1x test application (available at [0],
> function test_wait_base() in tests/tegra/host1x/tegra_host1x_test.c) on cardhu.
> I would appreciate help in reviewing the series and testing the patches
> on other boards.
>
> Changes in v2:
> - Reordered various code blocks to improve code consistency
> - Functions host1x_syncpt_alloc() and host1x_syncpt_request() take now a single
> bitfield argument instead of separate boolean arguments
> - Added a separate ioctl call for querying the base associated with some
> syncpoint
>
> [0] https://gitorious.org/linux-host1x/libdrm-host1x
>
> Arto Merilainen (4):
> gpu: host1x: Add 'flags' field to syncpt request
> gpu: host1x: Add syncpoint base support
> drm/tegra: Deliver syncpoint base to user space
> drm/tegra: Reserve base for gr2d
>
> drivers/gpu/host1x/dev.h | 2 ++
> drivers/gpu/host1x/drm/drm.c | 25 +++++++++++++
> drivers/gpu/host1x/drm/gr2d.c | 2 +-
> drivers/gpu/host1x/hw/channel_hw.c | 19 ++++++++++
> drivers/gpu/host1x/hw/hw_host1x01_uclass.h | 6 ++++
> drivers/gpu/host1x/syncpt.c | 58 +++++++++++++++++++++++++-----
> drivers/gpu/host1x/syncpt.h | 10 +++++-
> include/uapi/drm/tegra_drm.h | 26 +++++++++-----
> 8 files changed, 128 insertions(+), 20 deletions(-)
I've applied the series, though I had to rework some of it because
host1x and DRM have significantly changed in the meantime.
Thierry