mirror of
				git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
				synced 2025-09-04 20:19:47 +08:00 
			
		
		
		
	can: c_can: Add d_can suspend resume support
Adds suspend resume support to DCAN driver which enables DCAN power down mode bit (PDR). Then DCAN will ack the local power-down mode by setting PDA bit in STATUS register. Signed-off-by: AnilKumar Ch <anilkumar@ti.com> Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
This commit is contained in:
		
							parent
							
								
									4cdd34b268
								
							
						
					
					
						commit
						8212003260
					
				| @ -46,6 +46,9 @@ | ||||
| #define IF_ENUM_REG_LEN		11 | ||||
| #define C_CAN_IFACE(reg, iface)	(C_CAN_IF1_##reg + (iface) * IF_ENUM_REG_LEN) | ||||
| 
 | ||||
| /* control extension register D_CAN specific */ | ||||
| #define CONTROL_EX_PDR		BIT(8) | ||||
| 
 | ||||
| /* control register */ | ||||
| #define CONTROL_TEST		BIT(7) | ||||
| #define CONTROL_CCE		BIT(6) | ||||
| @ -65,6 +68,7 @@ | ||||
| #define TEST_BASIC		BIT(2) | ||||
| 
 | ||||
| /* status register */ | ||||
| #define STATUS_PDA		BIT(10) | ||||
| #define STATUS_BOFF		BIT(7) | ||||
| #define STATUS_EWARN		BIT(6) | ||||
| #define STATUS_EPASS		BIT(5) | ||||
| @ -164,6 +168,9 @@ | ||||
| /* minimum timeout for checking BUSY status */ | ||||
| #define MIN_TIMEOUT_VALUE	6 | ||||
| 
 | ||||
| /* Wait for ~1 sec for INIT bit */ | ||||
| #define INIT_WAIT_MS		1000 | ||||
| 
 | ||||
| /* napi related */ | ||||
| #define C_CAN_NAPI_WEIGHT	C_CAN_MSG_OBJ_RX_NUM | ||||
| 
 | ||||
| @ -1153,6 +1160,77 @@ struct net_device *alloc_c_can_dev(void) | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(alloc_c_can_dev); | ||||
| 
 | ||||
