| lh | 9ed821d | 2023-04-07 01:36:19 -0700 | [diff] [blame] | 1 | /* | 
 | 2 |  * LED control using Renesas TPU | 
 | 3 |  * | 
 | 4 |  *  Copyright (C) 2011 Magnus Damm | 
 | 5 |  * | 
 | 6 |  * This program is free software; you can redistribute it and/or modify | 
 | 7 |  * it under the terms of the GNU General Public License as published by | 
 | 8 |  * the Free Software Foundation; either version 2 of the License | 
 | 9 |  * | 
 | 10 |  * This program is distributed in the hope that it will be useful, | 
 | 11 |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
 | 12 |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
 | 13 |  * GNU General Public License for more details. | 
 | 14 |  * | 
 | 15 |  * You should have received a copy of the GNU General Public License | 
 | 16 |  * along with this program; if not, write to the Free Software | 
 | 17 |  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | 
 | 18 |  */ | 
 | 19 |  | 
 | 20 | #include <linux/module.h> | 
 | 21 | #include <linux/init.h> | 
 | 22 | #include <linux/platform_device.h> | 
 | 23 | #include <linux/spinlock.h> | 
 | 24 | #include <linux/printk.h> | 
 | 25 | #include <linux/ioport.h> | 
 | 26 | #include <linux/io.h> | 
 | 27 | #include <linux/clk.h> | 
 | 28 | #include <linux/leds.h> | 
 | 29 | #include <linux/platform_data/leds-renesas-tpu.h> | 
 | 30 | #include <linux/gpio.h> | 
 | 31 | #include <linux/err.h> | 
 | 32 | #include <linux/slab.h> | 
 | 33 | #include <linux/pm_runtime.h> | 
 | 34 | #include <linux/workqueue.h> | 
 | 35 |  | 
 | 36 | enum r_tpu_pin { R_TPU_PIN_UNUSED, R_TPU_PIN_GPIO, R_TPU_PIN_GPIO_FN }; | 
 | 37 | enum r_tpu_timer { R_TPU_TIMER_UNUSED, R_TPU_TIMER_ON }; | 
 | 38 |  | 
 | 39 | struct r_tpu_priv { | 
 | 40 | 	struct led_classdev ldev; | 
 | 41 | 	void __iomem *mapbase; | 
 | 42 | 	struct clk *clk; | 
 | 43 | 	struct platform_device *pdev; | 
 | 44 | 	enum r_tpu_pin pin_state; | 
 | 45 | 	enum r_tpu_timer timer_state; | 
 | 46 | 	unsigned long min_rate; | 
 | 47 | 	unsigned int refresh_rate; | 
 | 48 | 	struct work_struct work; | 
 | 49 | 	enum led_brightness new_brightness; | 
 | 50 | }; | 
 | 51 |  | 
 | 52 | static DEFINE_SPINLOCK(r_tpu_lock); | 
 | 53 |  | 
 | 54 | #define TSTR -1 /* Timer start register (shared register) */ | 
 | 55 | #define TCR  0 /* Timer control register (+0x00) */ | 
 | 56 | #define TMDR 1 /* Timer mode register (+0x04) */ | 
 | 57 | #define TIOR 2 /* Timer I/O control register (+0x08) */ | 
 | 58 | #define TIER 3 /* Timer interrupt enable register (+0x0c) */ | 
 | 59 | #define TSR  4 /* Timer status register (+0x10) */ | 
 | 60 | #define TCNT 5 /* Timer counter (+0x14) */ | 
 | 61 | #define TGRA 6 /* Timer general register A (+0x18) */ | 
 | 62 | #define TGRB 7 /* Timer general register B (+0x1c) */ | 
 | 63 | #define TGRC 8 /* Timer general register C (+0x20) */ | 
 | 64 | #define TGRD 9 /* Timer general register D (+0x24) */ | 
 | 65 |  | 
 | 66 | static inline unsigned short r_tpu_read(struct r_tpu_priv *p, int reg_nr) | 
 | 67 | { | 
 | 68 | 	struct led_renesas_tpu_config *cfg = p->pdev->dev.platform_data; | 
 | 69 | 	void __iomem *base = p->mapbase; | 
 | 70 | 	unsigned long offs = reg_nr << 2; | 
 | 71 |  | 
 | 72 | 	if (reg_nr == TSTR) | 
 | 73 | 		return ioread16(base - cfg->channel_offset); | 
 | 74 |  | 
 | 75 | 	return ioread16(base + offs); | 
 | 76 | } | 
 | 77 |  | 
 | 78 | static inline void r_tpu_write(struct r_tpu_priv *p, int reg_nr, | 
 | 79 | 			       unsigned short value) | 
 | 80 | { | 
 | 81 | 	struct led_renesas_tpu_config *cfg = p->pdev->dev.platform_data; | 
 | 82 | 	void __iomem *base = p->mapbase; | 
 | 83 | 	unsigned long offs = reg_nr << 2; | 
 | 84 |  | 
 | 85 | 	if (reg_nr == TSTR) { | 
 | 86 | 		iowrite16(value, base - cfg->channel_offset); | 
 | 87 | 		return; | 
 | 88 | 	} | 
 | 89 |  | 
 | 90 | 	iowrite16(value, base + offs); | 
 | 91 | } | 
 | 92 |  | 
 | 93 | static void r_tpu_start_stop_ch(struct r_tpu_priv *p, int start) | 
 | 94 | { | 
 | 95 | 	struct led_renesas_tpu_config *cfg = p->pdev->dev.platform_data; | 
 | 96 | 	unsigned long flags, value; | 
 | 97 |  | 
 | 98 | 	/* start stop register shared by multiple timer channels */ | 
 | 99 | 	spin_lock_irqsave(&r_tpu_lock, flags); | 
 | 100 | 	value = r_tpu_read(p, TSTR); | 
 | 101 |  | 
 | 102 | 	if (start) | 
 | 103 | 		value |= 1 << cfg->timer_bit; | 
 | 104 | 	else | 
 | 105 | 		value &= ~(1 << cfg->timer_bit); | 
 | 106 |  | 
 | 107 | 	r_tpu_write(p, TSTR, value); | 
 | 108 | 	spin_unlock_irqrestore(&r_tpu_lock, flags); | 
 | 109 | } | 
 | 110 |  | 
 | 111 | static int r_tpu_enable(struct r_tpu_priv *p, enum led_brightness brightness) | 
 | 112 | { | 
 | 113 | 	struct led_renesas_tpu_config *cfg = p->pdev->dev.platform_data; | 
 | 114 | 	int prescaler[] = { 1, 4, 16, 64 }; | 
 | 115 | 	int k, ret; | 
 | 116 | 	unsigned long rate, tmp; | 
 | 117 |  | 
 | 118 | 	if (p->timer_state == R_TPU_TIMER_ON) | 
 | 119 | 		return 0; | 
 | 120 |  | 
 | 121 | 	/* wake up device and enable clock */ | 
 | 122 | 	pm_runtime_get_sync(&p->pdev->dev); | 
 | 123 | 	ret = clk_enable(p->clk); | 
 | 124 | 	if (ret) { | 
 | 125 | 		dev_err(&p->pdev->dev, "cannot enable clock\n"); | 
 | 126 | 		return ret; | 
 | 127 | 	} | 
 | 128 |  | 
 | 129 | 	/* make sure channel is disabled */ | 
 | 130 | 	r_tpu_start_stop_ch(p, 0); | 
 | 131 |  | 
 | 132 | 	/* get clock rate after enabling it */ | 
 | 133 | 	rate = clk_get_rate(p->clk); | 
 | 134 |  | 
 | 135 | 	/* pick the lowest acceptable rate */ | 
 | 136 | 	for (k = 0; k < ARRAY_SIZE(prescaler); k++) | 
 | 137 | 		if ((rate / prescaler[k]) < p->min_rate) | 
 | 138 | 			break; | 
 | 139 |  | 
 | 140 | 	if (!k) { | 
 | 141 | 		dev_err(&p->pdev->dev, "clock rate mismatch\n"); | 
 | 142 | 		goto err0; | 
 | 143 | 	} | 
 | 144 | 	dev_dbg(&p->pdev->dev, "rate = %lu, prescaler %u\n", | 
 | 145 | 		rate, prescaler[k - 1]); | 
 | 146 |  | 
 | 147 | 	/* clear TCNT on TGRB match, count on rising edge, set prescaler */ | 
 | 148 | 	r_tpu_write(p, TCR, 0x0040 | (k - 1)); | 
 | 149 |  | 
 | 150 | 	/* output 0 until TGRA, output 1 until TGRB */ | 
 | 151 | 	r_tpu_write(p, TIOR, 0x0002); | 
 | 152 |  | 
 | 153 | 	rate /= prescaler[k - 1] * p->refresh_rate; | 
 | 154 | 	r_tpu_write(p, TGRB, rate); | 
 | 155 | 	dev_dbg(&p->pdev->dev, "TRGB = 0x%04lx\n", rate); | 
 | 156 |  | 
 | 157 | 	tmp = (cfg->max_brightness - brightness) * rate; | 
 | 158 | 	r_tpu_write(p, TGRA, tmp / cfg->max_brightness); | 
 | 159 | 	dev_dbg(&p->pdev->dev, "TRGA = 0x%04lx\n", tmp / cfg->max_brightness); | 
 | 160 |  | 
 | 161 | 	/* PWM mode */ | 
 | 162 | 	r_tpu_write(p, TMDR, 0x0002); | 
 | 163 |  | 
 | 164 | 	/* enable channel */ | 
 | 165 | 	r_tpu_start_stop_ch(p, 1); | 
 | 166 |  | 
 | 167 | 	p->timer_state = R_TPU_TIMER_ON; | 
 | 168 | 	return 0; | 
 | 169 |  err0: | 
 | 170 | 	clk_disable(p->clk); | 
 | 171 | 	pm_runtime_put_sync(&p->pdev->dev); | 
 | 172 | 	return -ENOTSUPP; | 
 | 173 | } | 
 | 174 |  | 
 | 175 | static void r_tpu_disable(struct r_tpu_priv *p) | 
 | 176 | { | 
 | 177 | 	if (p->timer_state == R_TPU_TIMER_UNUSED) | 
 | 178 | 		return; | 
 | 179 |  | 
 | 180 | 	/* disable channel */ | 
 | 181 | 	r_tpu_start_stop_ch(p, 0); | 
 | 182 |  | 
 | 183 | 	/* stop clock and mark device as idle */ | 
 | 184 | 	clk_disable(p->clk); | 
 | 185 | 	pm_runtime_put_sync(&p->pdev->dev); | 
 | 186 |  | 
 | 187 | 	p->timer_state = R_TPU_TIMER_UNUSED; | 
 | 188 | } | 
 | 189 |  | 
 | 190 | static void r_tpu_set_pin(struct r_tpu_priv *p, enum r_tpu_pin new_state, | 
 | 191 | 			  enum led_brightness brightness) | 
 | 192 | { | 
 | 193 | 	struct led_renesas_tpu_config *cfg = p->pdev->dev.platform_data; | 
 | 194 |  | 
 | 195 | 	if (p->pin_state == new_state) { | 
 | 196 | 		if (p->pin_state == R_TPU_PIN_GPIO) | 
 | 197 | 			gpio_set_value(cfg->pin_gpio, brightness); | 
 | 198 | 		return; | 
 | 199 | 	} | 
 | 200 |  | 
 | 201 | 	if (p->pin_state == R_TPU_PIN_GPIO) | 
 | 202 | 		gpio_free(cfg->pin_gpio); | 
 | 203 |  | 
 | 204 | 	if (p->pin_state == R_TPU_PIN_GPIO_FN) | 
 | 205 | 		gpio_free(cfg->pin_gpio_fn); | 
 | 206 |  | 
 | 207 | 	if (new_state == R_TPU_PIN_GPIO) { | 
 | 208 | 		gpio_request(cfg->pin_gpio, cfg->name); | 
 | 209 | 		gpio_direction_output(cfg->pin_gpio, !!brightness); | 
 | 210 | 	} | 
 | 211 | 	if (new_state == R_TPU_PIN_GPIO_FN) | 
 | 212 | 		gpio_request(cfg->pin_gpio_fn, cfg->name); | 
 | 213 |  | 
 | 214 | 	p->pin_state = new_state; | 
 | 215 | } | 
 | 216 |  | 
 | 217 | static void r_tpu_work(struct work_struct *work) | 
 | 218 | { | 
 | 219 | 	struct r_tpu_priv *p = container_of(work, struct r_tpu_priv, work); | 
 | 220 | 	enum led_brightness brightness = p->new_brightness; | 
 | 221 |  | 
 | 222 | 	r_tpu_disable(p); | 
 | 223 |  | 
 | 224 | 	/* off and maximum are handled as GPIO pins, in between PWM */ | 
 | 225 | 	if ((brightness == 0) || (brightness == p->ldev.max_brightness)) | 
 | 226 | 		r_tpu_set_pin(p, R_TPU_PIN_GPIO, brightness); | 
 | 227 | 	else { | 
 | 228 | 		r_tpu_set_pin(p, R_TPU_PIN_GPIO_FN, 0); | 
 | 229 | 		r_tpu_enable(p, brightness); | 
 | 230 | 	} | 
 | 231 | } | 
 | 232 |  | 
 | 233 | static void r_tpu_set_brightness(struct led_classdev *ldev, | 
 | 234 | 				 enum led_brightness brightness) | 
 | 235 | { | 
 | 236 | 	struct r_tpu_priv *p = container_of(ldev, struct r_tpu_priv, ldev); | 
 | 237 | 	p->new_brightness = brightness; | 
 | 238 | 	schedule_work(&p->work); | 
 | 239 | } | 
 | 240 |  | 
 | 241 | static int __devinit r_tpu_probe(struct platform_device *pdev) | 
 | 242 | { | 
 | 243 | 	struct led_renesas_tpu_config *cfg = pdev->dev.platform_data; | 
 | 244 | 	struct r_tpu_priv *p; | 
 | 245 | 	struct resource *res; | 
 | 246 | 	int ret = -ENXIO; | 
 | 247 |  | 
 | 248 | 	if (!cfg) { | 
 | 249 | 		dev_err(&pdev->dev, "missing platform data\n"); | 
 | 250 | 		goto err0; | 
 | 251 | 	} | 
 | 252 |  | 
 | 253 | 	p = kzalloc(sizeof(*p), GFP_KERNEL); | 
 | 254 | 	if (p == NULL) { | 
 | 255 | 		dev_err(&pdev->dev, "failed to allocate driver data\n"); | 
 | 256 | 		ret = -ENOMEM; | 
 | 257 | 		goto err0; | 
 | 258 | 	} | 
 | 259 |  | 
 | 260 | 	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | 
 | 261 | 	if (!res) { | 
 | 262 | 		dev_err(&pdev->dev, "failed to get I/O memory\n"); | 
 | 263 | 		goto err1; | 
 | 264 | 	} | 
 | 265 |  | 
 | 266 | 	/* map memory, let mapbase point to our channel */ | 
 | 267 | 	p->mapbase = ioremap_nocache(res->start, resource_size(res)); | 
 | 268 | 	if (p->mapbase == NULL) { | 
 | 269 | 		dev_err(&pdev->dev, "failed to remap I/O memory\n"); | 
 | 270 | 		goto err1; | 
 | 271 | 	} | 
 | 272 |  | 
 | 273 | 	/* get hold of clock */ | 
 | 274 | 	p->clk = clk_get(&pdev->dev, NULL); | 
 | 275 | 	if (IS_ERR(p->clk)) { | 
 | 276 | 		dev_err(&pdev->dev, "cannot get clock\n"); | 
 | 277 | 		ret = PTR_ERR(p->clk); | 
 | 278 | 		goto err2; | 
 | 279 | 	} | 
 | 280 |  | 
 | 281 | 	p->pdev = pdev; | 
 | 282 | 	p->pin_state = R_TPU_PIN_UNUSED; | 
 | 283 | 	p->timer_state = R_TPU_TIMER_UNUSED; | 
 | 284 | 	p->refresh_rate = cfg->refresh_rate ? cfg->refresh_rate : 100; | 
 | 285 | 	r_tpu_set_pin(p, R_TPU_PIN_GPIO, LED_OFF); | 
 | 286 | 	platform_set_drvdata(pdev, p); | 
 | 287 |  | 
 | 288 | 	INIT_WORK(&p->work, r_tpu_work); | 
 | 289 |  | 
 | 290 | 	p->ldev.name = cfg->name; | 
 | 291 | 	p->ldev.brightness = LED_OFF; | 
 | 292 | 	p->ldev.max_brightness = cfg->max_brightness; | 
 | 293 | 	p->ldev.brightness_set = r_tpu_set_brightness; | 
 | 294 | 	p->ldev.flags |= LED_CORE_SUSPENDRESUME; | 
 | 295 | 	ret = led_classdev_register(&pdev->dev, &p->ldev); | 
 | 296 | 	if (ret < 0) | 
 | 297 | 		goto err3; | 
 | 298 |  | 
 | 299 | 	/* max_brightness may be updated by the LED core code */ | 
 | 300 | 	p->min_rate = p->ldev.max_brightness * p->refresh_rate; | 
 | 301 |  | 
 | 302 | 	pm_runtime_enable(&pdev->dev); | 
 | 303 | 	return 0; | 
 | 304 |  | 
 | 305 |  err3: | 
 | 306 | 	r_tpu_set_pin(p, R_TPU_PIN_UNUSED, LED_OFF); | 
 | 307 | 	clk_put(p->clk); | 
 | 308 |  err2: | 
 | 309 | 	iounmap(p->mapbase); | 
 | 310 |  err1: | 
 | 311 | 	kfree(p); | 
 | 312 |  err0: | 
 | 313 | 	return ret; | 
 | 314 | } | 
 | 315 |  | 
 | 316 | static int __devexit r_tpu_remove(struct platform_device *pdev) | 
 | 317 | { | 
 | 318 | 	struct r_tpu_priv *p = platform_get_drvdata(pdev); | 
 | 319 |  | 
 | 320 | 	r_tpu_set_brightness(&p->ldev, LED_OFF); | 
 | 321 | 	led_classdev_unregister(&p->ldev); | 
 | 322 | 	cancel_work_sync(&p->work); | 
 | 323 | 	r_tpu_disable(p); | 
 | 324 | 	r_tpu_set_pin(p, R_TPU_PIN_UNUSED, LED_OFF); | 
 | 325 |  | 
 | 326 | 	pm_runtime_disable(&pdev->dev); | 
 | 327 | 	clk_put(p->clk); | 
 | 328 |  | 
 | 329 | 	iounmap(p->mapbase); | 
 | 330 | 	kfree(p); | 
 | 331 | 	return 0; | 
 | 332 | } | 
 | 333 |  | 
 | 334 | static struct platform_driver r_tpu_device_driver = { | 
 | 335 | 	.probe		= r_tpu_probe, | 
 | 336 | 	.remove		= __devexit_p(r_tpu_remove), | 
 | 337 | 	.driver		= { | 
 | 338 | 		.name	= "leds-renesas-tpu", | 
 | 339 | 	} | 
 | 340 | }; | 
 | 341 |  | 
 | 342 | module_platform_driver(r_tpu_device_driver); | 
 | 343 |  | 
 | 344 | MODULE_AUTHOR("Magnus Damm"); | 
 | 345 | MODULE_DESCRIPTION("Renesas TPU LED Driver"); | 
 | 346 | MODULE_LICENSE("GPL v2"); |