Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-or-later
0002 /*
0003  * Copyright (C) 2016 Imagination Technologies
0004  * Author: Paul Burton <paul.burton@mips.com>
0005  */
0006 
0007 #include <linux/kernel.h>
0008 #include <linux/io.h>
0009 #include <linux/mfd/syscon.h>
0010 #include <linux/module.h>
0011 #include <linux/of_address.h>
0012 #include <linux/of_platform.h>
0013 #include <linux/platform_device.h>
0014 #include <linux/regmap.h>
0015 #include <linux/slab.h>
0016 
0017 #include "line-display.h"
0018 
0019 struct img_ascii_lcd_ctx;
0020 
0021 /**
0022  * struct img_ascii_lcd_config - Configuration information about an LCD model
0023  * @num_chars: the number of characters the LCD can display
0024  * @external_regmap: true if registers are in a system controller, else false
0025  * @update: function called to update the LCD
0026  */
0027 struct img_ascii_lcd_config {
0028     unsigned int num_chars;
0029     bool external_regmap;
0030     void (*update)(struct linedisp *linedisp);
0031 };
0032 
0033 /**
0034  * struct img_ascii_lcd_ctx - Private data structure
0035  * @base: the base address of the LCD registers
0036  * @regmap: the regmap through which LCD registers are accessed
0037  * @offset: the offset within regmap to the start of the LCD registers
0038  * @cfg: pointer to the LCD model configuration
0039  * @linedisp: line display structure
0040  * @curr: the string currently displayed on the LCD
0041  */
0042 struct img_ascii_lcd_ctx {
0043     union {
0044         void __iomem *base;
0045         struct regmap *regmap;
0046     };
0047     u32 offset;
0048     const struct img_ascii_lcd_config *cfg;
0049     struct linedisp linedisp;
0050     char curr[] __aligned(8);
0051 };
0052 
0053 /*
0054  * MIPS Boston development board
0055  */
0056 
0057 static void boston_update(struct linedisp *linedisp)
0058 {
0059     struct img_ascii_lcd_ctx *ctx =
0060         container_of(linedisp, struct img_ascii_lcd_ctx, linedisp);
0061     ulong val;
0062 
0063 #if BITS_PER_LONG == 64
0064     val = *((u64 *)&ctx->curr[0]);
0065     __raw_writeq(val, ctx->base);
0066 #elif BITS_PER_LONG == 32
0067     val = *((u32 *)&ctx->curr[0]);
0068     __raw_writel(val, ctx->base);
0069     val = *((u32 *)&ctx->curr[4]);
0070     __raw_writel(val, ctx->base + 4);
0071 #else
0072 # error Not 32 or 64 bit
0073 #endif
0074 }
0075 
0076 static struct img_ascii_lcd_config boston_config = {
0077     .num_chars = 8,
0078     .update = boston_update,
0079 };
0080 
0081 /*
0082  * MIPS Malta development board
0083  */
0084 
0085 static void malta_update(struct linedisp *linedisp)
0086 {
0087     struct img_ascii_lcd_ctx *ctx =
0088         container_of(linedisp, struct img_ascii_lcd_ctx, linedisp);
0089     unsigned int i;
0090     int err = 0;
0091 
0092     for (i = 0; i < linedisp->num_chars; i++) {
0093         err = regmap_write(ctx->regmap,
0094                    ctx->offset + (i * 8), ctx->curr[i]);
0095         if (err)
0096             break;
0097     }
0098 
0099     if (unlikely(err))
0100         pr_err_ratelimited("Failed to update LCD display: %d\n", err);
0101 }
0102 
0103 static struct img_ascii_lcd_config malta_config = {
0104     .num_chars = 8,
0105     .external_regmap = true,
0106     .update = malta_update,
0107 };
0108 
0109 /*
0110  * MIPS SEAD3 development board
0111  */
0112 
0113 enum {
0114     SEAD3_REG_LCD_CTRL      = 0x00,
0115 #define SEAD3_REG_LCD_CTRL_SETDRAM  BIT(7)
0116     SEAD3_REG_LCD_DATA      = 0x08,
0117     SEAD3_REG_CPLD_STATUS       = 0x10,
0118 #define SEAD3_REG_CPLD_STATUS_BUSY  BIT(0)
0119     SEAD3_REG_CPLD_DATA     = 0x18,
0120 #define SEAD3_REG_CPLD_DATA_BUSY    BIT(7)
0121 };
0122 
0123 static int sead3_wait_sm_idle(struct img_ascii_lcd_ctx *ctx)
0124 {
0125     unsigned int status;
0126     int err;
0127 
0128     do {
0129         err = regmap_read(ctx->regmap,
0130                   ctx->offset + SEAD3_REG_CPLD_STATUS,
0131                   &status);
0132         if (err)
0133             return err;
0134     } while (status & SEAD3_REG_CPLD_STATUS_BUSY);
0135 
0136     return 0;
0137 
0138 }
0139 
0140 static int sead3_wait_lcd_idle(struct img_ascii_lcd_ctx *ctx)
0141 {
0142     unsigned int cpld_data;
0143     int err;
0144 
0145     err = sead3_wait_sm_idle(ctx);
0146     if (err)
0147         return err;
0148 
0149     do {
0150         err = regmap_read(ctx->regmap,
0151                   ctx->offset + SEAD3_REG_LCD_CTRL,
0152                   &cpld_data);
0153         if (err)
0154             return err;
0155 
0156         err = sead3_wait_sm_idle(ctx);
0157         if (err)
0158             return err;
0159 
0160         err = regmap_read(ctx->regmap,
0161                   ctx->offset + SEAD3_REG_CPLD_DATA,
0162                   &cpld_data);
0163         if (err)
0164             return err;
0165     } while (cpld_data & SEAD3_REG_CPLD_DATA_BUSY);
0166 
0167     return 0;
0168 }
0169 
0170 static void sead3_update(struct linedisp *linedisp)
0171 {
0172     struct img_ascii_lcd_ctx *ctx =
0173         container_of(linedisp, struct img_ascii_lcd_ctx, linedisp);
0174     unsigned int i;
0175     int err = 0;
0176 
0177     for (i = 0; i < linedisp->num_chars; i++) {
0178         err = sead3_wait_lcd_idle(ctx);
0179         if (err)
0180             break;
0181 
0182         err = regmap_write(ctx->regmap,
0183                    ctx->offset + SEAD3_REG_LCD_CTRL,
0184                    SEAD3_REG_LCD_CTRL_SETDRAM | i);
0185         if (err)
0186             break;
0187 
0188         err = sead3_wait_lcd_idle(ctx);
0189         if (err)
0190             break;
0191 
0192         err = regmap_write(ctx->regmap,
0193                    ctx->offset + SEAD3_REG_LCD_DATA,
0194                    ctx->curr[i]);
0195         if (err)
0196             break;
0197     }
0198 
0199     if (unlikely(err))
0200         pr_err_ratelimited("Failed to update LCD display: %d\n", err);
0201 }
0202 
0203 static struct img_ascii_lcd_config sead3_config = {
0204     .num_chars = 16,
0205     .external_regmap = true,
0206     .update = sead3_update,
0207 };
0208 
0209 static const struct of_device_id img_ascii_lcd_matches[] = {
0210     { .compatible = "img,boston-lcd", .data = &boston_config },
0211     { .compatible = "mti,malta-lcd", .data = &malta_config },
0212     { .compatible = "mti,sead3-lcd", .data = &sead3_config },
0213     { /* sentinel */ }
0214 };
0215 MODULE_DEVICE_TABLE(of, img_ascii_lcd_matches);
0216 
0217 /**
0218  * img_ascii_lcd_probe() - probe an LCD display device
0219  * @pdev: the LCD platform device
0220  *
0221  * Probe an LCD display device, ensuring that we have the required resources in
0222  * order to access the LCD & setting up private data as well as sysfs files.
0223  *
0224  * Return: 0 on success, else -ERRNO
0225  */
0226 static int img_ascii_lcd_probe(struct platform_device *pdev)
0227 {
0228     const struct of_device_id *match;
0229     const struct img_ascii_lcd_config *cfg;
0230     struct device *dev = &pdev->dev;
0231     struct img_ascii_lcd_ctx *ctx;
0232     int err;
0233 
0234     match = of_match_device(img_ascii_lcd_matches, dev);
0235     if (!match)
0236         return -ENODEV;
0237 
0238     cfg = match->data;
0239     ctx = devm_kzalloc(dev, sizeof(*ctx) + cfg->num_chars, GFP_KERNEL);
0240     if (!ctx)
0241         return -ENOMEM;
0242 
0243     if (cfg->external_regmap) {
0244         ctx->regmap = syscon_node_to_regmap(dev->parent->of_node);
0245         if (IS_ERR(ctx->regmap))
0246             return PTR_ERR(ctx->regmap);
0247 
0248         if (of_property_read_u32(dev->of_node, "offset", &ctx->offset))
0249             return -EINVAL;
0250     } else {
0251         ctx->base = devm_platform_ioremap_resource(pdev, 0);
0252         if (IS_ERR(ctx->base))
0253             return PTR_ERR(ctx->base);
0254     }
0255 
0256     err = linedisp_register(&ctx->linedisp, dev, cfg->num_chars, ctx->curr,
0257                 cfg->update);
0258     if (err)
0259         return err;
0260 
0261     /* for backwards compatibility */
0262     err = compat_only_sysfs_link_entry_to_kobj(&dev->kobj,
0263                            &ctx->linedisp.dev.kobj,
0264                            "message", NULL);
0265     if (err)
0266         goto err_unregister;
0267 
0268     platform_set_drvdata(pdev, ctx);
0269     return 0;
0270 
0271 err_unregister:
0272     linedisp_unregister(&ctx->linedisp);
0273     return err;
0274 }
0275 
0276 /**
0277  * img_ascii_lcd_remove() - remove an LCD display device
0278  * @pdev: the LCD platform device
0279  *
0280  * Remove an LCD display device, freeing private resources & ensuring that the
0281  * driver stops using the LCD display registers.
0282  *
0283  * Return: 0
0284  */
0285 static int img_ascii_lcd_remove(struct platform_device *pdev)
0286 {
0287     struct img_ascii_lcd_ctx *ctx = platform_get_drvdata(pdev);
0288 
0289     sysfs_remove_link(&pdev->dev.kobj, "message");
0290     linedisp_unregister(&ctx->linedisp);
0291     return 0;
0292 }
0293 
0294 static struct platform_driver img_ascii_lcd_driver = {
0295     .driver = {
0296         .name       = "img-ascii-lcd",
0297         .of_match_table = img_ascii_lcd_matches,
0298     },
0299     .probe  = img_ascii_lcd_probe,
0300     .remove = img_ascii_lcd_remove,
0301 };
0302 module_platform_driver(img_ascii_lcd_driver);
0303 
0304 MODULE_DESCRIPTION("Imagination Technologies ASCII LCD Display");
0305 MODULE_AUTHOR("Paul Burton <paul.burton@mips.com>");
0306 MODULE_LICENSE("GPL");