| #ifdef CONFIG_PM | ||||
| int c_can_power_down(struct net_device *dev) | ||||
| { | ||||
| 	u32 val; | ||||
| 	unsigned long time_out; | ||||
| 	struct c_can_priv *priv = netdev_priv(dev); | ||||
| 
 | ||||
| 	if (!(dev->flags & IFF_UP)) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	WARN_ON(priv->type != BOSCH_D_CAN); | ||||
| 
 | ||||
| 	/* set PDR value so the device goes to power down mode */ | ||||
| 	val = priv->read_reg(priv, C_CAN_CTRL_EX_REG); | ||||
| 	val |= CONTROL_EX_PDR; | ||||
| 	priv->write_reg(priv, C_CAN_CTRL_EX_REG, val); | ||||
| 
 | ||||
| 	/* Wait for the PDA bit to get set */ | ||||
| 	time_out = jiffies + msecs_to_jiffies(INIT_WAIT_MS); | ||||
| 	while (!(priv->read_reg(priv, C_CAN_STS_REG) & STATUS_PDA) && | ||||
| 				time_after(time_out, jiffies)) | ||||
| 		cpu_relax(); | ||||
| 
 | ||||
| 	if (time_after(jiffies, time_out)) | ||||
| 		return -ETIMEDOUT; | ||||
| 
 | ||||
| 	c_can_stop(dev); | ||||
| 
 | ||||
| 	c_can_pm_runtime_put_sync(priv); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(c_can_power_down); | ||||
| 
 | ||||
| int c_can_power_up(struct net_device *dev) | ||||
| { | ||||
| 	u32 val; | ||||
| 	unsigned long time_out; | ||||
| 	struct c_can_priv *priv = netdev_priv(dev); | ||||
| 
 | ||||
| 	if (!(dev->flags & IFF_UP)) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	WARN_ON(priv->type != BOSCH_D_CAN); | ||||
| 
 | ||||
| 	c_can_pm_runtime_get_sync(priv); | ||||
| 
 | ||||
| 	/* Clear PDR and INIT bits */ | ||||
| 	val = priv->read_reg(priv, C_CAN_CTRL_EX_REG); | ||||
| 	val &= ~CONTROL_EX_PDR; | ||||
| 	priv->write_reg(priv, C_CAN_CTRL_EX_REG, val); | ||||
| 	val = priv->read_reg(priv, C_CAN_CTRL_REG); | ||||
| 	val &= ~CONTROL_INIT; | ||||
| 	priv->write_reg(priv, C_CAN_CTRL_REG, val); | ||||
| 
 | ||||
| 	/* Wait for the PDA bit to get clear */ | ||||
| 	time_out = jiffies + msecs_to_jiffies(INIT_WAIT_MS); | ||||
| 	while ((priv->read_reg(priv, C_CAN_STS_REG) & STATUS_PDA) && | ||||
| 				time_after(time_out, jiffies)) | ||||
| 		cpu_relax(); | ||||
| 
 | ||||
| 	if (time_after(jiffies, time_out)) | ||||
| 		return -ETIMEDOUT; | ||||
| 
 | ||||
| 	c_can_start(dev); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(c_can_power_up); | ||||
| #endif | ||||
| 
 | ||||
| void free_c_can_dev(struct net_device *dev) | ||||
| { | ||||
| 	free_candev(dev); | ||||
|  | ||||
| @ -24,6 +24,7 @@ | ||||
| 
 | ||||
| enum reg { | ||||
| 	C_CAN_CTRL_REG = 0, | ||||
| 	C_CAN_CTRL_EX_REG, | ||||
| 	C_CAN_STS_REG, | ||||
| 	C_CAN_ERR_CNT_REG, | ||||
| 	C_CAN_BTR_REG, | ||||
| @ -104,6 +105,7 @@ static const u16 reg_map_c_can[] = { | ||||
| 
 | ||||
| static const u16 reg_map_d_can[] = { | ||||
| 	[C_CAN_CTRL_REG]	= 0x00, | ||||
| 	[C_CAN_CTRL_EX_REG]	= 0x02, | ||||
| 	[C_CAN_STS_REG]		= 0x04, | ||||
| 	[C_CAN_ERR_CNT_REG]	= 0x08, | ||||
| 	[C_CAN_BTR_REG]		= 0x0C, | ||||
| @ -166,6 +168,7 @@ struct c_can_priv { | ||||
| 	unsigned int tx_echo; | ||||
| 	void *priv;		/* for board-specific data */ | ||||
| 	u16 irqstatus; | ||||
| 	enum c_can_dev_id type; | ||||
| }; | ||||
| 
 | ||||
| struct net_device *alloc_c_can_dev(void); | ||||
| @ -173,4 +176,9 @@ void free_c_can_dev(struct net_device *dev); | ||||
| int register_c_can_dev(struct net_device *dev); | ||||
| void unregister_c_can_dev(struct net_device *dev); | ||||
| 
 | ||||
| #ifdef CONFIG_PM | ||||
| int c_can_power_up(struct net_device *dev); | ||||
| int c_can_power_down(struct net_device *dev); | ||||
| #endif | ||||
| 
 | ||||
| #endif /* C_CAN_H */ | ||||
|  | ||||
| @ -182,6 +182,7 @@ static int __devinit c_can_plat_probe(struct platform_device *pdev) | ||||
| 	priv->device = &pdev->dev; | ||||
| 	priv->can.clock.freq = clk_get_rate(clk); | ||||
| 	priv->priv = clk; | ||||
| 	priv->type = id->driver_data; | ||||
| 
 | ||||
| 	platform_set_drvdata(pdev, dev); | ||||
| 	SET_NETDEV_DEV(dev, &pdev->dev); | ||||
| @ -232,6 +233,65 @@ static int __devexit c_can_plat_remove(struct platform_device *pdev) | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| #ifdef CONFIG_PM | ||||
| static int c_can_suspend(struct platform_device *pdev, pm_message_t state) | ||||
| { | ||||
| 	int ret; | ||||
| 	struct net_device *ndev = platform_get_drvdata(pdev); | ||||
| 	struct c_can_priv *priv = netdev_priv(ndev); | ||||
| 
 | ||||
| 	if (priv->type != BOSCH_D_CAN) { | ||||
| 		dev_warn(&pdev->dev, "Not supported\n"); | ||||
| 		return 0; | ||||
| 	} | ||||
| 
 | ||||
| 	if (netif_running(ndev)) { | ||||
| 		netif_stop_queue(ndev); | ||||
| 		netif_device_detach(ndev); | ||||
| 	} | ||||
| 
 | ||||
| 	ret = c_can_power_down(ndev); | ||||
| 	if (ret) { | ||||
| 		netdev_err(ndev, "failed to enter power down mode\n"); | ||||
| 		return ret; | ||||
| 	} | ||||
| 
 | ||||
| 	priv->can.state = CAN_STATE_SLEEPING; | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int c_can_resume(struct platform_device *pdev) | ||||
| { | ||||
| 	int ret; | ||||
| 	struct net_device *ndev = platform_get_drvdata(pdev); | ||||
| 	struct c_can_priv *priv = netdev_priv(ndev); | ||||
| 
 | ||||
| 	if (priv->type != BOSCH_D_CAN) { | ||||
| 		dev_warn(&pdev->dev, "Not supported\n"); | ||||
| 		return 0; | ||||
| 	} | ||||
| 
 | ||||
| 	ret = c_can_power_up(ndev); | ||||
| 	if (ret) { | ||||
| 		netdev_err(ndev, "Still in power down mode\n"); | ||||
| 		return ret; | ||||
| 	} | ||||
| 
 | ||||
| 	priv->can.state = CAN_STATE_ERROR_ACTIVE; | ||||
| 
 | ||||
| 	if (netif_running(ndev)) { | ||||
| 		netif_device_attach(ndev); | ||||
| 		netif_start_queue(ndev); | ||||
| 	} | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| #else | ||||
| #define c_can_suspend NULL | ||||
| #define c_can_resume NULL | ||||
| #endif | ||||
| 
 | ||||
| static struct platform_driver c_can_plat_driver = { | ||||
| 	.driver = { | ||||
| 		.name = KBUILD_MODNAME, | ||||
| @ -240,6 +300,8 @@ static struct platform_driver c_can_plat_driver = { | ||||
| 	}, | ||||
| 	.probe = c_can_plat_probe, | ||||
| 	.remove = __devexit_p(c_can_plat_remove), | ||||
| 	.suspend = c_can_suspend, | ||||
| 	.resume = c_can_resume, | ||||
| 	.id_table = c_can_id_table, | ||||
| }; | ||||
| 
 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 AnilKumar Ch
						AnilKumar Ch