diff options
author | Terje Bergstrom <tbergstrom@nvidia.com> | 2012-09-03 12:55:35 +0300 |
---|---|---|
committer | Simone Willett <swillett@nvidia.com> | 2012-09-06 15:56:08 -0700 |
commit | 92e929e6ebd1f38f41682277fb1a5a554e124b94 (patch) | |
tree | 2a15979239ca3a5c74a3f2146b7251aa0e9132ba | |
parent | 831a9d6d26863d685b7f3c31ad034f959fbf1f64 (diff) |
video: tegra: host: Make 3D reg read device op
Make 3D register read an operation of the 3D device. For now,
implement it for Tegra20 and use the same implementation on other
chips.
Bug 1038891
Change-Id: Ic483d3554c5a3bad38d54043997a2a416cf37ea9
Signed-off-by: Terje Bergstrom <tbergstrom@nvidia.com>
Reviewed-on: http://git-master/r/129951
Reviewed-by: Automatic_Commit_Validation_User
-rw-r--r-- | drivers/video/tegra/host/bus_client.c | 3 | ||||
-rw-r--r-- | drivers/video/tegra/host/chip_support.h | 4 | ||||
-rw-r--r-- | drivers/video/tegra/host/gr3d/gr3d.c | 6 | ||||
-rw-r--r-- | drivers/video/tegra/host/gr3d/gr3d_t20.c | 191 | ||||
-rw-r--r-- | drivers/video/tegra/host/gr3d/gr3d_t20.h | 9 | ||||
-rw-r--r-- | drivers/video/tegra/host/host1x/host1x_channel.c | 189 | ||||
-rw-r--r-- | drivers/video/tegra/host/nvhost_channel.c | 11 | ||||
-rw-r--r-- | drivers/video/tegra/host/nvhost_channel.h | 2 | ||||
-rw-r--r-- | include/linux/nvhost.h | 9 |
9 files changed, 227 insertions, 197 deletions
diff --git a/drivers/video/tegra/host/bus_client.c b/drivers/video/tegra/host/bus_client.c index 5ec200aa5e04..c56e01416f61 100644 --- a/drivers/video/tegra/host/bus_client.c +++ b/drivers/video/tegra/host/bus_client.c @@ -394,8 +394,7 @@ static int nvhost_ioctl_channel_flush( static int nvhost_ioctl_channel_read_3d_reg(struct nvhost_channel_userctx *ctx, struct nvhost_read_3d_reg_args *args) { - BUG_ON(!channel_op().read3dreg); - return channel_op().read3dreg(ctx->ch, ctx->hwctx, + return nvhost_channel_read_reg(ctx->ch, ctx->hwctx, args->offset, &args->value); } diff --git a/drivers/video/tegra/host/chip_support.h b/drivers/video/tegra/host/chip_support.h index 26be55c1844a..9a889dd15589 100644 --- a/drivers/video/tegra/host/chip_support.h +++ b/drivers/video/tegra/host/chip_support.h @@ -48,10 +48,6 @@ struct nvhost_channel_ops { struct nvhost_master *, int chid); int (*submit)(struct nvhost_job *job); - int (*read3dreg)(struct nvhost_channel *channel, - struct nvhost_hwctx *hwctx, - u32 offset, - u32 *value); int (*save_context)(struct nvhost_channel *channel); int (*drain_read_fifo)(struct nvhost_channel *ch, u32 *ptr, unsigned int count, unsigned int *pending); diff --git a/drivers/video/tegra/host/gr3d/gr3d.c b/drivers/video/tegra/host/gr3d/gr3d.c index ea39cfe88163..ffedb4d94cd8 100644 --- a/drivers/video/tegra/host/gr3d/gr3d.c +++ b/drivers/video/tegra/host/gr3d/gr3d.c @@ -169,6 +169,8 @@ struct gr3d_desc { int (*prepare_poweroff)(struct nvhost_device *dev); struct nvhost_hwctx_handler *(*alloc_hwctx_handler)(u32 syncpt, u32 waitbase, struct nvhost_channel *ch); + int (*read_reg)(struct nvhost_device *dev, struct nvhost_channel *ch, + struct nvhost_hwctx *hwctx, u32 offset, u32 *value); }; static const struct gr3d_desc gr3d[] = { @@ -181,6 +183,7 @@ static const struct gr3d_desc gr3d[] = { .deinit = NULL, .prepare_poweroff = nvhost_gr3d_prepare_power_off, .alloc_hwctx_handler = nvhost_gr3d_t20_ctxhandler_init, + .read_reg = nvhost_gr3d_t20_read_reg, }, [gr3d_02] = { .finalize_poweron = NULL, @@ -191,6 +194,7 @@ static const struct gr3d_desc gr3d[] = { .deinit = nvhost_scale3d_deinit, .prepare_poweroff = nvhost_gr3d_prepare_power_off, .alloc_hwctx_handler = nvhost_gr3d_t30_ctxhandler_init, + .read_reg = nvhost_gr3d_t20_read_reg, }, [gr3d_03] = { .busy = nvhost_scale3d_notify_busy, @@ -201,6 +205,7 @@ static const struct gr3d_desc gr3d[] = { .prepare_poweroff = nvhost_gr3d_t114_prepare_power_off, .finalize_poweron = nvhost_gr3d_t114_finalize_power_on, .alloc_hwctx_handler = nvhost_gr3d_t114_ctxhandler_init, + .read_reg = nvhost_gr3d_t20_read_reg, }, }; @@ -229,6 +234,7 @@ static int __devinit gr3d_probe(struct nvhost_device *dev, drv->deinit = gr3d[index].deinit; drv->prepare_poweroff = gr3d[index].prepare_poweroff; drv->alloc_hwctx_handler = gr3d[index].alloc_hwctx_handler; + drv->read_reg = gr3d[index].read_reg; return nvhost_client_device_init(dev); } diff --git a/drivers/video/tegra/host/gr3d/gr3d_t20.c b/drivers/video/tegra/host/gr3d/gr3d_t20.c index 694b00527790..e34c5eafccef 100644 --- a/drivers/video/tegra/host/gr3d/gr3d_t20.c +++ b/drivers/video/tegra/host/gr3d/gr3d_t20.c @@ -25,6 +25,8 @@ #include "gr3d.h" #include "chip_support.h" #include "nvhost_memmgr.h" +#include "nvhost_job.h" +#include "nvhost_acm.h" #include <linux/slab.h> @@ -397,3 +399,192 @@ struct nvhost_hwctx_handler *nvhost_gr3d_t20_ctxhandler_init( return &p->h; } + +int nvhost_gr3d_t20_read_reg( + struct nvhost_device *dev, + struct nvhost_channel *channel, + struct nvhost_hwctx *hwctx, + u32 offset, + u32 *value) +{ + struct host1x_hwctx *hwctx_to_save = NULL; + struct nvhost_hwctx_handler *h = hwctx->h; + struct host1x_hwctx_handler *p = to_host1x_hwctx_handler(h); + bool need_restore = false; + u32 syncpt_incrs = 4; + unsigned int pending = 0; + DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wq); + void *ref; + void *ctx_waiter, *read_waiter, *completed_waiter; + struct nvhost_job *job; + u32 syncval; + int err; + + if (hwctx && hwctx->has_timedout) + return -ETIMEDOUT; + + ctx_waiter = nvhost_intr_alloc_waiter(); + read_waiter = nvhost_intr_alloc_waiter(); + completed_waiter = nvhost_intr_alloc_waiter(); + if (!ctx_waiter || !read_waiter || !completed_waiter) { + err = -ENOMEM; + goto done; + } + + job = nvhost_job_alloc(channel, hwctx, + NULL, + nvhost_get_host(dev)->memmgr, 0, 0); + if (!job) { + err = -ENOMEM; + goto done; + } + + /* keep module powered */ + nvhost_module_busy(dev); + + /* get submit lock */ + err = mutex_lock_interruptible(&channel->submitlock); + if (err) { + nvhost_module_idle(dev); + return err; + } + + /* context switch */ + if (channel->cur_ctx != hwctx) { + hwctx_to_save = channel->cur_ctx ? + to_host1x_hwctx(channel->cur_ctx) : NULL; + if (hwctx_to_save) { + syncpt_incrs += hwctx_to_save->save_incrs; + hwctx_to_save->hwctx.valid = true; + nvhost_job_get_hwctx(job, &hwctx_to_save->hwctx); + } + channel->cur_ctx = hwctx; + if (channel->cur_ctx && channel->cur_ctx->valid) { + need_restore = true; + syncpt_incrs += to_host1x_hwctx(channel->cur_ctx) + ->restore_incrs; + } + } + + syncval = nvhost_syncpt_incr_max(&nvhost_get_host(dev)->syncpt, + p->syncpt, syncpt_incrs); + + job->syncpt_id = p->syncpt; + job->syncpt_incrs = syncpt_incrs; + job->syncpt_end = syncval; + + /* begin a CDMA submit */ + nvhost_cdma_begin(&channel->cdma, job); + + /* push save buffer (pre-gather setup depends on unit) */ + if (hwctx_to_save) + h->save_push(&hwctx_to_save->hwctx, &channel->cdma); + + /* gather restore buffer */ + if (need_restore) + nvhost_cdma_push(&channel->cdma, + nvhost_opcode_gather(to_host1x_hwctx(channel->cur_ctx) + ->restore_size), + to_host1x_hwctx(channel->cur_ctx)->restore_phys); + + /* Switch to 3D - wait for it to complete what it was doing */ + nvhost_cdma_push(&channel->cdma, + nvhost_opcode_setclass(NV_GRAPHICS_3D_CLASS_ID, 0, 0), + nvhost_opcode_imm_incr_syncpt( + host1x_uclass_incr_syncpt_cond_op_done_v(), + p->syncpt)); + nvhost_cdma_push(&channel->cdma, + nvhost_opcode_setclass(NV_HOST1X_CLASS_ID, + host1x_uclass_wait_syncpt_base_r(), 1), + nvhost_class_host_wait_syncpt_base(p->syncpt, + p->waitbase, 1)); + /* Tell 3D to send register value to FIFO */ + nvhost_cdma_push(&channel->cdma, + nvhost_opcode_nonincr(host1x_uclass_indoff_r(), 1), + nvhost_class_host_indoff_reg_read(NV_HOST_MODULE_GR3D, + offset, false)); + nvhost_cdma_push(&channel->cdma, + nvhost_opcode_imm(host1x_uclass_inddata_r(), 0), + NVHOST_OPCODE_NOOP); + /* Increment syncpt to indicate that FIFO can be read */ + nvhost_cdma_push(&channel->cdma, + nvhost_opcode_imm_incr_syncpt( + host1x_uclass_incr_syncpt_cond_immediate_v(), + p->syncpt), + NVHOST_OPCODE_NOOP); + /* Wait for value to be read from FIFO */ + nvhost_cdma_push(&channel->cdma, + nvhost_opcode_nonincr(host1x_uclass_wait_syncpt_base_r(), 1), + nvhost_class_host_wait_syncpt_base(p->syncpt, + p->waitbase, 3)); + /* Indicate submit complete */ + nvhost_cdma_push(&channel->cdma, + nvhost_opcode_nonincr(host1x_uclass_incr_syncpt_base_r(), 1), + nvhost_class_host_incr_syncpt_base(p->waitbase, 4)); + nvhost_cdma_push(&channel->cdma, + NVHOST_OPCODE_NOOP, + nvhost_opcode_imm_incr_syncpt( + host1x_uclass_incr_syncpt_cond_immediate_v(), + p->syncpt)); + + /* end CDMA submit */ + nvhost_cdma_end(&channel->cdma, job); + nvhost_job_put(job); + job = NULL; + + /* + * schedule a context save interrupt (to drain the host FIFO + * if necessary, and to release the restore buffer) + */ + if (hwctx_to_save) { + err = nvhost_intr_add_action( + &nvhost_get_host(dev)->intr, + p->syncpt, + syncval - syncpt_incrs + + hwctx_to_save->save_incrs + - 1, + NVHOST_INTR_ACTION_CTXSAVE, hwctx_to_save, + ctx_waiter, + NULL); + ctx_waiter = NULL; + WARN(err, "Failed to set context save interrupt"); + } + + /* Wait for FIFO to be ready */ + err = nvhost_intr_add_action(&nvhost_get_host(dev)->intr, + p->syncpt, syncval - 2, + NVHOST_INTR_ACTION_WAKEUP, &wq, + read_waiter, + &ref); + read_waiter = NULL; + WARN(err, "Failed to set wakeup interrupt"); + wait_event(wq, + nvhost_syncpt_is_expired(&nvhost_get_host(dev)->syncpt, + p->syncpt, syncval - 2)); + nvhost_intr_put_ref(&nvhost_get_host(dev)->intr, p->syncpt, + ref); + + /* Read the register value from FIFO */ + err = nvhost_channel_drain_read_fifo(channel, value, 1, &pending); + + /* Indicate we've read the value */ + nvhost_syncpt_cpu_incr(&nvhost_get_host(dev)->syncpt, + p->syncpt); + + /* Schedule a submit complete interrupt */ + err = nvhost_intr_add_action(&nvhost_get_host(dev)->intr, + p->syncpt, syncval, + NVHOST_INTR_ACTION_SUBMIT_COMPLETE, channel, + completed_waiter, NULL); + completed_waiter = NULL; + WARN(err, "Failed to set submit complete interrupt"); + + mutex_unlock(&channel->submitlock); + +done: + kfree(ctx_waiter); + kfree(read_waiter); + kfree(completed_waiter); + return err; +} + diff --git a/drivers/video/tegra/host/gr3d/gr3d_t20.h b/drivers/video/tegra/host/gr3d/gr3d_t20.h index e6fb8fdf8aba..953739aa9cc0 100644 --- a/drivers/video/tegra/host/gr3d/gr3d_t20.h +++ b/drivers/video/tegra/host/gr3d/gr3d_t20.h @@ -24,10 +24,19 @@ #include <linux/types.h> struct nvhost_hwctx_handler; +struct nvhost_hwctx; struct nvhost_channel; +struct nvhost_device; struct nvhost_hwctx_handler *nvhost_gr3d_t20_ctxhandler_init( u32 syncpt, u32 waitbase, struct nvhost_channel *ch); +int nvhost_gr3d_t20_read_reg( + struct nvhost_device *dev, + struct nvhost_channel *channel, + struct nvhost_hwctx *hwctx, + u32 offset, + u32 *value); + #endif diff --git a/drivers/video/tegra/host/host1x/host1x_channel.c b/drivers/video/tegra/host/host1x/host1x_channel.c index 0274413ff698..87a0d2bfb9a3 100644 --- a/drivers/video/tegra/host/host1x/host1x_channel.c +++ b/drivers/video/tegra/host/host1x/host1x_channel.c @@ -310,194 +310,6 @@ error: return err; } -static int host1x_channel_read_3d_reg( - struct nvhost_channel *channel, - struct nvhost_hwctx *hwctx, - u32 offset, - u32 *value) -{ - struct host1x_hwctx *hwctx_to_save = NULL; - struct nvhost_hwctx_handler *h = hwctx->h; - struct host1x_hwctx_handler *p = to_host1x_hwctx_handler(h); - bool need_restore = false; - u32 syncpt_incrs = 4; - unsigned int pending = 0; - DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wq); - void *ref; - void *ctx_waiter, *read_waiter, *completed_waiter; - struct nvhost_job *job; - u32 syncval; - int err; - - if (hwctx && hwctx->has_timedout) - return -ETIMEDOUT; - - ctx_waiter = nvhost_intr_alloc_waiter(); - read_waiter = nvhost_intr_alloc_waiter(); - completed_waiter = nvhost_intr_alloc_waiter(); - if (!ctx_waiter || !read_waiter || !completed_waiter) { - err = -ENOMEM; - goto done; - } - - job = nvhost_job_alloc(channel, hwctx, - NULL, - nvhost_get_host(channel->dev)->memmgr, 0, 0); - if (!job) { - err = -ENOMEM; - goto done; - } - - /* keep module powered */ - nvhost_module_busy(channel->dev); - - /* get submit lock */ - err = mutex_lock_interruptible(&channel->submitlock); - if (err) { - nvhost_module_idle(channel->dev); - return err; - } - - /* context switch */ - if (channel->cur_ctx != hwctx) { - hwctx_to_save = channel->cur_ctx ? - to_host1x_hwctx(channel->cur_ctx) : NULL; - if (hwctx_to_save) { - syncpt_incrs += hwctx_to_save->save_incrs; - hwctx_to_save->hwctx.valid = true; - nvhost_job_get_hwctx(job, &hwctx_to_save->hwctx); - } - channel->cur_ctx = hwctx; - if (channel->cur_ctx && channel->cur_ctx->valid) { - need_restore = true; - syncpt_incrs += to_host1x_hwctx(channel->cur_ctx) - ->restore_incrs; - } - } - - syncval = nvhost_syncpt_incr_max(&nvhost_get_host(channel->dev)->syncpt, - p->syncpt, syncpt_incrs); - - job->syncpt_id = p->syncpt; - job->syncpt_incrs = syncpt_incrs; - job->syncpt_end = syncval; - - /* begin a CDMA submit */ - nvhost_cdma_begin(&channel->cdma, job); - - /* push save buffer (pre-gather setup depends on unit) */ - if (hwctx_to_save) - h->save_push(&hwctx_to_save->hwctx, &channel->cdma); - - /* gather restore buffer */ - if (need_restore) - nvhost_cdma_push(&channel->cdma, - nvhost_opcode_gather(to_host1x_hwctx(channel->cur_ctx) - ->restore_size), - to_host1x_hwctx(channel->cur_ctx)->restore_phys); - - /* Switch to 3D - wait for it to complete what it was doing */ - nvhost_cdma_push(&channel->cdma, - nvhost_opcode_setclass(NV_GRAPHICS_3D_CLASS_ID, 0, 0), - nvhost_opcode_imm_incr_syncpt( - host1x_uclass_incr_syncpt_cond_op_done_v(), - p->syncpt)); - nvhost_cdma_push(&channel->cdma, - nvhost_opcode_setclass(NV_HOST1X_CLASS_ID, - host1x_uclass_wait_syncpt_base_r(), 1), - nvhost_class_host_wait_syncpt_base(p->syncpt, - p->waitbase, 1)); - /* Tell 3D to send register value to FIFO */ - nvhost_cdma_push(&channel->cdma, - nvhost_opcode_nonincr(host1x_uclass_indoff_r(), 1), - nvhost_class_host_indoff_reg_read(NV_HOST_MODULE_GR3D, - offset, false)); - nvhost_cdma_push(&channel->cdma, - nvhost_opcode_imm(host1x_uclass_inddata_r(), 0), - NVHOST_OPCODE_NOOP); - /* Increment syncpt to indicate that FIFO can be read */ - nvhost_cdma_push(&channel->cdma, - nvhost_opcode_imm_incr_syncpt( - host1x_uclass_incr_syncpt_cond_immediate_v(), - p->syncpt), - NVHOST_OPCODE_NOOP); - /* Wait for value to be read from FIFO */ - nvhost_cdma_push(&channel->cdma, - nvhost_opcode_nonincr(host1x_uclass_wait_syncpt_base_r(), 1), - nvhost_class_host_wait_syncpt_base(p->syncpt, - p->waitbase, 3)); - /* Indicate submit complete */ - nvhost_cdma_push(&channel->cdma, - nvhost_opcode_nonincr(host1x_uclass_incr_syncpt_base_r(), 1), - nvhost_class_host_incr_syncpt_base(p->waitbase, 4)); - nvhost_cdma_push(&channel->cdma, - NVHOST_OPCODE_NOOP, - nvhost_opcode_imm_incr_syncpt( - host1x_uclass_incr_syncpt_cond_immediate_v(), - p->syncpt)); - - /* end CDMA submit */ - nvhost_cdma_end(&channel->cdma, job); - nvhost_job_put(job); - job = NULL; - - /* - * schedule a context save interrupt (to drain the host FIFO - * if necessary, and to release the restore buffer) - */ - if (hwctx_to_save) { - err = nvhost_intr_add_action( - &nvhost_get_host(channel->dev)->intr, - p->syncpt, - syncval - syncpt_incrs - + hwctx_to_save->save_incrs - - 1, - NVHOST_INTR_ACTION_CTXSAVE, hwctx_to_save, - ctx_waiter, - NULL); - ctx_waiter = NULL; - WARN(err, "Failed to set context save interrupt"); - } - - /* Wait for FIFO to be ready */ - err = nvhost_intr_add_action(&nvhost_get_host(channel->dev)->intr, - p->syncpt, syncval - 2, - NVHOST_INTR_ACTION_WAKEUP, &wq, - read_waiter, - &ref); - read_waiter = NULL; - WARN(err, "Failed to set wakeup interrupt"); - wait_event(wq, - nvhost_syncpt_is_expired(&nvhost_get_host(channel->dev)->syncpt, - p->syncpt, syncval - 2)); - nvhost_intr_put_ref(&nvhost_get_host(channel->dev)->intr, p->syncpt, - ref); - - /* Read the register value from FIFO */ - err = host1x_drain_read_fifo(channel, value, 1, &pending); - - /* Indicate we've read the value */ - nvhost_syncpt_cpu_incr(&nvhost_get_host(channel->dev)->syncpt, - p->syncpt); - - /* Schedule a submit complete interrupt */ - err = nvhost_intr_add_action(&nvhost_get_host(channel->dev)->intr, - p->syncpt, syncval, - NVHOST_INTR_ACTION_SUBMIT_COMPLETE, channel, - completed_waiter, NULL); - completed_waiter = NULL; - WARN(err, "Failed to set submit complete interrupt"); - - mutex_unlock(&channel->submitlock); - -done: - kfree(ctx_waiter); - kfree(read_waiter); - kfree(completed_waiter); - return err; -} - - static int host1x_drain_read_fifo(struct nvhost_channel *ch, u32 *ptr, unsigned int count, unsigned int *pending) { @@ -675,7 +487,6 @@ static int host1x_channel_init(struct nvhost_channel *ch, static const struct nvhost_channel_ops host1x_channel_ops = { .init = host1x_channel_init, .submit = host1x_channel_submit, - .read3dreg = host1x_channel_read_3d_reg, .save_context = host1x_save_context, .drain_read_fifo = host1x_drain_read_fifo, }; diff --git a/drivers/video/tegra/host/nvhost_channel.c b/drivers/video/tegra/host/nvhost_channel.c index fd309ee9917b..626b92970edf 100644 --- a/drivers/video/tegra/host/nvhost_channel.c +++ b/drivers/video/tegra/host/nvhost_channel.c @@ -181,6 +181,17 @@ int nvhost_channel_save_context(struct nvhost_channel *ch) } +int nvhost_channel_read_reg(struct nvhost_channel *ch, + struct nvhost_hwctx *hwctx, + u32 offset, u32 *value) +{ + struct nvhost_driver *drv = to_nvhost_driver(ch->dev->dev.driver); + if (!drv->read_reg) + return -EINVAL; + + return drv->read_reg(ch->dev, ch, hwctx, offset, value); +} + int nvhost_channel_drain_read_fifo(struct nvhost_channel *ch, u32 *ptr, unsigned int count, unsigned int *pending) { diff --git a/drivers/video/tegra/host/nvhost_channel.h b/drivers/video/tegra/host/nvhost_channel.h index d7f096db1ffa..c7c8a71def48 100644 --- a/drivers/video/tegra/host/nvhost_channel.h +++ b/drivers/video/tegra/host/nvhost_channel.h @@ -62,7 +62,7 @@ int nvhost_channel_suspend(struct nvhost_channel *ch); int nvhost_channel_drain_read_fifo(struct nvhost_channel *ch, u32 *ptr, unsigned int count, unsigned int *pending); -int nvhost_channel_read_3d_reg(struct nvhost_channel *channel, +int nvhost_channel_read_reg(struct nvhost_channel *channel, struct nvhost_hwctx *hwctx, u32 offset, u32 *value); diff --git a/include/linux/nvhost.h b/include/linux/nvhost.h index 5f754f04e665..05d9ed7cf8b1 100644 --- a/include/linux/nvhost.h +++ b/include/linux/nvhost.h @@ -27,7 +27,7 @@ #include <linux/types.h> struct nvhost_master; - +struct nvhost_hwctx; struct nvhost_device_power_attr; #define NVHOST_MODULE_MAX_CLOCKS 3 @@ -180,6 +180,13 @@ struct nvhost_driver { /* Clock gating callbacks */ int (*prepare_clockoff)(struct nvhost_device *dev); void (*finalize_clockon)(struct nvhost_device *dev); + + /* Read module register into memory */ + int (*read_reg)(struct nvhost_device *dev, + struct nvhost_channel *ch, + struct nvhost_hwctx *hwctx, + u32 offset, + u32 *value); }; extern int nvhost_driver_register(struct nvhost_driver *); |