|  | // SPDX-License-Identifier: GPL-2.0-only | 
|  | /* linux/drivers/parport/parport_ax88796.c | 
|  | * | 
|  | * (c) 2005,2006 Simtec Electronics | 
|  | *	Ben Dooks <ben@simtec.co.uk> | 
|  | */ | 
|  |  | 
|  | #include <linux/module.h> | 
|  | #include <linux/kernel.h> | 
|  | #include <linux/parport.h> | 
|  | #include <linux/interrupt.h> | 
|  | #include <linux/errno.h> | 
|  | #include <linux/platform_device.h> | 
|  | #include <linux/slab.h> | 
|  |  | 
|  | #include <asm/io.h> | 
|  | #include <asm/irq.h> | 
|  |  | 
|  | #define AX_SPR_BUSY		(1<<7) | 
|  | #define AX_SPR_ACK		(1<<6) | 
|  | #define AX_SPR_PE		(1<<5) | 
|  | #define AX_SPR_SLCT		(1<<4) | 
|  | #define AX_SPR_ERR		(1<<3) | 
|  |  | 
|  | #define AX_CPR_nDOE		(1<<5) | 
|  | #define AX_CPR_SLCTIN		(1<<3) | 
|  | #define AX_CPR_nINIT		(1<<2) | 
|  | #define AX_CPR_ATFD		(1<<1) | 
|  | #define AX_CPR_STRB		(1<<0) | 
|  |  | 
|  | struct ax_drvdata { | 
|  | struct parport		*parport; | 
|  | struct parport_state	 suspend; | 
|  |  | 
|  | struct device		*dev; | 
|  | struct resource		*io; | 
|  |  | 
|  | unsigned char		 irq_enabled; | 
|  |  | 
|  | void __iomem		*base; | 
|  | void __iomem		*spp_data; | 
|  | void __iomem		*spp_spr; | 
|  | void __iomem		*spp_cpr; | 
|  | }; | 
|  |  | 
|  | static inline struct ax_drvdata *pp_to_drv(struct parport *p) | 
|  | { | 
|  | return p->private_data; | 
|  | } | 
|  |  | 
|  | static unsigned char | 
|  | parport_ax88796_read_data(struct parport *p) | 
|  | { | 
|  | struct ax_drvdata *dd = pp_to_drv(p); | 
|  |  | 
|  | return readb(dd->spp_data); | 
|  | } | 
|  |  | 
|  | static void | 
|  | parport_ax88796_write_data(struct parport *p, unsigned char data) | 
|  | { | 
|  | struct ax_drvdata *dd = pp_to_drv(p); | 
|  |  | 
|  | writeb(data, dd->spp_data); | 
|  | } | 
|  |  | 
|  | static unsigned char | 
|  | parport_ax88796_read_control(struct parport *p) | 
|  | { | 
|  | struct ax_drvdata *dd = pp_to_drv(p); | 
|  | unsigned int cpr = readb(dd->spp_cpr); | 
|  | unsigned int ret = 0; | 
|  |  | 
|  | if (!(cpr & AX_CPR_STRB)) | 
|  | ret |= PARPORT_CONTROL_STROBE; | 
|  |  | 
|  | if (!(cpr & AX_CPR_ATFD)) | 
|  | ret |= PARPORT_CONTROL_AUTOFD; | 
|  |  | 
|  | if (cpr & AX_CPR_nINIT) | 
|  | ret |= PARPORT_CONTROL_INIT; | 
|  |  | 
|  | if (!(cpr & AX_CPR_SLCTIN)) | 
|  | ret |= PARPORT_CONTROL_SELECT; | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static void | 
|  | parport_ax88796_write_control(struct parport *p, unsigned char control) | 
|  | { | 
|  | struct ax_drvdata *dd = pp_to_drv(p); | 
|  | unsigned int cpr = readb(dd->spp_cpr); | 
|  |  | 
|  | cpr &= AX_CPR_nDOE; | 
|  |  | 
|  | if (!(control & PARPORT_CONTROL_STROBE)) | 
|  | cpr |= AX_CPR_STRB; | 
|  |  | 
|  | if (!(control & PARPORT_CONTROL_AUTOFD)) | 
|  | cpr |= AX_CPR_ATFD; | 
|  |  | 
|  | if (control & PARPORT_CONTROL_INIT) | 
|  | cpr |= AX_CPR_nINIT; | 
|  |  | 
|  | if (!(control & PARPORT_CONTROL_SELECT)) | 
|  | cpr |= AX_CPR_SLCTIN; | 
|  |  | 
|  | dev_dbg(dd->dev, "write_control: ctrl=%02x, cpr=%02x\n", control, cpr); | 
|  | writeb(cpr, dd->spp_cpr); | 
|  |  | 
|  | if (parport_ax88796_read_control(p) != control) { | 
|  | dev_err(dd->dev, "write_control: read != set (%02x, %02x)\n", | 
|  | parport_ax88796_read_control(p), control); | 
|  | } | 
|  | } | 
|  |  | 
|  | static unsigned char | 
|  | parport_ax88796_read_status(struct parport *p) | 
|  | { | 
|  | struct ax_drvdata *dd = pp_to_drv(p); | 
|  | unsigned int status = readb(dd->spp_spr); | 
|  | unsigned int ret = 0; | 
|  |  | 
|  | if (status & AX_SPR_BUSY) | 
|  | ret |= PARPORT_STATUS_BUSY; | 
|  |  | 
|  | if (status & AX_SPR_ACK) | 
|  | ret |= PARPORT_STATUS_ACK; | 
|  |  | 
|  | if (status & AX_SPR_ERR) | 
|  | ret |= PARPORT_STATUS_ERROR; | 
|  |  | 
|  | if (status & AX_SPR_SLCT) | 
|  | ret |= PARPORT_STATUS_SELECT; | 
|  |  | 
|  | if (status & AX_SPR_PE) | 
|  | ret |= PARPORT_STATUS_PAPEROUT; | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static unsigned char | 
|  | parport_ax88796_frob_control(struct parport *p, unsigned char mask, | 
|  | unsigned char val) | 
|  | { | 
|  | struct ax_drvdata *dd = pp_to_drv(p); | 
|  | unsigned char old = parport_ax88796_read_control(p); | 
|  |  | 
|  | dev_dbg(dd->dev, "frob: mask=%02x, val=%02x, old=%02x\n", | 
|  | mask, val, old); | 
|  |  | 
|  | parport_ax88796_write_control(p, (old & ~mask) | val); | 
|  | return old; | 
|  | } | 
|  |  | 
|  | static void | 
|  | parport_ax88796_enable_irq(struct parport *p) | 
|  | { | 
|  | struct ax_drvdata *dd = pp_to_drv(p); | 
|  | unsigned long flags; | 
|  |  | 
|  | local_irq_save(flags); | 
|  | if (!dd->irq_enabled) { | 
|  | enable_irq(p->irq); | 
|  | dd->irq_enabled = 1; | 
|  | } | 
|  | local_irq_restore(flags); | 
|  | } | 
|  |  | 
|  | static void | 
|  | parport_ax88796_disable_irq(struct parport *p) | 
|  | { | 
|  | struct ax_drvdata *dd = pp_to_drv(p); | 
|  | unsigned long flags; | 
|  |  | 
|  | local_irq_save(flags); | 
|  | if (dd->irq_enabled) { | 
|  | disable_irq(p->irq); | 
|  | dd->irq_enabled = 0; | 
|  | } | 
|  | local_irq_restore(flags); | 
|  | } | 
|  |  | 
|  | static void | 
|  | parport_ax88796_data_forward(struct parport *p) | 
|  | { | 
|  | struct ax_drvdata *dd = pp_to_drv(p); | 
|  | void __iomem *cpr = dd->spp_cpr; | 
|  |  | 
|  | writeb((readb(cpr) & ~AX_CPR_nDOE), cpr); | 
|  | } | 
|  |  | 
|  | static void | 
|  | parport_ax88796_data_reverse(struct parport *p) | 
|  | { | 
|  | struct ax_drvdata *dd = pp_to_drv(p); | 
|  | void __iomem *cpr = dd->spp_cpr; | 
|  |  | 
|  | writeb(readb(cpr) | AX_CPR_nDOE, cpr); | 
|  | } | 
|  |  | 
|  | static void | 
|  | parport_ax88796_init_state(struct pardevice *d, struct parport_state *s) | 
|  | { | 
|  | struct ax_drvdata *dd = pp_to_drv(d->port); | 
|  |  | 
|  | memset(s, 0, sizeof(struct parport_state)); | 
|  |  | 
|  | dev_dbg(dd->dev, "init_state: %p: state=%p\n", d, s); | 
|  | s->u.ax88796.cpr = readb(dd->spp_cpr); | 
|  | } | 
|  |  | 
|  | static void | 
|  | parport_ax88796_save_state(struct parport *p, struct parport_state *s) | 
|  | { | 
|  | struct ax_drvdata *dd = pp_to_drv(p); | 
|  |  | 
|  | dev_dbg(dd->dev, "save_state: %p: state=%p\n", p, s); | 
|  | s->u.ax88796.cpr = readb(dd->spp_cpr); | 
|  | } | 
|  |  | 
|  | static void | 
|  | parport_ax88796_restore_state(struct parport *p, struct parport_state *s) | 
|  | { | 
|  | struct ax_drvdata *dd = pp_to_drv(p); | 
|  |  | 
|  | dev_dbg(dd->dev, "restore_state: %p: state=%p\n", p, s); | 
|  | writeb(s->u.ax88796.cpr, dd->spp_cpr); | 
|  | } | 
|  |  | 
|  | static struct parport_operations parport_ax88796_ops = { | 
|  | .write_data	= parport_ax88796_write_data, | 
|  | .read_data	= parport_ax88796_read_data, | 
|  |  | 
|  | .write_control	= parport_ax88796_write_control, | 
|  | .read_control	= parport_ax88796_read_control, | 
|  | .frob_control	= parport_ax88796_frob_control, | 
|  |  | 
|  | .read_status	= parport_ax88796_read_status, | 
|  |  | 
|  | .enable_irq	= parport_ax88796_enable_irq, | 
|  | .disable_irq	= parport_ax88796_disable_irq, | 
|  |  | 
|  | .data_forward	= parport_ax88796_data_forward, | 
|  | .data_reverse	= parport_ax88796_data_reverse, | 
|  |  | 
|  | .init_state	= parport_ax88796_init_state, | 
|  | .save_state	= parport_ax88796_save_state, | 
|  | .restore_state	= parport_ax88796_restore_state, | 
|  |  | 
|  | .epp_write_data	= parport_ieee1284_epp_write_data, | 
|  | .epp_read_data	= parport_ieee1284_epp_read_data, | 
|  | .epp_write_addr	= parport_ieee1284_epp_write_addr, | 
|  | .epp_read_addr	= parport_ieee1284_epp_read_addr, | 
|  |  | 
|  | .ecp_write_data	= parport_ieee1284_ecp_write_data, | 
|  | .ecp_read_data	= parport_ieee1284_ecp_read_data, | 
|  | .ecp_write_addr	= parport_ieee1284_ecp_write_addr, | 
|  |  | 
|  | .compat_write_data	= parport_ieee1284_write_compat, | 
|  | .nibble_read_data	= parport_ieee1284_read_nibble, | 
|  | .byte_read_data		= parport_ieee1284_read_byte, | 
|  |  | 
|  | .owner		= THIS_MODULE, | 
|  | }; | 
|  |  | 
|  | static int parport_ax88796_probe(struct platform_device *pdev) | 
|  | { | 
|  | struct device *_dev = &pdev->dev; | 
|  | struct ax_drvdata *dd; | 
|  | struct parport *pp; | 
|  | struct resource *res; | 
|  | unsigned long size; | 
|  | int spacing; | 
|  | int irq; | 
|  | int ret; | 
|  |  | 
|  | dd = kzalloc(sizeof(*dd), GFP_KERNEL); | 
|  | if (!dd) | 
|  | return -ENOMEM; | 
|  |  | 
|  | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | 
|  | if (res == NULL) { | 
|  | dev_err(_dev, "no MEM specified\n"); | 
|  | ret = -ENXIO; | 
|  | goto exit_mem; | 
|  | } | 
|  |  | 
|  | size = resource_size(res); | 
|  | spacing = size / 3; | 
|  |  | 
|  | dd->io = request_mem_region(res->start, size, pdev->name); | 
|  | if (dd->io == NULL) { | 
|  | dev_err(_dev, "cannot reserve memory\n"); | 
|  | ret = -ENXIO; | 
|  | goto exit_mem; | 
|  | } | 
|  |  | 
|  | dd->base = ioremap(res->start, size); | 
|  | if (dd->base == NULL) { | 
|  | dev_err(_dev, "cannot ioremap region\n"); | 
|  | ret = -ENXIO; | 
|  | goto exit_res; | 
|  | } | 
|  |  | 
|  | irq = platform_get_irq(pdev, 0); | 
|  | if (irq <= 0) | 
|  | irq = PARPORT_IRQ_NONE; | 
|  |  | 
|  | pp = parport_register_port((unsigned long)dd->base, irq, | 
|  | PARPORT_DMA_NONE, | 
|  | &parport_ax88796_ops); | 
|  |  | 
|  | if (pp == NULL) { | 
|  | dev_err(_dev, "failed to register parallel port\n"); | 
|  | ret = -ENOMEM; | 
|  | goto exit_unmap; | 
|  | } | 
|  |  | 
|  | pp->private_data = dd; | 
|  | dd->parport = pp; | 
|  | dd->dev = _dev; | 
|  |  | 
|  | dd->spp_data = dd->base; | 
|  | dd->spp_spr  = dd->base + (spacing * 1); | 
|  | dd->spp_cpr  = dd->base + (spacing * 2); | 
|  |  | 
|  | /* initialise the port controls */ | 
|  | writeb(AX_CPR_STRB, dd->spp_cpr); | 
|  |  | 
|  | if (irq >= 0) { | 
|  | /* request irq */ | 
|  | ret = request_irq(irq, parport_irq_handler, | 
|  | IRQF_TRIGGER_FALLING, pdev->name, pp); | 
|  |  | 
|  | if (ret < 0) | 
|  | goto exit_port; | 
|  |  | 
|  | dd->irq_enabled = 1; | 
|  | } | 
|  |  | 
|  | platform_set_drvdata(pdev, pp); | 
|  |  | 
|  | dev_info(_dev, "attached parallel port driver\n"); | 
|  | parport_announce_port(pp); | 
|  |  | 
|  | return 0; | 
|  |  | 
|  | exit_port: | 
|  | parport_remove_port(pp); | 
|  | exit_unmap: | 
|  | iounmap(dd->base); | 
|  | exit_res: | 
|  | release_mem_region(dd->io->start, size); | 
|  | exit_mem: | 
|  | kfree(dd); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int parport_ax88796_remove(struct platform_device *pdev) | 
|  | { | 
|  | struct parport *p = platform_get_drvdata(pdev); | 
|  | struct ax_drvdata *dd = pp_to_drv(p); | 
|  |  | 
|  | free_irq(p->irq, p); | 
|  | parport_remove_port(p); | 
|  | iounmap(dd->base); | 
|  | release_mem_region(dd->io->start, resource_size(dd->io)); | 
|  | kfree(dd); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | #ifdef CONFIG_PM | 
|  |  | 
|  | static int parport_ax88796_suspend(struct platform_device *dev, | 
|  | pm_message_t state) | 
|  | { | 
|  | struct parport *p = platform_get_drvdata(dev); | 
|  | struct ax_drvdata *dd = pp_to_drv(p); | 
|  |  | 
|  | parport_ax88796_save_state(p, &dd->suspend); | 
|  | writeb(AX_CPR_nDOE | AX_CPR_STRB, dd->spp_cpr); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int parport_ax88796_resume(struct platform_device *dev) | 
|  | { | 
|  | struct parport *p = platform_get_drvdata(dev); | 
|  | struct ax_drvdata *dd = pp_to_drv(p); | 
|  |  | 
|  | parport_ax88796_restore_state(p, &dd->suspend); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | #else | 
|  | #define parport_ax88796_suspend NULL | 
|  | #define parport_ax88796_resume  NULL | 
|  | #endif | 
|  |  | 
|  | MODULE_ALIAS("platform:ax88796-pp"); | 
|  |  | 
|  | static struct platform_driver axdrv = { | 
|  | .driver		= { | 
|  | .name	= "ax88796-pp", | 
|  | }, | 
|  | .probe		= parport_ax88796_probe, | 
|  | .remove		= parport_ax88796_remove, | 
|  | .suspend	= parport_ax88796_suspend, | 
|  | .resume		= parport_ax88796_resume, | 
|  | }; | 
|  |  | 
|  | module_platform_driver(axdrv); | 
|  |  | 
|  | MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>"); | 
|  | MODULE_DESCRIPTION("AX88796 Parport parallel port driver"); | 
|  | MODULE_LICENSE("GPL"); |